コンテンツにスキップ
タイプアンロック
ステージフリート
難易度中程度
ジョブストリームを共有
対象開発チーム
対象ソロ起業家
サービスCron
サービスExec
サービスパイプ
Hoody の利点HTTP ネイティブ
Hoody の利点コンテナ経済学
タイプアンロック
ステージフリート
難易度中程度
ジョブストリームを共有
対象開発チーム
対象ソロ起業家
サービスCron
サービスExec
サービスパイプ
Hoody の利点HTTP ネイティブ
Hoody の利点コンテナ経済学
タイプアンロック
ステージフリート
難易度中程度
ジョブストリームを共有
対象開発チーム
対象ソロ起業家
サービスCron
サービスExec
サービスパイプ
Hoody の利点HTTP ネイティブ
Hoody の利点コンテナ経済学
タイプアンロック
ステージフリート
難易度中程度
ジョブストリームを共有
対象開発チーム
対象ソロ起業家
サービスCron
サービスExec
サービスパイプ
Hoody の利点HTTP ネイティブ
Hoody の利点コンテナ経済学
CRON · EXEC · PIPE ファンアウト

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

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

cron ドキュメントを読む

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] 転送が完了しました。

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

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 のシートでもありません。

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

受信者は同じ 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 を持ちます。

月曜 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 に渡します。遅い側はバックプレッシャー、速い側はミリ秒で完了。実行全体は数秒で終わります。

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

これまでのやり方基盤がやるやり方
変更前 · 直列の SMTP ワーカーfor sub in 200: smtp.send(render(sub))11 分 · クラッシュ時に半分配信 · 受信者ごとの請求
変更後 · ひとつの Pipe パスrender | curl -T - pipe/digest?n=2004 秒 · 冪等 · 起動 1 回ぶんの請求
pipe 仕様を読む

これが置き換えるもの

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

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

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

cron ガイドを読む

他のユースケースを読む