【ITニュース解説】Batch Calls for Catalogue Thumbnails: Rate Limits and Partial Failure
2026年10月13日に「Dev.to」が公開したITニュース「Batch Calls for Catalogue Thumbnails: Rate Limits and Partial Failure」について初心者にもわかりやすく解説しています。
ITニュース概要
大量の画像処理では、全体を一括でなく、個々の画像をキューに入れ、並行処理数を制限して安全に進めるべきだ。失敗した処理だけを再試行し、各画像の状況を明確に追跡することで、システム負荷を抑え、回復力を高めることができる。
ITニュース解説
商品カタログのサムネイル画像を大量に生成するような場面では、多くの画像を効率的かつ確実に処理するためのシステム設計が求められる。このとき、「バッチ処理」(複数の画像をまとめて処理する)と「個別処理」(画像を一つずつ処理する)のどちらを選ぶか、また、その処理をどのように構築するかが重要な検討事項となる。特に、サーバーからの処理要求の制限(レート制限)や、一部の処理だけがうまくいかない状況(部分的な失敗)に対応しながら、システム全体の信頼性と回復性を高めることが、最も大切な課題である。
多くのシステム設計者が陥りがちな誤解は、処理の数を単純な「HTTPリクエストの数」で数えることだ。例えば、1000個の画像を処理するのに、1000回のリクエストを送信するのと、1回のバッチリクエストで1000個の画像をまとめて送信するのとでは、リクエストの数は大きく異なる。しかし、実際にシステムが裏側で行う処理、例えば画像のデコード(読み込み)、リサイズ(サイズ変更)、エンコード(保存形式への変換)、ストレージへの保存、キャッシュの更新といった作業は、どちらの方法を選んでも発生する。つまり、リクエストの「封筒」の数だけを数えても、実際の処理の負荷や複雑さを正確に表すことはできない。封筒の数は実態を隠してしまうのだ。
そこで推奨されるのは、各画像を個別の「ジョブ」として扱い、それらを耐久性のある「キュー」(待ち行列)に投入する設計である。キューは、システムが一時的に停止したり、処理能力を超えてリクエストが押し寄せたりしても、ジョブの情報が失われないようにデータを保持する。そして、このキューから並行処理の数を制限された「ワーカー」(実際の処理を行うプログラム)がジョブを取り出して処理を実行する。これにより、一つの大量インポート作業が、一時的なアクセス集中(トラフィックスパイク)を引き起こしたり、ある一つの問題のある画像が原因で全体が曖昧な失敗に終わったりするリスクを避けられる。
具体的な処理のパイプラインは次のような形をとる。まず、商品データがシステムに入力され、関連する画像が検証される。次に、各画像に対して一意の識別子(ジョブID)が付与された処理指示(ジョブ)が生成され、キューに送られる。ワーカーはキューからジョブを取り出し、必要なサイズのサムネイル画像を複数生成する。画像が正常に生成されると、ストレージに保存され、元の商品データは「準備完了」状態となる。もし処理中にエラーが発生した場合でも、そのジョブは独立して「再試行」または「拒否」の状態に移行し、全体の処理には影響を与えない。例えば、1000個の画像処理のうち913個が準備完了、74個が処理中、13個が失敗といった具合に、個々の状態を正確に把握できるようになる。これにより、最も古い未完了のジョブがどれくらいの期間停滞しているか、そしてそれを再試行しても重複する画像が生成されないか、といった重要な問いに答えられるようになる。
このシステムの設計においては「べき等性」が非常に重要だ。べき等性とは、同じ操作を何度行ってもシステムの状態が同じになる性質を指す。例えば、サムネイル生成ジョブが何らかの理由で再実行されたとしても、同じジョブIDとバリアント(サムネイルの種類)が指定されていれば、すでに同じ画像がストレージに存在する場合は新しいオブジェクトを無駄に作成しないようにする。そのためには、ジョブの識別子と、生成されるファイルの保存先キーが常に一意になるように設計する必要がある。ストレージへの保存も、重複作成を防ぐための原子的な(途中で中断されない)コミットルールを持つべきだ。ストレージはシステムの状態そのものであり、その管理は慎重に行う必要がある。また、画像ファイルの内容を直接検証し、サポートするフォーマットのみを受け入れるようにすることも、無駄な処理を防ぐ上で重要となる。
システムの健全性を監視する際も、単なるリクエストレート(単位時間あたりのリクエスト数)だけでなく、より詳細なメトリクスを追跡する必要がある。例えば、受け付けられたジョブ、処理が開始されたジョブ、完了したジョブ、再試行待ちのジョブ、拒否されたジョブの数などを、画像の種類やインポート元ごとに把握する。さらに、キューの中で最も長く待っているジョブの経過時間、キューの深さ(待機中のジョブの数)、現在アクティブなワーカーの数、再試行回数、書き込まれたデータ量、生成されたサムネイルの数といった指標も重要だ。これにより、「ユーザーが感じる遅延」という本質的な問題にアラートを出すことができる。例えば、最も古いジョブの待機時間が目標とする完了時間を超えた場合や、最終的に処理が拒否される割合が持続的に上昇した場合などだ。レート制限は問題の「原因」を示すシグナルかもしれないが、キューの年齢こそが、最終的に編集者や利用者が感じる「症状」なのである。
では、バッチ処理が本当に優れているのはどのような状況だろうか。バッチ処理がその価値を発揮するのは、複数のアイテムをまとめて扱うことでシステムの調整コストを削減しつつ、各アイテムの処理結果を個別に識別できる場合だ。つまり、バッチ処理の応答やステータス情報が、バッチ内のどのアイテムが受け付けられ、拒否され、あるいは保留中であるかを明確に区別し、さらに個別のアイテムごとに再試行が可能でなければならない。これらの特性が欠けていると、バッチの一部が失敗した場合に、どのアイテムが問題なのかを特定するのが困難になり、曖昧な状態が生まれてしまう。
一方、個別処理を並行処理の制限されたキューを介して行う方法は、アイテムのサイズや処理時間が大きく異なる場合に、より理にかなっている。一つの処理に時間がかかる画像があったとしても、それが全体のバッチ処理を停滞させることはなく、その画像に対してだけ処理の待機時間(バックオフ)を適用できる。この方法のデメリットは、リクエストごとの通信オーバーヘッドがやや増えることや、クライアント側で各アイテムの状態管理がより多く必要になる点だが、そのトレードオフは、より高い回復性と明確な状態管理を可能にする。
最後に、JavaScriptのPromise.allSettledのような機能は、小規模で閉じられたセットの処理結果を集約するのに役立つが、これは並行処理の数を制御するポリシーではない。また、プログラムのメモリは永続的なキューではなく、システムが再起動すれば再試行計画も消滅する。永続性、バックオフ、そして過剰な負荷を避けるための受付制御といった機能は提供されない。実用的なルールとして、未完了の作業を正確に列挙できるインターフェースを選択し、ワーカーが作業を取得する速度を制限し、すべての状態変化を記録し、同じ処理を繰り返しても常に同じ最終結果(サムネイル画像)が生成されるように設計することが重要である。これにより、画像変換の能力を保護しつつ、ストレージ容量やキャッシュの肥大化を合理的に管理できるようになる。