プレイブック

サポートされているデータベースはリースで動作します (Db Backed Jobs With Leases)

Conditional UPDATE を使用したリース、スタックしたジョブを保存するウォッチャー、およびメッセージを裸にするだけでは制作費を支払うのに十分ではない理由。

分散型決済エンジン

一部 13 の 22

取得と完了の間のギャップを埋める一連の分散型支払いアーキテクチャ。

Distributed payment engine architecture diagram

前のセクションで再試行アルゴリズムを確立しました。しかし、このアルゴリズムはジョブが どのワーカー によって実行されたかを想定していました。複数のワーカーが同じジョブ キューからプルしている場合、同じ支払いジョブを 2 人のワーカーが同時に処理できますか?

メッセージ ブローカーは、「可視性タイムアウト」または「ACK/NACK」を使用してこの問題を解決します。ただし、データベースにバックアップされたジョブ キュー (ジョブの状態がすでにデータベースに存在するため、多くの支払いシステムで推奨されるアプローチ) では、リース パターンを通じて同じ保証が提供されます。```text Worker A Worker B │ SELECT ... FOR UPDATE? │ │ ya da conditional UPDATE │ ▼ ▼ Job'ı kilitlemeye çalışır Job'ı kilitlemeye çalışır → sadece biri kazanır


## 最初に説明した概念```text
📦 Lease
Bir worker'ın bir işi belirli bir süre için 'sahiplendiğini' işaretleyen zaman damgalı kayıt.

📦 Conditional UPDATE
Sadece beklenen koşul (örn. status = Pending) doğruysa satırı güncelleyen atomik SQL işlemi.

📦 Lease süresi (Lease TTL)
Bir worker'ın işi ne kadar süreyle sahiplenebileceğinin üst sınırı; bu süre geçince iş tekrar alınabilir hale gelir.

📦 Stuck Watcher
Lease'i süresi dolmuş ama tamamlanmamış işleri periyodik olarak tarayıp yeniden kuyruğa alan arka plan süreci.
```リースはロックではありません。ロックを保持しているプロセスがクラッシュすると、ロックが無期限に残る可能性があります。リースには期間があるため、ワーカーがクラッシュしても、しばらくするとジョブは自動的に解放されます。

## リースを取得するアトミックな方法

ジョブを取得するための読み取りと更新 (read-then-write) は競合状態になる可能性があります。2 つのワーカーが同じ行を読み取り、両方とも「空」と表示され、両方とも更新しようとする可能性があります。正しい方法は、読み取りと条件を同じアトミック式に移動することです。```sql
UPDATE payment_jobs
SET status = 'Processing',
    lease_owner = :workerId,
    lease_until = now() + interval '60 seconds'
WHERE id = :jobId
  AND (status = 'Pending' OR (status = 'Processing' AND lease_until < now()))
RETURNING id;
```この UPDATE が行を返さない場合、ジョブはすでに別のワーカー上にあるか、まだ有効なリース下にあります。ワーカーは黙って次のジョブに進みます。行が返された場合、そのワーカーがジョブの唯一の所有者になります。リースの期限が切れるまで。

## リース期間をハートビートとともに延長する必要があるのはなぜですか?

固定のリース期間 (60 秒など) では、すべてのトランザクションに十分ではない可能性があります。長時間かかる可能性のある操作 (PSP 呼び出しが予想外に長くなるなど) は、リースが期限切れになる前にハートビートで延長する必要があります。```text
Worker işe başlar → lease_until = now + 60s
  ... iş sürüyor ...
Worker heartbeat gönderir → lease_until = now + 60s (yenilenir)
  ... iş tamamlanır ...
Worker status = Completed olarak işaretler
```ハートビートを送信できない (ダウンしている、ネットワークから切断されている) ワーカーはリースを更新できません。時間が経過すると、ジョブは再び利用可能になります。これは、クラッシュ シナリオが自動的に処理されることを保証するメカニズムです。

## Stuck watcher: リースが期限切れになったジョブを検出する人

リースの期限が切れてもジョブは自動的に「回復」されません。ワーカーは再度 `SELECT` する必要があります。そのため、定期的に実行されるウォッチャー プロセスは、リース期限は切れているがまだ `Processing` 状態にあるジョブをスキャンし、それらを取得可能にします (または新しいワーカーに直接割り当てます)。```text
Watcher (her 30 saniyede bir)
  SELECT id FROM payment_jobs
  WHERE status = 'Processing' AND lease_until < now()
  → bu işler 'stuck' olarak işaretlenir veya doğrudan Pending'e döndürülür
