Есть сервис, который логирует запросы. Время, ip, userAgent, и один параметр из query string, назовём его query_param. Логирование происходит в таблицу в БД. Назовём её redirect. Соответственно в таблице redirect есть поля createdAt, ip, ...., query_id. Поле query_id - ссылка на таблицу query, где хранятся значения параметра query_param из запроса. Таблица query имеет всего два поля id и value (уникальный индекс). Логика работы с этой таблицей:
1. Получили значение параметра query_param из запроса
2. Ищем в таблице query запись, где value равно значению из п. 1
3. Если запись не нашли - создаём.
4. В итоге получаем id записи из таблицы query
5. Далее работа уже идёт с таблицей redirect
Сервис логирования регистрирует около миллиона запросов в день, и не редки случаи, когда в один момент прилетают два запроса с одинаковым значением query_param, которого нет ещё в таблице query. И вот тут возникает состояние гонки. Оба процесса в один момент ищут в query значение query_param - оба не находят. Оба пытаются вставить новое значение. Какой то из запросов срабатывает быстрей, а второй падает с ошибкой UniqueConstraintViolationException, т.к. на поле стоит уникальный индекс. Проблема в том, что после этого закрывается entityManager и дальнейшая работа с базой невозможна. Я знаю что можно пересоздать entityManager, но теряется весь контекст. Может у кого есть мысли как решить данную проблему?
На symfony перешёл совсем недавно. До этого работал с Yii - там всё просто. Я ловлю эту ошибку и понимаю что кто-то успел влезть со вставкой вперёд этого процесса, значит стоит опять поискать значение в этой таблице. Вообще есть идея следующая: если я не нашёл в таблице query запись с этим значением - засовывать в тот же redis метку, что я приступил ко вставке. Но перед этим проверять, что другой процесс не вставляет это значение. Но мне кажется это костыльно. Может есть какое-то изящное решение на уровне Doctrine?
По сути в вашем случае запись в базу данных является критической секцией. Соответственно вам необходима реализация любого из способов синхронизации потоков для избегания состояния гонки. Вариантов множество, но конкретно в Symfony именно для этого (и для других подобных сценариев) существует компонент Lock, он предоставляет реализации готовых примитивов синхронизации.
Вот здесь можно посмотреть на практически готовый пример того как должен выглядеть ваш код коммита изменений с учётом использования lock'а.
Помимо варианта с Lock: ON CONFLICT (expression) DO NOTHING и похожие решения в других СУБД могут быть иногда довольно подходящими в виду простоты реализации...
Это выглядит буквально вот так:
$sql = <<<SQL
INSERT INTO table (id, value)
VALUES (555, \'uniqValue\')
ON CONFLICT (value) DO NOTHING
SQL;
$this->em->getConnection()->executeQuery($sql);
Если это некие логи или словари с простой логикой хранения, то в самый раз...