Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】What I learned from building a Image Search System

2026年10月07日に「Dev.to」が公開したITニュース「What I learned from building a Image Search System」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

画像検索システムの構築を通じ、データ不整合や処理遅延など多くの設計課題が浮上した。S3とベクトルデータベース間のデータ一貫性維持の難しさや、スケーラビリティ問題が指摘され、Transactional Outboxパターンなどによる解決策が検討されている。

ITニュース解説

この解説では、画像検索システムを構築した経験から学んだこと、特にシステム設計と運用における課題とその解決策について詳しく説明する。このプロジェクトの目的は、アップロードされた画像やテキストの検索クエリに基づいて、データベース内の類似画像を検索するシステム、いわばGoogle Photosのようなものを作ることだった。開発当初は単純な作業に思えたが、実際にシステムを構築する過程で、表面上は些細に見えるが実際には非常に難しい問題に数多く直面した。

まず、システムの基盤となる「セットアップとインフラ」について説明する。このシステムでは、クラウドサービスであるAWSの機能をローカル環境で模擬的に利用できる「LocalStack」というツールを使った。これは、特にオブジェクトストレージサービスであるS3の機能を試すために活用された。また、画像の「特徴」(後述する「埋め込み」)を効率的に検索するための「ベクトルデータベース」として「Qdrant」を使用した。これらの主要なコンポーネントは、「Docker」と呼ばれる技術を用いて、それぞれ独立した環境で動いている。Dockerを使うことで、異なるアプリケーションやサービスを互いに干渉することなく実行できる。このプロトタイプでは、全てのサービスを一つのマシン上で動かし、ネットワークAPIを通じて相互に通信させた。具体的には、Pythonのライブラリである「Boto3」を使ってLocalStackのS3互換APIと通信し、QdrantのPythonクライアントを使ってQdrantのAPIと通信している。この段階では、本番環境のような複雑なネットワーク構成を再現するよりも、システムの主要な機能をローカルで試すことに重点が置かれた。

次に、「入力パス」、つまり画像がシステムにアップロードされる際の処理の流れと、そこで見つかった問題点について解説する。画像アップロードのAPIは、画像の保存、特徴の抽出(埋め込み)、そしてデータベースへの登録(インデックス作成)という一連のステップを一つのまとまりとして実行する。アップロード処理は、まず画像をS3に保存し、次にその画像から「埋め込み」と呼ばれる数値データを生成するサービスを呼び出し、最後にその埋め込みと画像の情報をQdrantに登録するサービスを呼び出す、というように直線的に進む。

しかし、この直線的な処理にはいくつかの深刻な問題があることが明らかになった。一つ目は「一貫性の問題」である。もし途中のステップでエラーが発生した場合、S3には画像が保存されたがQdrantにはその情報が登録されない、といったデータの不整合が生じる可能性がある。全てのステップが滞りなく完了する保証はなく、また全てのサービスが常に利用可能であるとも限らないからだ。二つ目は「スケーラビリティの問題」である。アップロードのリクエストが、複数の処理が全て完了するまで応答を返さないため、もしどれか一つの処理が遅くなったり停止したりすると、リクエストが長時間開いたままになり、システムのリソースを消費し続ける。これにより、システム全体の負荷が増加し、処理能力が低下する可能性がある。三つ目は「冪等性の問題」である。処理が途中でタイムアウトして、クライアントが同じアップロード操作を再試行した場合、システムがどのように振る舞うべきか(例えば、重複して登録されるのか、元の情報を上書きするのか)が明確に定義されていない。これは、同じデータが複数回処理されることによって予期せぬ結果を引き起こす可能性があるということである。