```Watcher がないと、クラッシュしたワーカーによって残されたジョブは永久に「処理中」であるように見える可能性があります。誰にも気付かれずに支払いが完了することはありません。

## ブローカーのナックだけでは不十分な理由

メッセージ ブローカーのワーカーが `Nack` メッセージを送信した場合 (または可視性タイムアウトの期限が切れた場合)、メッセージはキューに戻ります。これは DB のリースとよく似ていますが、2 つの重要な違いがあります。1 つは、ブローカー自身の可視性ウィンドウが、ビジネス ステータスの永続的な記録と同期していないことがよくあります (メッセージが失われたり、2 回配信される可能性があります)。 2 番目に、`Nack` は「このメッセージを残してください」と言うだけで、ジョブが *どの段階* で停止しているのか、ジョブが何回試行されたのかは永久にわかりません。データベースベースのリースにより、ジョブのステータスとトライアル履歴が同じトランザクション制限内に保持され、クエリ可能になります。

## 混同されやすい区別```text
❌ Lease = kilit (lock)
✓ Lease süresi dolan bir zaman sınırıdır; kilit process çökerse sonsuza kalabilir

❌ Read-then-write yeterlidir
✓ Read-then-write yarış durumuna açıktır; conditional UPDATE atomik olmalıdır

❌ Nack, lease'in yerini tamamen tutar
✓ Nack mesaj görünürlüğünü yönetir; lease iş durumunu ve deneme geçmişini kalıcı olarak tutar
```## DB-Backed リースとブローカー可視性タイムアウトの比較

|基準 |ブローカー可視性タイムアウト | DB 保証付きリース |
| --- | --- | --- |
|ステータスに関する疑問 |限定 | SQL を完全に使用 |
|トライアル履歴の永続化 |ブローカーによって異なります |簡単に同じ線上に |
|スタックしたジョブの検出 |間接的 |直接クエリを使用する場合 |

## リース設計時のチェックリスト

1. リースは単一のアトミックな `UPDATE ... WHERE` ステートメントですか、それとも read-then-write ですか?
2. リース期間は本当に予想される最長処理時間よりも長いのでしょうか?
3. 長時間かかる可能性のあるトランザクションに対してハートビートを使用したリース更新はありますか?
4. Stuck ウォッチャーは定期的に動作しますか? それともリースの期限が切れたジョブは永久に「処理中」のままですか?
5. `lease_owner` フィールドには、どのワーカー インスタンスがジョブを受信したかを診断するために保存されますか?
6. トライアルの数はリースごとに増加し、永続的に保持されますか?

## この記事で覚えておくべきこと

1. リースはロックではありません。これは期限付きの所有権の請求であり、自動的に終了します。
2. リースを取得するには、アトミックな条件付き UPDATE が必要です。 read-then-write は競合状態になりやすいです。
3. 長いトランザクションはハートビートでリースを更新する必要があります。そうしないと、リースが早期に終了する可能性があります。
4. スタック ウォッチャーは、クラッシュしたワーカーが残したジョブを回復する必須のバックグラウンド プロセスです。

> ジョブ キューの信頼性は良好な方向にありません。作業員が途中で墜落したときにテストされます。

次のセクションでは、このリース メカニズムの上に構築されるワーカーのタイプ、つまり調整ワーカーについて検討します。これは PSP では成功しますが、システム内でまだ保留中の支払いを改善します。

FAQ

よくある質問

リースとは何ですか?

ワーカーが特定の期間ジョブを「所有」していることを示すタイムスタンプ付きのレコード。

条件付き更新とは何ですか?

予期される条件 (ステータス = 保留中など) が true の場合にのみ行を更新するアトミック SQL 操作。

「リース=ロック(施錠)」でいいでしょうか?

リースは期限が切れる期限です。プロセスがクラッシュすると、ロックが永久に持続する可能性があります

このセクションでは何を修正しますか?

メッセージ ブローカーは、「可視性タイムアウト」または「ACK/NACK」を使用してこの問題を解決します。ただし、データベースにバックアップされたジョブ キュー (ジョブの状態がすでにデータベースに存在するため、多くの支払いシステムで推奨されるアプローチ) では、**リース** パターンを通じて同じ保証が提供されます。リースはロックではありません。これは期限付きの所有権の請求であり、自動的に終了します。前のセクションで再試行アルゴリズムを確立しました。しかし、このアルゴリズムはジョブが *どのワーカー* によって実行されたかを想定していました。複数のワーカーが同じジョブ キューからプルしている場合、同じ支払いジョブを 2 人のワーカーが同時に処理できますか?

学んだエンジニアリング原則

  • リースはロックではありません。時間制限があり、自動的に終了します。
  • リースを取得するには、読み取り後書き込みではなく、アトミックな条件付き UPDATE が必要です。
  • 立ち往生している監視者がいないと、クラッシュした労働者の仕事は永久に失われる可能性があります。

続きを読む

続きを読む

シリーズの次のシリーズ

エッセイ

支払調整員建設

スイーパーによるドリフトの改善方法: PSP が成功している間、ローカル登録が期限切れになる可能性があります。古い FinalizePending を回復する方法。

シリーズの次のシリーズ

同じシリーズ

エッセイ

有料だが注文なし: 改善

インシデント対応ガイド: 顧客は請求したが注文はされなかった。多目的のバスケットの乱雑さ。重複除去を慎重にクリーニングします。

Paylaş