Как избежать лока таблицы, если нужно запретить получение записи до тех пор, пока эта запись находится в процессе создания?
Ситуация такая:
- Есть два сервиса, которые общаются между собой с помощью gcloud pubsub
- Первый сервис принимает запрос на отправку балк сообщений (т.е. сообщение сразу отправляется на несколько адресов). Сплитит этот запрос на количество адресов в запросе. Затем отправляет все в очередь.
- Другой сервис пулит эти сообщения (мы используем сабскрибшн пул) и начинает отправлять нотификации пользователю, который создал запрос на отправку балков.
Проблема в том, что одна нотификация не должна отправляться более чем один раз. Т.е. мы проверяем в бд, нет ли такой нотификации, и если нет, то создаем ее.
Но при пулле, когда первая запись из очереди заходить на обработку, и начинается создание нотификации, приходит уже другая запись из очереди, и тоже начинает создавать нотификацию, т.к. предидущий процесс создания не завершился, соответственно данных в бд нет.
Мы использовали лок таблицы, но у нас очень хайлоад проект, поэтому лок приводил к куче 500 ошибкам при попытке отправить большое количество балков.
Может есть какой-то вариант, залокать строку? Или, возможно, кто-то сталкивался с подобной ситуацией и знает лучшее решение.
Буду очень благодарна за советы.
Ответы (2 шт):
Расскажу как у нас.
- Никто на прямую не стучится в базу кроме основного сервиса базы данных. Баз несколько и сервисов столько же тоже.
- Все запросы к сервисам 1 проходят через MQ (RabbitMQ, MSMQ) который выстраивает очередь для сообщений и отправляет уже запросы напрямую к сервисам 1. Как обрабатывать ту или иную очередь указывается логикой брокера. Есть процессы которые могут запускаться параллельно, а некоторые должны работать строго по очереди (следующий по очереди запрос ожидает завершение первого).
- MQ получив реквест указывает на что он получил его, и отправляет ответом его message id.
- Далее процесс инициализировавший запрос получив номер в очереди проверяет периодически статус его запроса, либо ждет уведомление о завершении в callback.
- Выстраивая очерёдность мы избегаем взаимные блокировки которые могут замедлить обработку запросов.
По поводу
Но при пулле, когда первая запись из очереди заходить на обработку, и начинается создание нотификации, приходит уже другая запись из очереди, и тоже начинает создавать нотификацию, т.к. предидущий процесс создания не завершился, соответственно данных в бд нет.
Обработку таких запросов нужно выполнять по очереди. Если очередь очень быстро переполняется есть повод подумать дальше над усовершенствованием процесса (оптимизация, добавление новых серверов, разделение данных .....)
А что значит "отправить нотификацию"?
Если что, "exactly-once delivery semantics" (куда угодно) не бывает. Получатель должен уметь идемпотентность.
Можно ситуацию чуть улучшить, если сделать еще получатель промежуточный. Например воркер отправляет сообщение промежуточному, который отвечает за конечную отправку.
Если промежуточный не справляются, можно сделать несколько промежуточных, разбить по хэш от ид пользователя или балка.