また、「削除処理」においても同様の問題がある。S3から画像を削除した場合、Qdrantから対応するベクトル情報も確実に削除される必要があるが、その保証がない。フロントエンドがS3とQdrant両方の削除操作を調整すべきではなく、バックエンドがこの変更をシステム全体に伝播させる仕組みを持つべきだ。S3とQdrantは独立したシステムであるため、これらの操作間で共有される「トランザクション」が存在しない。そのため、S3からは画像が削除されたが、Qdrantの操作は失敗し、元のオブジェクトが存在しないにもかかわらず、そのベクトル情報だけがデータベースに残ってしまう可能性がある。これは「デュアルライト問題」と呼ばれ、関連するデータを複数の異なるシステムに書き込む際に、それらの整合性を保つのが難しいという課題を指す。

この一貫性の問題に対処するために、いくつかのアプローチが検討された。 まず「Two-Phase Commit (2PC)」だが、S3やQdrantが分散トランザクションをサポートしていないため、このシステムでは実用的ではない。 次に「SAGAパターン」がある。これは、一連の操作をワークフローとしてモデル化し、途中で失敗した場合には「補償処理」と呼ばれる逆の操作を実行して整合性を保つ方法だ。これによりサービス間の結合度を低減できる可能性があるが、最終的な整合性であり、イベントが確実に公開されるかは別途考慮する必要がある。 最も有望視されたのは「Transactional Outboxパターン」である。このアプローチでは、外部システムを直接更新しようとするのではなく、まずデータベースのトランザクション内で状態の変更と、その変更を他のシステムに伝えるための「イベント」を同時に記録する。そして、別のプロセスがこのイベントを読み取り、S3やQdrantなどの他のサービスを更新するというものだ。この方法は、PostgreSQLなどのデータベースを「真のデータ源(Source of Truth)」として、変更が必要なイベントを記録する「アウトボックステーブル」を活用し、その変更を「変更データキャプチャ(CDC)」のような仕組みで他のシステムに伝播させる。

次に、「クエリパス」、つまり画像検索を行う際の処理の流れと問題点について説明する。テキストクエリが入力されると、それは「埋め込み」と呼ばれる数値データに変換され、Qdrantを使って最も類似するベクトルを検索する「近似近傍探索(ANN search)」が行われる。S3オブジェクトの識別子とQdrantのベクトル識別子を同じにすることで、検索結果から元の画像に直接アクセスできる。しかし、ここでも整合性の問題が影響する。Qdrantにはベクトル情報があるが、S3に元の画像が存在しないという不整合が生じている場合、クエリはこの状況を検出したり回復したりできない。さらに、S3のgenerate_presigned_urlという機能は、オブジェクトが実際に存在するかどうかを確認せずに有効なURLを生成する。そのため、フロントエンドはURLを受け取って画像を読み込もうとするが、オブジェクトが存在しないためにエラーが発生するという事態が起こりうる。

これらの問題点以外にも、システムエンジニアとして考慮すべき重要な点がある。それは「セキュリティ」である。特にユーザーがアップロードする画像のメタデータ(撮影日時や位置情報など)には、プライバシーに関わる敏感な情報が含まれている場合がある。これらの情報が他のユーザーに公開されると、ユーザーを危険にさらす可能性があるため、システムに画像が取り込まれる際には、表示されるバージョンからこれらの機密性の高いメタデータを削除するプロセスが必要だ。同時に、画像の所有者が後でダウンロードできるように、オリジナルの画像とそのメタデータは安全に保存しておくべきである。この場合、オリジナルバージョンは所有者のみがアクセスできるプライベートなものとし、処理済みのバージョンを検索結果や表示用として使用するという方法が考えられる。

このプロジェクトを通じて、システムの構築は単に機能を実装するだけでなく、データの整合性、スケーラビリティ、セキュリティといった多岐にわたる課題を考慮した設計が不可欠であることが深く学べた。特に、複数の独立したデータストア間でデータを同期させる際の「デュアルライト問題」とその解決策としての「Transactional Outboxパターン」の考え方は、今後のシステム開発において非常に重要な視点となるだろう。

関連コンテンツ

関連IT用語