Wetaskの次回アップデートでは、耐久性とフェンシングを備えた外部ワーカー、アトミックなバッチ決済、ファーストパーティのGo、TypeScript、Pythonクライアントが導入されます。

Wetaskの当初の目標はシンプルでした。バックグラウンドジョブシステムの周りに通常集められるコンポーネントを、1つのGoベースのランタイムに統合することです。

それにより、別々のブローカー、リザルトバックエンド、スケジュールサービスをデプロイごとに用意することなく、タスクキュー、スケジューラー、キャッシュ、コンセンサス、運用API、監視機能を提供します。

次回のWetaskアップデートでは、その考え方を組み込みハンドラーの枠を超えて拡張します。アプリケーションは、Wetaskが耐久性のあるタスク状態の責任を負い続けながら、別のプロセス、コンテナ、言語、実行環境で独自のワーカーを実行できるようになります。

独自のワーカーを使用する

外部ワーカーは、実行をWetaskサーバー内で行うことができない、または行うべきでない場合に役立ちます。

  • 機械学習ジョブを実行するPythonワーカー
  • アプリケーションサービスを呼び出すTypeScriptワーカー
  • 高スループットのバックエンド作業を処理するGoワーカー
  • GPUまたはメモリ特化型のワーカープール
  • 独自のリリースサイクルを持つ独立してデプロイされたワーカー
  • キューおよびコントロールプレーンプロセスから分離されたワーカー

ファーストパーティのGo、TypeScript、PythonクライアントはワーカーAPIを提供し、HTTPおよびgRPCはカスタム統合用に利用できます。

ワーカーはキューとタスクタイプを指定してバッチを要求したり、長時間実行中の作業を更新したり、成功を報告したり、失敗を一時的・永続的・キャンセルと分類したりできます。クレデンシャルは特定のキューとタスクタイプに制限可能です。

フェンシングはもう1つのAckエンドポイントよりも重要

少なくとも1回の配信を保証するシステムでは、タスクが再配信される可能性を想定する必要があります。ワーカーは一時停止したり、接続を失ったり、リース期限を超過したりした後、別のワーカーが同じタスクを受け取った後も実行を継続することがあります。

Wetaskは、要求された試行ごとに1回限りのフェンシングトークンを発行します。完了は、タスク、要求、トークン、プリンシパル、試行、コーディネーターがすべて一致する場合にのみ受け付けられます。試行が期限切れになるか上書きされると、古いワーカーはそれを完了できません。

トークン自体はワーカーにのみ返され、WetaskはそのSHA-256ハッシュを保存します。

これにより外部副作用が厳密に1回限りになるわけではありません。カード課金後にクラッシュしてタスクを認識できなかったワーカーは、再試行を引き起こす可能性があります。ハンドラーはタスクIDと試行を使用してダウンストリーム操作を冪等にしなければなりません。

フェンシングが保証するのは、古いワーカーが現在の試行の状態を上書きできないことです。

耐久性と冪等性を備えた決済

成功および失敗の決済は、Wetaskがレシートを返す前に永続化されます。同じ確認を再試行すると、タスクを2回完了または再試行する代わりに元のレシートが返されます。

失敗処理はタスクを意識します。

  • transient は残りの再試行回数がある場合に別の試行をスケジュールします
  • permanent はタスクを終了し、デッドレターキューに記録します
  • cancelled は再試行せずに実行を終了します
  • リースの期限切れは、設定された再試行およびDLQポリシーを自動的に適用します

ワーカーは、完全なバッチをアトミックに確認または拒否できます。Wetaskはまずすべてのフェンスを検証します。1つのアイテムが古いまたは無効な場合、そのバッチ内の未決済アイテムはすべて変更されません。

バッチ決済は、バッチを1回のWAL同期で記録することで、耐久性のあるストレージオーバーヘッドも削減します。

ベンチマーク

完全なインプロセスライフサイクルをベンチマークしました。

  1. タスクの送信
  2. 外部ワーカーとしての要求
  3. 確認

このベンチマークはアプリケーションの作業を実行せず、HTTPやgRPCのネットワークオーバーヘッドも含まれません。タスクランタイム自体を測定します。

