Comments 14
Отличная статья! Именно так и нужно делиться опытом и проводить ретроспективный анализ своей работы — это дает на много больше опыта, чем бездумный кодинг “на авось”, который сейчас особенно популярен из-за LLM.
Молодец. Да это хороший вектор. А почему именно redis а не rabbitMQ? Redis кажется больше для разгрузки основной базы. Первый раз сталкиваюсь что бы её использовать в качестве брокера сообщений
Redis дает функционал который подходит для работы с очередью. С помощью него и можно реализовать паттерн Producer-Consumer.
Если в двух словах, мы можем релазиовать через него FIFO (first in, first out) так же как в RabbitMQ.
PS
Это не совсем моя зона экспертизы, если что-то не так сказал, буду рад если поправят более опытные
Получается вначале у тебя была проблема с тем происходила потеря данных и проблема дублей, затем ты вставил прослойку с редисом которая никак не решает проблему с потерей данных и дублированием,а потом понял что надо очередь перекладывать в бд которая опять же может упасть/словить потерю данных? Другими словами ты не решил практически ни одной проблемы из тех что были изначально, а просто все переложил на очереди что бы отдать result ok за 50мс вне зависимости от настоящего результата, я верно понял?
Почему бы вам не переформулировать все это, но исключить весь токсичный тон из текста? Так и карма будет в порядке и людям будем общаться с вами проще.
Лол, чел, в БД дубли не появлялись. Ошибка просто выдавалась в эндпоинтах. А почему внезапно приходили дубли? Почти наверняка из-за таймаута. Хоть POST запрос неидемпотентен (его НЕЛЬЗЯ ретраить), некоторые "хорошие" сервисы всё равно делают это. Отсюда и дубли при таймауте. Так что Ваше замечание некорректно. Просто автор, скорее всего, забыл это упомянуть, или даже не задумался о том, куда исчезли дубли)
Стоит посмотреть в сторону Redis Streams, если в приоритете остаться на том стеке, что используется сейчас
Сейчас события потребляются из очереди и после просто из неё пропадают (но будут в базе, возможно), но, а если воркер упадет сразу же после взятия события, не выполнив никакой работы, событие просто испарится?
Стоит рассмотреть вариант историчности событий, ну и их запись сразу на диск (а Redis Streams это все как раз умеет), сможешь правильно обрабатывать события из очередей, еще и перечитывать их, если понадобится
# 4. Быстрый ответ
Что произойдет, если на этапе записи в БД не будет найден клиент с переданным в запросе user_id?
(Вы уже ответили "ОК", сразу)
Не зная нюансов работы с платёжной системой, не становится понятнее, как может быть полезен быстрый ответ, не гарантирующий, что за ним стоит реальная работа и проверка данных и всё такое. "Мы вас услышали".
А зачем в этой схеме редис? По идее вы можете писать таску сразу в БД и джобой ее брать, при параллельной обработки брать лок
либо делаете аутбокс а воркеры это отдельные сервисы которые из брокера высчитывают
У вас здесь возможен некоторый race condition, если его так вообще можно назвать, я бы даже назвал это просто ошибкой логики.
Вы добавляете новый запрос в payments_queue только если он ещё не был обработан. Проверяете это через запись, которая появляется только после успешной обработки.
При этом изначально заявленная проблема с дублированием запросов по всей логике остаётся, ведь ваш воркер может не успеть обработать таску или обработать ее неуспешно.
Рассмотрим гипотетическую ситуацию: от платёжного сервиса вам приходит два одинаковых уведомления подряд.
Допустим, все ваши воркеры выключены, тогда никто гарантированно не добавит в Redis запись processed для таски, а значит в очередь гарантированно попадут две одинаковые таски.
Или, например, запросы придут во время таймаута в вашем воркере. Ситуация аналогична прошлой.
При этом если в базе данных есть индексы на уникальность (а судя по вашему описанию проблемы — они есть), то process_task упадет с ошибкой и ваш except всегда будет возвращать эту дублированную таску в очередь.
Я не работал с очередями в Redis, может быть там всё проще, чем я думаю, но хочу предложить такое решение: перед добавлением таски в очередь добавлять запись, например, f"received:{payment_id}". И проверять именно её наличие, а не наличие f"processed:{payment_id}". В таком случае проблема с дублированием запросов решится.
То есть нужно проверять на дубликаты не по статусу успешной обработки в воркере-обработчике, а по статусу добавления непосредственно в очередь. Вроде бы это вполне логично.
Потенциально еще может возникнуть проблема с тем, что два одинаковых запроса попадут в разные воркеры-приемники. В таком случае не исключаю возможности уже настоящего race condition, когда оба воркера успеют проверить наличие received записи и оба получат False. Было бы интересно узнать как с этим бороться. И может быть я выдумал себе эту проблему.
Что за курс или где обучаетесь?
Мой первый опыт обработки вебхуков: как я учился делать надёжный бэкенд на Python