コンテンツにスキップ
use-cases / daily-digest-fan-out / hero
CRON · EXEC · PIPE FAN-OUT

200 件の受信箱へファンアウトするスケジュール配信

毎週月曜の 9 時、1 つの cron エントリが 1 つのコンテナを起こします。スクリプトはダイジェストを 1 度だけレンダリングし、?n=200 のパイプ URL に書き込みます。200 個の curl ループ、購読者ごとに 1 個が、同じバイトを並列で取り出して SMTP に渡します。ファンアウトは基盤側に住み、あなたのコードには住みません。

cron ドキュメントを読む
use-cases / daily-digest-fan-out / mechanism

cron、exec、pipe — 3 つの呼び出しで完了

Hoody Cron API が 5 フィールドの crontab 行を管理エントリに落とします。その行は exec スクリプトを動かし、ダイジェストを 1 度レンダリングし、n=200 のパイプパスに押し込みます。200 個の購読者ループが同じパスを並列で読み出します。サーバーは何も保持せず、遅い読み手が他をブロックすることもありません。

cron · entries
POST · schedule
# 月曜 09:00 — 管理 cron エントリPOST /users/root/entries# /users/root/entries に送るボディ{schedule: "0 9 * * 1",command: "bash /scripts/digest.sh"}
exec · digest.sh
PUT · 送信側
# 1 度レンダリング — markdown → HTMLdigest=$(render-digest.py)# バイトをパイプパスに押し込むecho "$digest" | curl -T - https://pipe.hoody.com/api/v1/pipe/digest-monday?n=200# パイプは 200 受信者が接続するまで保留し、その後ストリーミング
pipe · subscribers
GET · 受信側
# 軽量な curl ループ 200 個、購読者ごとに 1 つwhile read addr; docurl -s https://pipe.hoody.com/api/v1/pipe/digest-monday?n=200 \| smtp-send "$addr" &done < subscribers.txt# 200 個すべて並列でストリーミング — 遅い読み手はバックプレッシャーで処理[INFO] Transfer complete.

cron は複雑になっていません。ファンアウトが基盤側に移っただけです。パイプは何も保持せず、スクリプトは 1 度レンダリングし、ループは末端の SMTP だけです。キューもリトライテーブルもキャンペーンツールのシートもありません。

use-cases / daily-digest-fan-out / powers

HTTP ファンアウトが SMTP ファンアウトに勝る理由

素朴な設計は SMTP 送信を 200 回直列でループし、11 分かかり、途中でクラッシュすると二重配信を起こします。パイプの形は、並列性、冪等性、より小さなコンテナを無料で手に入れさせてくれます。

PARALLELISM

200 個の受信者、1 度のレンダリング

ダイジェストは厳密に 1 度だけ作られます。200 個の curl ループが同じバイトを同時に読み出します。4 秒の実行が、11 分の直列ループを置き換えます。パイプは遅い読み手にバックプレッシャーをかけますが、他はブロックしません。

IDEMPOTENCY

途中でクラッシュしても掃除不要

参照すべきキャンペーン状態テーブルはありません。200 全員が接続する前に実行が落ちても、パイプの TTL が未完了の半分を排除し、次の cron ティックが再レンダリングします。二重配信なし、半分送られたバッチを突き合わせる必要もなしです。

ECONOMICS

1 つのコンテナ、23 時間眠ったまま

スクリプトは週に 1 度起き、4 秒走り、コンテナはアイドルへ戻ります。あなたが払うのはその 4 秒ぶんだけです。常時稼働のキャンペーンサービスでも、受信者単位の SES 請求でも、Mailchimp のシートでもありません。

use-cases / daily-digest-fan-out / timing

ファンアウトをワイヤーが担うと何が変わるか

受信者は同じ 200 人、ダイジェスト本文も同じです。動くのは実行の形だけ。SMTP の直列で数分から、HTTP の並列で数秒へ。

  1. RUN DURATION4.2s

    cron ティックから最後の配信までの実時間です。パイプは 200 受信者すべてに並列でストリーミングし、ボトルネックはループではなく、最も遅い購読者の SMTP になります。

  2. RENDER COUNT

    ダイジェスト本文は 1 度だけ計算されます。パイプは同じバイトをすべての受信者に転送します。受信者ごとのテンプレート再レンダリングも、受信者ごとの請求も、受信者ごとのキャッシュも不要です。

  3. RECEIVERS PER PATH200

    Hoody Pipe API は n を 256 までに制限します。週次ダイジェストの 200 はその上限の十分内に収まり、遅い読み手はバックプレッシャーをかけても他をブロックしません。

Hoody Pipe API の仕様: 受信者数 1〜256、接続待ちのパイプ TTL は 5 分、サーバー全体で同時転送 1000 件まで。cron エントリそのものは /users/root/entries の 1 行で、schedule、command、オプションの expires_at を持ちます。

use-cases / daily-digest-fan-out / steps

月曜 9 時、実行はこう展開します

4 つの瞬間。それぞれは手作業でも投げる単発の HTTP 呼び出しです。cron は目覚まし時計、exec はレンダラー、pipe はワイヤー、ループはエージェントが書く唯一の部分です。

    01
    09:00:00

    cron ティック

    /users/root/entries の管理エントリが発火します。スケジュール: 0 9 * * 1。コマンド: bash /scripts/digest.sh。crontab そのものは 1 つの JSON レコードです。Airflow DAG でもワークフローサービスでもありません。

    02
    09:00:00

    1 度だけレンダリング

    exec スクリプトが今週のデータを取得し、markdown をレンダリングし、HTML に変換し、本文を stdout へ書き出します。1 度のレンダリング、1 つのペイロード。受信者ごとのメールマージループはありません。

    03
    09:00:00

    PUT pipe ?n=200

    スクリプトは stdout を curl -T - に流し込み、pipe/digest-monday?n=200 へ向けます。パイプは 200 受信者が接続するまでアップロードを保留し、その後本文を全員へ並列でストリーミングします。

    04
    09:00:04

    200 件の SMTP

    200 個のループが同じパスを curl し、本文を購読者の SMTP に渡します。遅い側はバックプレッシャー、速い側はミリ秒で完了。実行全体は数秒で終わります。

use-cases / daily-digest-fan-out / punchline

cron エントリ 1 つ、コンテナ 1 つ、受信者 200 人。

これまでのやり方基盤がやるやり方
BEFORE · SERIAL SMTP WORKERfor sub in 200: smtp.send(render(sub))11 分 · クラッシュ時に半分配信 · 受信者ごとの請求
AFTER · ONE PIPE PATHrender | curl -T - pipe/digest?n=2004 秒 · 冪等 · 起動 1 回ぶんの請求
pipe 仕様を読む
use-cases / daily-digest-fan-out / replaces

これが置き換えるもの

リストに同じメールを送りたくなったときに手を伸ばす定番ツール群です。それぞれ、結局のところ 1 度のレンダリングと HTTP のファンアウトループに対して、サービス階層料金を請求してきます。

  • SendGrid のスケジュールキャンペーンあなたのスクリプトが既に作っているペイロードに対する、メール単位の課金
  • Mailchimp の日次ダイジェスト週 1 回の送信のために、キャンペーン UI 一式とオーディエンスのシート
  • カスタムメールマージ cron ジョブ直列ループとリトライテーブル、半分送信されたバッチのポストモーテム
  • AWS SES + Lambda のスケジュールバッチキュー、ワーカー、IAM ロール、面倒を見る CloudWatch アラーム
  • Resend のバッチ API 呼び出し送信間で変わらない本文に対する、受信者単位の API 課金
  • Customer.io のドリップキャンペーンテキストファイルで持っているリストのために、セグメンテーションエンジン
use-cases / daily-digest-fan-out / cta

月曜 9 時はかつて、SMTP をすり潰すワーカーを意味していました。今は cron ティック 1 回、コンテナ 1 つ、そして残りをこなすパイプを意味します。

cron ガイドを読む
use-cases / daily-digest-fan-out / related

他のユースケースを読む