環境:

  • Apple M3
  • macOS darwin/arm64
  • 8ウェイ並列性を報告するGoベンチマークプロセス
  • バッチサイズ 1、8、32
  • シナリオごとに3サンプル
  • 3秒間のベンチマークウィンドウ

コマンド:

go test ./internal/task \
  -run '^$' \
  -bench '^BenchmarkWorkerClaimAck($|Parallel$)' \
  -benchmem \
  -benchtime=3s \
  -count=3

Enter fullscreen mode Exit fullscreen mode

インメモリライフサイクル

表は中央値の1秒あたりのタスク数を示します。

シナリオ 以前のベースライン 現在の結果 差異
バッチ 1 74,933 77,123 +2.9%
バッチ 8 84,434 86,393 +2.3%
バッチ 32 84,661 86,783 +2.5%
並列、単一タスク操作 64,429 63,443 -1.5%

バッチ 32 は、タスクあたり約 11.52 マイクロ秒 で完全なライフサイクルを完了します。

インメモリの増加は意図的に控えめです。償却すべきディスクの障壁はなく、すべてのタスクにID、要求トークン、状態遷移、キュー管理、結果保持、メトリクス、ライフサイクルイベントが必要です。並列の結果は実質的に横ばいで、隠さずに含めています。

耐久性のあるライフサイクル

耐久性モードには、受け入れられた送信、要求、決済のためのWAL同期が含まれます。

シナリオ 中央値スループット 以前の86.34 tasks/sベースラインとの相対値
バッチ 1 80.08 tasks/s 0.93x
バッチ 8 645.7 tasks/s 7.5x
バッチ 32 2,402 tasks/s 27.8x
並列、単一タスク操作 136.4 tasks/s 1.58x

バッチ 32 では、耐久性のある処理はタスクあたり約 416 マイクロ秒 かかります。

理論上のバッチング上限は32倍です。同じ耐久性のある障壁が32タスクで共有されるためです。測定された27.8倍の改善は、その上限の約87%に達しています。残りの時間はWALシリアライズ、タスク状態、メトリクス、ディスク同期自体に費やされます。

これらはWetaskのみの測定値であり、Wetaskがすべてのブローカーよりも高速であるという主張ではありません。公正な競合ベンチマークでは、同等のペイロード、トランスポート、バッチサイズ、永続化ポリシー、レプリケーション、確認セマンティクスを使用する必要があります。

Wetaskの比較

RabbitMQクォーラムキュー

RabbitMQクォーラムキューは、データ安全性とリーダーフェイルオーバーのために設計された成熟したRaftレプリケートキューです。パブリッシャー確認と手動コンシューマー確認は強力な本番基盤を提供します。

RabbitMQは、レプリケートされたキューの耐久性、運用履歴、プロトコルサポート、エコシステムサイズにおいてWetaskを上回っています。

Wetaskはよりタスク特化型の体験を目指します。結果、再試行、期限、キャンセル、デッドレター、スケジューリング、キャッシュ機能、ワーカーフェンシングが1つのランタイムとAPIを通じて利用できます。

参考:
RabbitMQ quorum queues

NATS JetStream

JetStreamはプロトコルレベルの比較として最も近いものです。プルおよびプッシュコンシューマー、明示的なAck、Nak、遅延Nak、Term、進行中の確認、AckWait、バックオフ、最大配信回数を提供します。

JetStreamは、クラスタ化されたストリームストレージ、パブ/サブ、レプリケーション、高スループットメッセージングにおいて大幅に成熟しています。

Wetaskの特徴は厳密な試行フェンシングです。JetStreamは、メッセージがすでに別のサブスクライバーに再配信された後に遅延確認が受け入れられる可能性があると文書化しています。Wetaskは上書きされた試行からの決済を拒否します。

参考:
NATS JetStream consumers

AWS SQSおよびGoogle Cloud Pub/Sub

マネージドキューはほとんどのブローカー運用を排除し、弾力性のあるマルチアベイラビリティゾーンインフラストラクチャを提供します。

SQSは可視性タイムアウトと受信ハンドルを使用します。標準キューは少なくとも1回の配信を保証し、コンシューマーは重複配信を許容する必要があります。

