
2026/06/10 22:45
Erlang/OTP をベースとした軽量タスクキューで、SQLite で後援し、過剰な設計を行わないもの。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
EZRA は Erlang/OTP および SQLite を基盤とした軽量なシングルノードタスクキューであり、外部の Redis サーバーまたは複雑なランタイム環境を必要とせず、Redis の機能を実装しています。本書は単一著者がメンテナンスしており、プラーリクエストは受け付けておらず、大規模なエンタープライズクラスターではなく、孤立したアプリケーション向けのシンプルさを重視しています。開発者は Mac (Apple Silicon)、Linux x86_64、Linux arm64、Docker 向けのプレビルトバイナリを使用して EZRA をデプロイすることもできますし、Mix を通じて Elixir アプリケーション内に直接埋め込むこともできます。自己完結型のバイナリのサイズは約 20MB です。EZRA は内部の RESP3 プロトコルと一意のワーカー名を利用することでアクティブなジョブを追跡し、複数のワーカーおよびプロデューサーをサポートし、動的なタスク分配によって高スループットワークロードに対応します。スループットはストレージメディアに依存し、SSD 上で約 15k–30k タスク/秒、NVMe 上では 40k–80k タスク/秒を達成でき、エンジン側のオーバーヘッドは呼び出しあたり约 1–5µs です。EZRA は "at-least-once" ロジックを採用することで信頼性の高いデータ配信を確保しており、タスクは決して無視されて削除されることはありません。「available」から「in-flight」、そして最大試行回数(デフォルト 3 回)を超えた場合は「dead」状態へ移行し、専用の
dead キューより読み出すことができます。再試行が尽きた失敗したタスクは「available」キューに戻され、反復して失敗した場合には dead キューに移動されます。EZRA は XADD、XREADGROUP、XACK、XDEL、XNACK という特定の Redis Streams コマンドをサポートしますが、GET、SET、pub/sub など他の標準的な Redis 操作に対してはエラーを返します。優先順位キュー、遅延タスク、クросスキュー間のトランザクション、ネイティブのファンアウトといった高度な機能はサポートされていません。パフォーマンス維持のためには SQLite の効率性を確保するためにレコメンデドペイロードサイズ制限(100KB)が推奨されており、大規模なデータセットについては外部に保管し、タスクペイロード内へ参照を保存する必要があります。すべてのデータは単一の SQLite ファイル (ezra.db) に格納されるため、マルチノード構成での高可用性には不適切ですが、rsync などのツールの使用によってバックアップやレプリケーションは容易に行えます。本文
EZRA:ゼロロス・リレーエージェントによるメッセージエクスチェンジ
プロジェクト概要
EZRA は、永続的なタスクキューです。以下の特性を備えています。
- プッシュ/プルモデル: 複数のサービス(プロデューサー)がタスクをプッシュし、複数のワーカーが処理します。
- 完全な可視化と追跡: 完了確認までのすべてのタスクが追跡可能です。「サイレント・ドロップ」(消え失せること)も**「ファイア・アンド・フォーゲット」**(放置すること)もありません。
- 簡潔なバックエンド: SQLite を使用し、Erlang/OTP ランタイム によって動作します。
- 言語非依存: どの言語のRedis クライアントでも接続でき(Redis サーバーは不要)、新規 SDK のインストールも不要です。
注意: このプロジェクトは単一著者によって維持されており、プルリクエストはお受けできません。バグや質問に関する Issue は大歓迎です。
目次
- クイックスタート
- 全体像とパフォーマンス
- なぜ EZRA が存在するのか?
- 仕組みとライフサイクル
- トラブル時の挙動
- マルチワーカー・プロデューサー
- トレードオフ(制約事項)
- 適切なユースケース
- インストールと起動
- 用語解説
クイックスタート
サーバーを起動するには、以下の Docker コマンドを実行します。
docker run -d --name ezra \ -p 42002:42002 \ -v ezra_data:/data \ ghcr.io/entgriff/ezra
この後、ローカルホスト(
localhost)のポート 42002 に対して以下の処理を行います。
プロデューサー:タスクをプッシュする (Python 例)
import redis # EZRA サーバーに接続(ポート 42002) r = redis.Redis(host="localhost", port=42002, decode_responses=True, protocol=3) # "emails" というキューへタスクをプッシュします。 # キューは事前に作成する必要はありません。最初のプッシュが自動的に作成します。 r.xadd("emails", {"payload": '{"to": "alice@example.com"}'})
ワーカー:タスクを取得して処理する (Python 例)
import redis r = redis.Redis(host="localhost", port=42002, decode_responses=True, protocol=3) while True: # Ezra から"emails"キューの次のタスクを要求します。 # "workers": ワイヤープロトコル上のコンシューマーグループ名です(EZRA では無視されます)。 # "worker-1": このワーカーの一意の識別子です。 # {"emails": ">"}: このキューから未配信の次のタスクをください。 # block=0: 接続を維持し、タスクが到着する瞬間に即時配信します。 results = r.xreadgroup("workers", "worker-1", {"emails": ">"}, count=1, block=0) if results: for task_id, fields in results["emails"][0]: send_email(fields["payload"]) # ここに処理のロジックを追加 # 成功を報告します。この手順は必須です。 # 報告しないと、可視化タイムアウト(デフォルト 30 秒)後に EZRA がタスクを再配信します。 r.xack("emails", "workers", task_id)
対応言語: Python, Node.js, Go, Ruby, Java などで動作可能です。Redis クライアントの設定先をポート
6379 から 42002 に変更するだけで利用可能です。
全体像とパフォーマンス
- アーキテクチャ: サービスやワーカーはどのマシン、どの言語でも動作します。ワーカーはアイドル時でも接続を維持し、タスクが到着した瞬間に即時処理を開始します。
- 永続性: すべてのデータはサーバー上の
に永続化されます。ezra.db
パフォーマンス指標
| 項目 | 値 |
|---|---|
| 接続ワーカーあたりのメモリ使用量 | ~2 KB (OS スレッドではない) |
| ベースラインメモリ使用量 | ~20 MB |
| 通常クラウド VM (SSD) スループット | ~15k – 30k タスク/秒 |
| NVMe ディスク スループット | ~40k – 80k タスク/秒 |
| バイナリサイズ | ~20 MB(自完) |
補足: スループットは SQLite の書き込み速度(ディスクに依存)によって制限されます。エンジン自体のオーバーヘッドは、1 コールあたり約 1–5 µs です。
なぜ EZRA が存在するのか?
多くのアプリでは、ユーザーへのレスポンスを即座に返したまま、バックグラウンドで重い処理(メール送信、PDF 生成など)を行う必要があります。EZRA は以下の課題を解決します。
- 失敗の管理: 失敗した場合の再試行ロジックを提供します。
- 永続性: サーバー再起動時でもデータが失われることはありません。
- 過剰設計への対抗: クラスターの調達や専用サーバーの設定などの重荷を負うことなく、大規模なリクエスト処理(100 万 RPS など)を必要としない実用的な規模をカバーします。
EZRA は以下の軽量な代替案を提供します。
- シンプル: 1 つのバイナリ、1 つの SQLite ファイル。
- 無依存: 事前設定やクラスター化不要。
- 監視容易: SQLite ブラウザでデータベースを開き、キューの内容を直接確認できます。
仕組みとライフサイクル
EZRA は Redis が使用するRESP3プロトコル(ワイヤーフォーマット)を採用しています。既存の Redis クライアントはそのまま使用可能です。
実装されているコマンド
Redis Streams に由来する主要な機能を実装しています:
: ナメッドキューへタスクをプッシュします。XADD
: 次のタスクを取得し、ワーカー識別子下で占有します(ポーリング不要)。XREADGROUP
: 処理完了の報告です。XACK
: 失敗時の削除(EZRA では再試行のために保持します)。XDEL
: SDK から直接コマンドを送信する際の使用。XNACK
制限: Redis の他機能(GET, SET, pub/sub など)はエラーを返します。EZRA は Redis のコピーではなく、タスクキューに特化したプロトコルです。
タスクライフサイクル図
stateDiagram-v2 [*] --> available : push available --> in_flight : pop in_flight --> available : crash, timeout, or nack with retries left in_flight --> done : ack in_flight --> dead : nack or timeout, no retries left dead --> [*] : readable via queue::dead
ライフサイクルの詳細
- プッシュ: 常に
ステートへ追加されます。available - ポップ: ワーカーが取得し、
ステートになります(タスクは削除されません)。in_flight - 完了 (ACK): ワーカーが処理を完了させ、EZRA に通知すると
となります。done - 失敗/タイムアウト: ワーカーが応答しない場合や
した場合:NACK- タイムアウト発生後(デフォルト 30 秒)、タスクは再び
に戻り、他のワーカーに再配信されます。available - 最大回数を尽くしても失敗(
)するとNACK
ステートへ移動します(デフォルト:3 回)。dead
- タイムアウト発生後(デフォルト 30 秒)、タスクは再び
ゼロロスの保証:
- サーバーが再起動した場合でも、起動時点で
のタスクはすべて即時in_flight
に戻されます。データは一切失われません。available - ワーカーが失敗したタスクも、同じ識別子で再取得可能か、または別のワーカーが拾うまで保持されます。
トラブル時の挙動
| シナリオ | 挙動 |
|---|---|
| ワーカーのクラッシュ | のタスクは可視化タイムアウト後、EZRA 側で回収され再配信されます(最大回数増加分)。 |
| EZRA サーバーのクラッシュ | TCP 切断を検知し、再起動時に即座に全ての タスクを にリセットします。「アット・least・オンス」保証のため、障害でもデータはロスしません。 |
| タスクの連続失敗 | を超えると ステートへ移動し、 キューに格納されます。ここで確認可能です。 |
| ワーカーの遅延 | タイムアウトまで処理が完了しない場合、他のワーカーが受け取ります。キューごとに最適な を設定してください(最悪ケースを基準)。 |
マルチワーカー・プロデューサー
EZRA は TCP ベースの API であり、登録や複雑な構成は不要です。以下の特性があります。
- 任意数のクライアント: 複数のプロデューサーが同時にプッシュできます。
- 公平な分配: 複数のワーカーがいる場合、タスクは自動的に最も軽いワーカーへ割り当てられます(ラウンドロビンではなく、即時処理可能な者へ)。
- 一意の識別子: ワーカーには一意の名前が必要(例:
,worker-1
)です。EZRA はこれを使ってどのプロセスがタスクを担当しているか追跡します。worker-2 - 非同期スケーリング: 追加ワーカーを起動するだけでスケールでき、構成変更は不要です。
注記: EZRA 単体では SQLite を使用するため、実質的にはシングルスノードです。データは EZRA が動作するマシン上のファイルとして存在します。
トレードオフ(制約事項)
| 項目 | 内容と代替案 |
|---|---|
| シングルスノード | データは 1 マシンに限定。そのマシンのダウンはキューの停止を意味します。対応策: SQLite ファイル自体の単純なバックアップ(rsync など)。 |
| アット・least・オンス配信 | タイムアウトで処理が完了していない場合、重複実行が発生する可能性があります。対応策: ワーカー側に重複処理(イデンプト)への耐性を設計する。 |
| 可視化タイムアウトの遅延 | クラッシュ直後の即時回収ではなく、設定秒数後に回収されます(将来的に改善予定)。 |
| タスクの蓄積 | 完了タスクは永久に残ります。対応策: オプションまたはプッシュ時の を設定する。 |
| ファノウト機能なし | 1 つのタスクは 1 つのワーカーへしか送られません。ブロードキャストには複数タスクのプッシュが必要。 |
| 優先度キューなし | , という別のキューを作成し、それぞれ消費させるワークアラウンドが必要です。 |
| 遅延配信(スケジュール)なし | 即時プッシュが必須です(将来的にサポート予定)。 |
| ペイロード制限 | 推奨は 100KB。大規模な BLOB は外部ストレージへ置いて参照のみを渡すのが望ましい。 |
| クロス・キュートランザクションなし | 複数のキューへのアトミックなプッシュはサポートされません。 |
適切なユースケース
✅ ゴッドフィット(推奨)
- バックグラウンドジョブ: メール配信、PDF 生成、画像リサイズ、Webhook 送信など。
- 信頼性の高い非同期処理: マイクロ秒の超低遅延よりも、確実な実行と再試行が求められる場合。
- マルチ言語チーム: Go や Python でバックグランド処理を、Elixir や Node.js でフロントエンドを行い、1 つのキューで連携したい場合。
- スタートアップ/初期製品: Kafka や RabbitMQ を導入するとリソースや複雑さ釣り合いが悪くなる段階での代替案。
❌ ゴッドフィットではない(不推奨)
- ハイアベイラビリティが必要な分散環境: ノード間でデータ整合性が厳しく、マルチマシンの完全冗長化が必須の場合。
- Pub/Sub パターン: 同じメッセージを複数のコンシューマーに同時に届ける場合。
- 超高速スループット: 単一ディスクの書き込み性能限界を超える必要がある場合(例:80k/秒を超えたい)。
- イベントソーシング: ストリーム自体を主たるデータモデルとして監査ログなどに利用する場合。
- 複雑なルティング: メッセージのフィルタリングやブロードキャストルーティングがブローカー側で必須の場合。
インストールと起動
環境確認
ランタイムは不要です。バイナリのみダウンロードして実行すれば動作します(サイズ ~20 MB)。
ダウンロードコマンド
以下のいずれかのコマンドを実行してください。
# macOS (Apple Silicon) curl -Lo ezra https://github.com/entgriff/ezra/releases/latest/download/ezra-macos_arm64 chmod +x ezra # Linux x86_64 curl -Lo ezra https://github.com/entgriff/ezra/releases/latest/download/ezra-linux_x86_64 chmod +x ezra # Linux arm64 curl -Lo ezra https://github.com/entgriff/ezra/releases/latest/download/ezra-linux_arm64 chmod +x ezra
サーバー起動方法
初回実行時にデータディレクトリに
ezra.db が作成されます。
./ezra --data-dir /var/ezra
環境変数による設定:
EZRA_DATA_DIR=/var/ezra EZRA_PORT=42002 ./ezra
停止するには Ctrl+C または SIGTERM を送ります。EZRA は処理中のタスクを完了させてからクリーンにシャットダウンします。詳細なオプションと Docker 設定は docs/usage.md にあります。
用語解説
- Push: キューに新しいタスクを追加します。
- Pop: 次のタスクを取得し、ワーカー側で作業に取り掛かる準備をします。削除されるのではなく、
というステータスで一時的に占有されます。in_flight - Ack (Acknowledge): 「処理完了」を EZRA に報告します。これでタスクは
状態になり、他の誰にも渡されません(ただし永続的に記録されます)。done - Nack (Negative Acknowledge): 「失敗しました」と報告します。EZRA はこのタスクを
に戻して、別のワーカーが再試行できるようにします(available
回まで)。max_attempts - In-Flight: ワーカーに渡されつつあり、完了確認されていない状態です。ワーカーから応答がない場合、可視化タイムアウト後に EZRA が回収します。
おすすめドキュメントとリソース
- GitHub Examples: 動作する Docker Compose デモ(Python, Node.js)。
- Usage Docs: 言語クライアント、完全な例、Docker、オプション参照、systemd セットアップ。
- Architecture Docs: ストレージスキーマ、モジュールマップ、ワイヤープロトコル。
- Elixir Client Docs: Elixir ライブラリモードでの利用例。