Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як зробити обробку зовнішніх подій і синхронізацію ідемпотентною та уникнути дублікатів?

У інтеграціях дублікати неминучі: провайдер повторює вебхук, бо не дочекався відповіді; джоба повторюється після тайм-ауту, хоча перша спроба насправді завершилася; користувач двічі натискає «Синхронізувати». Обробка має давати той самий результат незалежно від кількості повторів.

1. Унікальність на вході - журнал оброблених подій:

Schema::create('processed_events', function (Blueprint $table) {
    $table->string('provider');
    $table->string('event_id');
    $table->timestamp('processed_at');
    $table->primary(['provider', 'event_id']);
});
public function handle(): void
{
    DB::transaction(function () {
        $inserted = DB::table('processed_events')->insertOrIgnore([
            'provider' => 'stripe',
            'event_id' => $this->event['id'],
            'processed_at' => now(),
        ]);

        if ($inserted === 0) {
            return;   // уже оброблено
        }

        $this->applyPayment($this->event);
    });
}

Унікальний ключ у базі - єдиний надійний захист від гонитви: перевірка «чи є запис» і вставка окремими запитами пропускають паралельні дублікати.

2. Ідемпотентні операції за природою:

  • updateOrCreate/upsert за зовнішнім ідентифікатором (external_id) замість create;
  • «встановити статус paid» замість «збільшити суму на X»;
  • переходи станів з перевіркою: «оплачене» не можна оплатити вдруге.

3. Унікальні джоби - щоб не ставити в чергу однакову роботу:

#[UniqueFor(600)]
final class SyncCustomer implements ShouldQueue, ShouldBeUnique
{
    public function uniqueId(): string
    {
        return (string) $this->customerId;
    }
}

Поки джоба для цього клієнта в черзі, нові такі ж відкидаються. Потребує кешу з атомарними блокуваннями і спільного кешу для всіх серверів.

4. Вихідні операції з побічними ефектами (створення платежу, відправка SMS) - ключ ідемпотентності в запиті до провайдера, стабільний для повторів тієї самої операції (Idempotency-Key: order-42-capture). Тоді повтор джоби після тайм-ауту не створить другий платіж.

5. Звірка (reconciliation) - останній рубіж: періодичне порівняння стану у вас і в провайдера (оплати за добу, залишки на складі), автоматичне виправлення розбіжностей і звіт про них. Ловить те, що загубилося попри всі механізми.

Типова помилка: покладатися на ShouldBeUnique як на захист від повторної обробки. Він не дає поставити дублікат у чергу, але не захищає від повторного виконання вже взятої джоби після збою воркера - для цього потрібні ідемпотентні операції й унікальні ключі в базі.

Докладніше в документації: Черги: унікальні джоби

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання