Заявка: идемпотентн...
 
Уведомления
Очистить всё

Заявка: идемпотентные вебхуки, или как мы дважды провели один платёж

6 Сообщения
5 Пользователи
9 Реакции
426 Просмотры
GalinaKorneva
Сообщения: 30
Автор темы
Читает чужие PR бэкенд-разработчица
Присоединился: 4 года назад
[#24]

Хочу рассказать на ноябрьской встрече минут 15 про вебхуки от платёжной системы. История такая: платёжка прислала одно и то же уведомление три раза за 40 секунд, наш обработчик в Sidekiq дважды честно провёл платёж, клиенту на баланс упали деньги два раза, а потом неделю разбирались с бухгалтерией. Ничего экзотического, просто никто не принял всерьёз, что «доставка хотя бы один раз» значит ровно это. Покажу, как сделали: уникальный индекс на id события и запись события в той же транзакции, что и проводка.

class PaymentWebhookJob
  include Sidekiq::Job
  sidekiq_options retry: 10

  def perform(event_id, payload)
    ActiveRecord::Base.transaction do
      WebhookEvent.create!(external_id: event_id, payload: payload)
      Payments::Apply.call(payload)
    end
  rescue ActiveRecord::RecordNotUnique
    Rails.logger.info("duplicate webhook #{event_id}")
  end
end

Если проводка упала, транзакция откатится вместе с записью события и ретрай пройдёт заново. Отдельно расскажу, почему 25 ретраев по умолчанию для платежей опасны.


5 ответов
viktor.petrov
Сообщения: 26
Читает чужие PR бэкенд-разработчик
Присоединился: 4 года назад

Ставлю тебя на 22 ноября первым слотом. Тема как раз для тех, кто думает, что у них такого не бывает


Ответ
TimurK62
Сообщения: 31
Читает чужие PR бэкенд-разработчик
Присоединился: 3 года назад

Интересно, сколько стоит этот уникальный индекс на вставке, если вебхуков тысячи в минуту. Принесёшь цифры? Я бы ещё сравнил с вариантом через SET NX в Redis, по задержке он наверняка быстрее, но там свои вопросы с TTL.


Ответ
1 ответ
Егор64
Присоединился: 3 года назад

Читает чужие PR тимлид
Сообщения: 30

Redis для такого я бы не брал: TTL истёк, платёжка повторила событие через сутки, и привет. Уникальный индекс в базе живёт столько, сколько живёт таблица, а тысячи вставок в минуту PostgreSQL даже не заметит.


Ответ
kostya.sokurov
Сообщения: 73
Держит прод бэкенд-разработчик, senior
Присоединился: 4 года назад

Приду. Покажи ещё, где держать payload: в jsonb или отдельными колонками, это всегда спрашивают


Ответ
GalinaKorneva
Сообщения: 30
Автор темы
Читает чужие PR бэкенд-разработчица
Присоединился: 4 года назад

Цифры принесу, но они скучные: в пиках у нас около 2 тысяч событий в час, вставка с индексом на графиках не видна совсем. Payload храним в jsonb, всё старше 90 дней раз в неделю чистит отдельная джоба. Про Redis Егор ответил ровно то, что я сказала бы сама.


Ответ