Google Cloud Pub/Subの厳密に1回のプルサブスクリプションは特に興味深い比較を提供します。再配信後は最新の確認IDのみが有効です。これはWetaskのフェンシングモデルに概念的に近いものです。

Wetaskは、セルフホスティング、ポータビリティ、ローカルデプロイメント、タスク特化型の状態を必要とするチーム向けです。クラウドキューは、マネージドサービスとプロバイダー運用の可用性がより重要な場合に適した選択肢です。

参考:

Apache Kafka

Kafkaは順序付けられた再生可能なイベントログです。イベントストリーミング、大規模な保持履歴、パーティション順序付け、多くの独立したコンシューマーグループに適しています。

従来のKafkaコンシューマーは、任意のジョブを独立して決済するのではなく、パーティションオフセットをコミットします。1つのレコードの再試行はパーティションの進行に影響を与える可能性があり、タスク結果、タスクごとのリース、デッドレター動作は通常、アプリケーションの規約または追加のトピックを必要とします。

Wetaskはバックグラウンドジョブにより直接的なモデルです。Kafkaは耐久性のあるイベントストリームにより強力なモデルです。

参考:
Kafka delivery semantics

RabbitMQを使用したCelery

CeleryはPythonアプリケーション向けの成熟した選択肢であり続けています。大規模なエコシステム、使い慣れたタスクデコレータ、再試行、Canvasワークフロー、長年の運用知識を備えています。

典型的な本番構成は、Celeryワーカー、RabbitMQなどのブローカー、リザルトバックエンド、スケジュール用のCelery Beatを組み合わせます。

Wetaskの提案は、より小さな運用サーフェスです。1つのGoランタイムがキュー、タスク状態、スケジューラー、キャッシュ、API、管理を提供し、外部ワーカーはPython、TypeScript、Goで記述できます。

参考:
Celery documentation

Temporal

Temporalはタスクキューよりも大きな問題を解決します。プロセス障害やインフラストラクチャ障害をまたいで再開できる耐久性のあるマルチステップワークフローのために設計されています。

Temporalは、長時間実行されるビジネスプロセス、サーガ、耐久性のあるタイマー、ワークフロー履歴に適した選択肢です。Wetaskは、通常の非同期ジョブ、スケジュールされた作業、アプリケーションレベルのキャッシュのために意図的にシンプルにしています。

参考:
Temporal documentation

Wetaskの位置づけ

Wetaskはすべてのメッセージングシステムを置き換えようとしているわけではありません。

ログが製品である場合はKafkaを選択します。耐久性のあるワークフローオーケストレーションが製品である場合はTemporalを選択します。運用をアウトソースすることが優先される場合はマネージドクラウドキューを選択します。成熟したレプリケートされたメッセージング基盤が統合アプリケーショランタイムよりも重要な場合はRabbitMQまたはJetStreamを選択します。

Wetaskは次のようなチーム向けです。

  • コンパクトなセルフホスト型タスクプラットフォーム
  • 耐久性のあるバックグラウンドジョブ
  • サーバープロセス外のワーカー
  • 厳密な古いワーカーの拒否
  • タスク結果、再試行、期限、DLQ管理
  • 別のサービススタックを必要としないスケジューリングおよびキャッシュ機能
  • HTTP、gRPC、Go、TypeScript、Python統合

提供状況

外部ワーカーは次回のWetaskアルファリリースで予定されています。初回リリースは、耐久性のあるシングルノード使用と制御された本番パイロットに焦点を当てます。マルチノード調整は存在しますが、タスクキューのWAL所有権は現在ノードごとです。レプリケートされたタスク所有権と永続的なノード損失からの回復は、今後の強化作業のままです。

この境界は意図的です。アルファの目標は、完全なワーカー体験を実際のプロジェクトに投入し、再現可能な証拠を公開し、本番フィードバックが次の段階を形作るようにすることです。

バックグラウンドジョブインフラストラクチャを評価している場合、最も有用なフィードバックは「どのベンチマーク数値が最大か?」ではありません。どの失敗セマンティクス、運用モデル、ワーカー体験が実際に実行する必要があるシステムに一致するかが重要です。