Скачать CV

Backend

Ack в Symfony Messenger это не забор платежа

Redis-транспорт отдаёт сообщение после падения снова. У Horizon то же правило. Ack после вызова провайдера это второй платёж.

Карту списали дважды на хосте Symfony, где уже крутился Horizon на Laravel. Laravel Horizon и Symfony Messenger в этом стеке оба сидят на Redis. Доставка минимум один раз. Воркер php-fpm в контейнере queue умер после 200 от провайдера и до ack. Redis отдал сообщение следующему. У обработчика не было забора. Карту списали снова.

Пользователи увидели две выписки. В логе воркера один успех и один ретрай. Ack после HTTP это как ждёт второй платёж. Middleware unique забором не считаю. Оно закрывает пересечение двух живых воркеров. Не закрывает ретрай после SIGKILL.

Забор это таблица с уникальным ключом id заказа плюс имя шага. Insert до HTTP. Если insert ударился в unique, выходим и ack.

Ack после HTTP это второй платёж. Сначала уникальный ключ.
Ack после HTTP это второй платёж. Сначала уникальный ключ.

Ack в конце, захват в начале

Локальный insert в транзакции. Вызов провайдера после захвата ключа. Ссылка провайдера лежит рядом с ключом. Повтор использует этот id. Ретраи Messenger ограничены. Три попытки, потом failure transport и алерт. Timeout job короче резерва Redis.

Тот же шаблон в заметке про Horizon на этом сайте. Symfony это не другая продуктовая задача. Это тот же список Redis с другим бинарником воркера.

Я сравнил auto-ack на Redis-транспорте с ack после обработчика. Auto-ack до HTTP теряет сообщение, если процесс умрёт посреди вызова. Ack после вызова без забора дублирует вызов. Сначала захват в MySQL, потом HTTP, сохранить ссылку, потом ack. Failure transport не должен снова бить HTTP, не попав в unique.

Второй запуск бьётся в unique, берёт id провайдера, делает ack и выходит.
Второй запуск бьётся в unique, берёт id провайдера, делает ack и выходит.

Что на шину не кладу

Логин, CSRF и /api/user остаются синхронными. Сообщение, которое должно закончиться до HTML, это медленный запрос с лишними отказами. Письмо с контакта может подождать. Пересборка кэша может подождать. Платёж без забора ждать не может.

Очередь, кэш и сессии делят Redis. Префиксы разделены, чтобы вытеснение slab листинга не сняло зарезервированное сообщение. Этот префикс часть обработчика, не строка в логе после инцидента.

Упавшие сообщения Messenger выглядели здоровыми, а в таблице платежей было две строки на один шаг. Считаю insert забора против 200 провайдера. Если 200 больше, ack прошёл после вызова без unique-захвата.

Какой опыт из этого

Меряю 200 провайдера против уникальных строк на шаг заказа, не throughput Messenger. Redis отдаст снова. Второй запуск обработчика должен сделать ack без списания.

Middleware unique и ack после HTTP забором не считаю. Сначала insert ключа. Сохранить id провайдера. Ack в конце.

Правило, которое оставляю: тот же забор, что у Horizon, префиксы отдельно от кэша, логин не на шину. Timeout короче резерва Redis. Три попытки, потом failure transport.

К заметкам