Недавно я столкнулся с проблемой в реальном времени приложения.
Я понял, что у меня все больше и больше исключений параллелизма и блокировок с базой данных.
В основном я начинаю транзакцию, которая требует SELECT
и INSERT
на том же столе, чтобы совершить.
Но поскольку нагрузка действительно велика, каждая транзакция блокирует таблицу, в большинстве случаев она настолько быстра, что не вызывает никаких проблем, но есть момент, когда блокировки начинают ожидать все больше и больше.
Мне удалось несколько решить эту проблему путем настройки запросов.
Хотя сейчас я хотел бы написать несколько тестов с PHPUnit, чтобы проверить мое исправление и избежать каких-либо регрессий.
Я не смог найти никаких материалов о том, как это сделать.
Поскольку PHP не является многопоточным, я понятия не имею, как можно выполнить параллельные запросы в одном тесте для проверки.
По сути, я хотел бы иметь возможность выполнять несколько вызовов в одном тесте, чтобы убедиться, что все в порядке.
Я знаю, что мог бы попытаться выполнить некоторые высокоуровневые тесты, напрямую запросив http-сервер и загрузив все приложение, но, поскольку моя проблема связана с отдельной библиотекой, я бы предпочел проверить ее самостоятельно.
Есть идеи?
Короткий ответ: нет хорошего способа протестировать одновременное чтение / запись в реальной базе данных с помощью PHPUnit. Это просто не правильный инструмент для этой работы.
Но есть несколько аспектов хорошего решения для проверки этого. Сначала код может (и должен) быть написан для обработки всех возможных проблем. Система баз данных, такая как Postgres, сразу выйдет из строя при блокировках и проблемах транзакций. Для элегантной обработки я использую код, который выглядит примерно так (псевдокод, также используется для ответа Другой вопрос):
begin transaction
while not successful and count < 5
try
execute sql
commit
except
if error code is '40P01' or '55P03'
# Deadlock or lock not available
sleep a random time (200 ms to 1 sec) * number of retries
else if error code is '40001' or '25P02'
# "In failed sql transaction" or serialized transaction failure
rollback
sleep a random time (200 ms to 1 sec) * number of retries
begin transaction
else if error message is 'There is no active transaction'
sleep a random time (200 ms to 1 sec) * number of retries
begin transaction
increment count
Затем создайте два набора тестов: один набор должен подтвердить, что код правильно обрабатывает ситуации (т.е. модульные тесты). Другой набор тестов предназначен для среды (т. Е. Интеграционные / функциональные тесты).
Модульные тесты
Я считаю, что это трудная ситуация для воспроизведения в тесте PHPUnit, который подключается к базе данных, и использование реальной базы данных не подходит для настоящего модульного теста. Вместо этого создайте заглушки PDO и модульные тесты, которые генерируют все виды исключений из базы данных. Это подтверждает, что код работает как положено, но не тестирует параллелизм ни в одной реальной базе данных, например.:
$iterationCount = 0;
$db->runInTransaction(function() use (&$iterationCount) {
$iterationCount++;
if ($iterationCount === 1) {
$exception = new PDOExceptionStub('Deadlock');
$exception->setCode('40P01');
throw $exception;
}
});
// First time fails, second time succeeds
$this->assertEquals(2, $iterationCount, 'Expected 2 iterations of runInTransaction');
Напишите полный набор тестов, которые не подключаются к БД, но подтверждают логику.
Интеграционные тесты
Как вы обнаружили, PHPUnit просто не подходит для проведения нагрузочного теста. Это не подходит для чего-то более сложного, чем последовательные юнит-тесты и интеграционные тесты. Вы можете запустить несколько экземпляров PHPUnit одновременно, чтобы увеличить нагрузку на базу данных. Однако я считаю, что это выходит за рамки того, для чего оно предназначалось, плюс оно не помогает вам отслеживать проблемы в базе данных. Поэтому я не вижу возможности обойти тесты более высокого уровня, которых вы хотите избежать.
Но ваша библиотека может быть протестирована без запуска вашего полного приложения. Я бы создал простейшее из возможных приложений только для его тестирования. Он может иметь один или несколько сценариев CLI, которые подключаются к базе данных. Эти сценарии могут порождаться несколько раз для загрузки базы данных. Или создайте простую веб-страницу с библиотекой и используйте любое из множества приложений для нагрузочного тестирования, чтобы протестировать ее.
Других решений пока нет …