大規模システムと長時間実行バックグラウンドジョブのための構築。
クレジット:Ilias Chebbi on Unsplash数ヶ月前、私はメディア(オーディオ)ストリーミングのためのインフラ構築が必要な役割を引き受けました。しかし、ストリーム可能なチャンクとしてオーディオを提供するだけでなく、長時間実行のメディア処理ジョブと、文字起こし、トランスコーディング、埋め込み、連続的なメディア更新に対応する広範なRAGパイプラインがありました。プロダクション思考でMVPを構築することで、シームレスなシステムを実現するまで何度も繰り返しました。私たちのアプローチは、機能と優先順位の基盤となるスタックを統合するものでした。
構築の過程で、各イテレーションは即時かつしばしば「包括的な」ニーズへの対応として現れました。最初の懸念はジョブのキューイングでしたが、Redisで十分に対応できました。単に発行して忘れるだけでした。NEST JSフレームワークのBull MQは、リトライ、バックログ、デッドレターキューに対するさらに良いコントロールを提供しました。ローカルで、そして本番環境でいくつかのペイロードを使用して、メディアフローを正しく設定しました。すぐに観測性の重みに悩まされるようになりました:
ログ → ジョブの記録(リクエスト、レスポンス、エラー)。
メトリクス → これらのジョブがどれだけ/どれくらいの頻度で実行、失敗、完了するかなど。
トレース → ジョブがサービス間で取ったパス(フローパス内で呼び出された関数/メソッド)。
APIを設計し、それらをプラグインするカスタムダッシュボードを構築することで、これらの一部を解決できますが、スケーラビリティの問題が十分になります。実際、私たちはAPIを設計しました。
複雑で長時間実行するバックエンドワークフローを管理する課題、失敗が回復可能でなければならず、状態が耐久性を持つ必要がある場合、Inngestは私たちのアーキテクチャ的救済となりました。それは根本的に私たちのアプローチを再構成しました:各長時間実行バックグラウンドジョブは、特定のイベントによってトリガーされるバックグラウンド関数になります。
例えば、Transcription.request イベントはTranscribeAudio 関数をトリガーします。この関数には、fetch_audio_metadata、deepgram_transcribe、parse_save_trasncription、notify_userなどのステップ実行が含まれる場合があります。
コアの耐久性プリミティブはステップ実行です。バックグラウンド関数は内部的にこれらのステップ実行に分解され、それぞれが最小限の原子的なロジックブロックを含んでいます。
Inngest関数の抽象:
import { inngest } from 'inngest-client';
export const createMyFunction = (dependencies) => {
return inngest.createFunction(
{
id: 'my-function',
name: 'My Example Function',
retries: 3, // 失敗時に実行全体を再試行
concurrency: { limit: 5 },
onFailure: async ({ event, error, step }) => {
// ここでエラーを処理
await step.run('handle-error', async () => {
console.error('Error processing event:', error);
});
},
},
{ event: 'my/event.triggered' },
async ({ event, step }) => {
const { payload } = event.data;
// ステップ1:最初のステップを定義
const step1Result = await step.run('step-1', async () => {
// ステップ1のロジック
return `Processed ${payload}`;
});
// ステップ2:2番目のステップを定義
const step2Result = await step.run('step-2', async () => {
// ステップ2のロジック
return step1Result + ' -> step 2';
});
// ステップN:必要に応じて続ける
await step.run('final-step', async () => {
// 最終化ロジック
console.log('Finished processing:', step2Result);
});
return { success: true };
},
);
};
Inngestのイベント駆動モデルは、すべてのワークフロー実行に対する詳細な洞察を提供します:
純粋なイベント処理に依存する際の注意点は、Inngestが関数実行を効率的にキューに入れる一方で、イベント自体は従来のメッセージングブローカーの意味で内部的にキューに入れられないことです。明示的なイベントキューがないことは、取り込みエンドポイントが過負荷になった場合の潜在的な競合状態やドロップされたイベントにより、高トラフィックシナリオで問題になる可能性があります。
これに対処し、厳格なイベント耐久性を強制するために、バッファとして専用のキューイングシステムを実装しました。
既存のAWSインフラストラクチャを考慮して、AWS Simple Queue System (SQS)が選択されたシステムでした(ただし、堅牢なキューイングシステムであれば何でも可能です)。私たちは2つのキューシステムを設計しました:メインキューとデッドレターキュー (DLQ)です。
私たちは、メインキューから直接メッセージを消費するように特別に構成されたElastic Beanstalk(EB)ワーカー環境を確立しました。メインキュー内のメッセージがEBワーカーによって一定回数処理に失敗すると、メインキューは自動的に失敗したメッセージを専用のDLQに移動します。これにより、イベントがトリガーに失敗したり、Inngestによって取得されなかったりしても、永続的に失われることがないようにします。このワーカー環境は標準のEBウェブサーバー環境とは異なり、その唯一の責任はメッセージの消費と処理(この場合、消費されたメッセージをInngest APIエンドポイントに転送すること)です。
エンタープライズスケールのインフラストラクチャを構築する際の控えめでありながら関連性の高い部分は、それがリソースを消費し、長時間実行されることです。マイクロサービスアーキテクチャはサービスごとのスケーラビリティを提供します。ストレージ、RAM、リソースのタイムアウトが重要になります。例えば、AWSインスタンスタイプの仕様は、t3.microからt3.smallへと急速に移行し、現在はt3.mediumに固定されています。長時間実行されるCPU集約型のバックグラウンドジョブでは、小さなインスタンスによる水平スケーリングは失敗します。なぜなら、ボトルネックは単一のジョブを処理するのにかかる時間であり、キューに入る新しいジョブの量ではないからです。
トランスコーディングや埋め込みなどのジョブや関数は、通常CPU制約とメモリ制約があります。CPU制約は持続的で強力なCPU使用率が必要なため、メモリ制約は大きなモデルをロードしたり、大きなファイルやペイロードを効率的に処理したりするために大量のRAMが必要なことが多いためです。
最終的に、SQSの耐久性とEBワーカー環境の制御された実行をInngest APIの直接上流に配置するこの拡張アーキテクチャは、不可欠なレジリエンスを提供しました。私たちは厳格なイベント所有権を達成し、トラフィックスパイク中の競合状態を排除し、不揮発性のデッドレターメカニズムを獲得しました。ワークフローオーケストレーションとデバッグ機能のためにInngestを活用しながら、最大のメッセージスループットと耐久性のためにAWSプリミティブに依存しました。結果として得られたシステムはスケーラブルであるだけでなく、高度に監査可能であり、複雑で長時間実行するバックエンドジョブを安全で観測可能で障害に強いマイクロステップに変換することに成功しました。
Building Spotify for Sermons.はもともとMediumのCoinmonksで公開され、人々はこの記事をハイライトして返信することで会話を続けています。


