コンテンツにスキップ
use-cases / the-webhook-fan-out-you-didnt-have-to-build / hero
PIPE · HTTP FAN-OUT

構築する必要のなかった Webhook ファンアウト

Stripe が ?n=12 付きでパイプパスにイベント本文を POST します。12 のサブスクライバーが同じパスを ?n=12 付きで GET します。パイプは全員が接続するまでメッセージを保持し、その後 12 人全員に同時にストリームします。ブローカーも、コンシューマーグループも、DLQ も不要です。

パイプ docs を読む
use-cases / the-webhook-fan-out-you-didnt-have-to-build / mechanism

1 つの URL。12 のリーダー。接続ごとのバックプレッシャー。

Hoody Pipe はマルチレシーバーモードを持つ HTTP ストリーミングです。送信者と受信者の両方の URL に ?n=N を追加すると、パイプは N 個のリーダーが接続するまで待機し、その後本文を全員に同時にミラーリングします。遅いものは自分の接続を絞り、速いものは流れ続けます。

01STEP 01 · INTAKE

Stripe がパイプに POST

Stripe エンドポイントがイベント本文を /api/v1/pipe/billing?n=12 にそのまま転送します。パイプが 12 のレシーバーが集まるのを待つ間、接続はブロックされます。

02STEP 02 · ASSEMBLE

12 のサブスクライバーが接続

各サブスクライバーはコンテナ内の curl ループで、同じパスを ?n=12 付きで GET します。パイプは 12 番目のリーダーが接続するまで本文をメモリ内に保持します — オンディスクキューも、フラッシュするものもありません。

03STEP 03 · STREAM

ファンアウトして、忘れる

全員が接続したら、本文は 12 人全員に一度にストリームされます。遅いリーダーは自分のソケットにバックプレッシャーをかけ、他は進み続けます。最後のリーダーが切断すると、パイプはメッセージを忘れます。

stripe-webhook.sh
# Sender side — your Stripe webhook handler.
# Pipe holds the body until 12 readers are attached.
curl -X POST "https://api.hoody.com/api/v1/pipe/billing?n=12" \
  -H "Authorization: Bearer $HOODY_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary "@stripe-event.json"

# Receiver side — one of twelve subscriber containers.
# Same path, same n. Streams the event body when fan-out is ready.
curl -N "https://api.hoody.com/api/v1/pipe/billing?n=12" \
  -H "Authorization: Bearer $HOODY_TOKEN" | ./billing-handler.sh

# n must match on both ends — mismatch returns 400.
# Default n=1 is point-to-point. n=12 is fan-out for twelve.

同じ URL、同じクエリ文字列、両端共通。送信者の接続がブローカー、リーダーの接続がコンシューマーグループです。トピックがないため、バックプレッシャーはトピック単位ではなくソケット単位です — 1 つのインフライト本文と、それを引っ張る 12 のソケットがあるだけです。

use-cases / the-webhook-fan-out-you-didnt-have-to-build / fleet

サブスクライバーはプロセスのように出入りします

サブスクライバーを追加するのは、別のコンテナ内でもう 1 つの curl を実行するだけです。削除するのは curl を kill するだけです。ブローカー設定も、コンシューマーグループのリバランスも、ドレインする DLQ もありません — クラスタートポロジーはまさに今動いているプロセスそのものです。

subscribers diff · 過去 5 分リバランス不要
  • + ADDED
    WHKAUD
    新しいコンテナ 2 つを起動
  • − REMOVED
    MIX
    コンテナを kill、curl が終了
  • = STABLE
    BILANLLOGFRDSUCSLACRMCDNNOT
    依然として同じ n=12 パスにいる

パイプは誰がサブスクライブしているかを追跡しません。イベントごとに n 個の接続を待つだけです。両端で n をバンプすると、次のイベントは新しい人数を待ちます。メンバーシップが存在しないため、破損するメンバーシップ状態もありません。

use-cases / the-webhook-fan-out-you-didnt-have-to-build / advantages

もう運用しなくていいもの

下のすべての行は、ブローカーがプロトコルそのものになると消える作業のカテゴリーです。プロビジョニングするインフラも、学ぶ抽象化もなく — ただ curl と ?n だけ。

  • プロビジョニングするキューはない

    SQS も、Kafka クラスターも、RabbitMQ エクスチェンジもありません。パイプがキューであり、一度に 1 メッセージのために生きています。

  • コンシューマーグループの管理は不要

    オフセットも、コミットも、リバランスもありません。パイプは n のソケットが接続するまで本文を保持します — それが調整モデルのすべてです。

  • ドレインする DLQ は不要

    送信者が n のリーダーが揃わずに切断した場合、パイプはタイムアウト (5 分 TTL) し、Stripe がリトライします。世話するポイズンメッセージのバケットはありません。

  • 言語ごとの SDK は不要

    サブスクライバーは curl ループ。送信者は curl。プロトコルは HTTP。URL を叩けるものなら何でもクラスターに参加できます。

  • ソケットごとのバックプレッシャー

    遅いリーダーは自分の接続を絞ります。他の 11 はフルスピードでストリームし続けます。フリート全体にわたるヘッドオブラインブロッキングはありません。

  • デフォルトで忘れっぽい

    本文は最後のリーダーが切断した瞬間に消えます。設定する保持ポリシーも、スケジュールするログ圧縮も、書く GDPR の delete-on-request ジョブもありません。

use-cases / the-webhook-fan-out-you-didnt-have-to-build / punchline

12 のサブスクライバー、1 つの URL、ブローカーなし。

下にあるブローカーは、HTTP ファンアウトがクエリパラメーターになる前にインストールしていたものです。右側はそれらを置き換えるもの — 1 つのパス、1 つの n、そして自分たちがクラスターであることを知らない 12 の curl プロセスです。

もう実行しないもの
  • AWS Lambda + SQS/SNS
  • Apache Kafka
  • RabbitMQ exchanges
  • カスタム Webhook ルーター
代わりに呼び出すもの
POST /api/v1/pipe/billing?n=12

12 のレシーバーが同じパスを同じ n で GET します。パイプがブローカーであり — 最後のリーダーが切断した瞬間にメッセージを忘れます。

ファンアウトセクションを読む
use-cases / the-webhook-fan-out-you-didnt-have-to-build / replaces

これが置き換えるもの

Webhook を N 個のコンシューマーにブロードキャストするためにこれらのいずれかに手を伸ばすなら、パイプモデルは 2 回の curl 呼び出しで同じ仕事をします — 片側に送信者、もう片側に受信者。

  • AWS Lambda + SQS/SNSトピックごとのブローカー、サブスクライバーごとの IAM
  • Apache Kafkaクラスター、ZooKeeper/KRaft、コンシューマーグループ
  • RabbitMQ exchangesバインディング、vhosts、環境ごとの管理 UI
  • カスタム Webhook ルーターメンテするサービス、発明するリトライ
  • Pub/Sub サービスメッセージごとの課金、GCP/AWS ロックイン
  • HTTP ファンアウトプラグインスタックごとのゲートウェイプラグイン、バックプレッシャーなし
use-cases / the-webhook-fan-out-you-didnt-have-to-build / cta

1 スプリントかけて書いていたファンアウトが、今やクエリパラメーターです。?n=12 を追加して、出荷してください。

パイプ docs を読む
use-cases / the-webhook-fan-out-you-didnt-have-to-build / related

他のユースケースを読む