検索機能をOpenSearchからRDSに移行した話
みなさんこんにちは!
ワンキャリアでデータエンジニアをしている野田です!(GitHub:@tsugumi-sys)
最近はウイスキーにハマっています!世界中のウイスキー価格が高騰する中、お手頃で美味しいものはないかと必死に探しています。最近、Amazonが招待制(抽選)で高級ウイスキーを定価で販売していることを知りました。抽選に応募してはや1ヶ月経ちましたが、そろそろ当選メールが届くとワクワクしながら待ち続けています。さて、趣味の話はこれくらいにして、早速本題に入っていきましょう!
本記事では、ONE CAREERで展開するサービスで利用している検索機能をOpenSearchからRDSでの検索に移行した際の知見を共有します。OpenSearchを止めるべきだという話ではなく、要件次第ではコストを抑えつつ、RDSで検索機能を実現可能であるという学びを共有したいモチベーションで書かせていただきました。筆者は検索システムを取り扱うのが初めてのひよっこなので、温かい目で読んでいただけると幸いです。
背景
OpenSearchをRDSに移行するきっかけは、別サービスのシステムデザインにおいて採用すべきベクターDBの議論の中で、OpenSearchの話が出てきたことです。
参考情報として、現在のONE CAREERにおけるOpenSearchの用途とインフラの費用を改めて確認してみると、用途が企業名の検索のみだったにもかかわらず、そこそこのランニングコスト・管理運用コストが発生していることが判明しました。
現状の用途と照らし合わせてOpenSearchがオーバースペックである可能性が出てきたため、RDSへの移行を検討することになりました。
準備・計画
すでに説明した通り、第1の準備として検索機能の要件を洗い出すことから始めました。簡潔にまとめると、次の要件を満たしていれば良いことが判明しました。
機能要件
検索機能:企業名(ひらがな・英語などの表記揺れ含む)を用いて企業を検索できること。
非機能要件
レイテンシー:OpenSearchと同等であること。
すでに背景でも説明した通り、ONE CAREERにおける検索機能は企業検索のみに限られていました。こちらは、OpenSearchに接続している各サービスを地道に調査しました。
また非機能要件については、OpenSearchと同等のレイテンシーであることが求められました。一方で、企業検索のトラフィック規模を調べたところ、そこまで高負荷なトラフィックが発生しないことが判明しました。こちらは検索機能を利用するユーザー行動ログ(GA4)に加え、Datadogの計測結果を元に計算しました。したがって、検索機能をRDSに切り替えた際に大きな負荷がかからないことをあらかじめ把握することができました。
要件が判明した段階で、次に各サービスのOpenSearchへの依存方法を整理しました。調査の結果、依存方法として以下の2つが存在していました。
パターン1:異なるサービスで共通利用しているモジュール経由での依存。
パターン2:上記モジュールを経由せず、直接依存。
ONE CAREERのソフトウェアは、技術的負債の返済と新規機能の開発を同時に進行しながらスピーディに発達しているため、依存状態がやや複雑となっていました。そこで、まずはより複雑度の小さいパターン2の移行から段階的に行う方針で計画を立てました。段階的に移行していくことで、下記のようなリスクの回避を期待できます。
予期できなかったバグなどの不確実性
サービス全体での検索機能ダウン
実装・リリース
まずは、OpenSearchに直接依存しているサービスのOpenSearch移行(先述のパターン2)から取り掛かりました。
実装
RDS(PostgreSQL)を用いた検索方法としては、LIKE/ILIKE、pg_bigmを使う方法などがあります。検証の結果、LIKE/ILIKEで十分検索可能であることが判明したためこちらを採用することとなりました。これは、企業名で検索しているため検索対象の1レコード当たりのデータが小さいこと(企業名・表記揺れデータの2カラムのみ)、検索件数が膨大ではなかったためです。また、pg_bigmが外部ライブラリであるためインストールが必要である一方、LIKE/ILIKEはデフォルトで利用できることも嬉しい点です。
検索パフォーマンス悪化を防ぐために、アプリケーション側でキャッシュを利用する実装も加えました。人気企業に関する情報は他企業に比べて頻繁に検索される等のアクセスパターンを考慮すると、キャッシュのヒット率はある程度期待できると判断しました(実際、リリース後のヒット率は約65%でした)。
SELECT id, name FROM companies WHERE name ILIKE '%keyword%' OR
alias_names ILIKE '%keyword%';
(keyword: 検索ワード)
検索精度のテスト
検索精度のテストも行いました。検索要件がシンプルなものであることがわかっていたため、既存のユニットテストとモンキーテストを実施することで検索精度のデグレが発生していないことを確認しました。より柔軟な検索の場合は、より詳細なテストが必要だと考えられます。一方で今回は、テストにかかるコストとのバランスをみた上で上記のテストで十分であると判断できました。
パフォーマンステスト
ユーザビリティを損なわないスピードで検索結果を返せることも、テストして検証しました。具体的には以下の2種類のテストを行いました。
キャッシュがない場合の検索レイテンシーテスト
負荷テスト
検索レイテンシーテストに関しては、モンキーテストにより実施しました。トラフィック量が大規模ではないことがわかっていたため、より高度な検索レイテンシーテスト(実際のアクセスログを用いて再現する等)は不要と判断したためです。一定量のサンプルを抽出して、検索レイテンシーの大きなデグレが発生していないことを確認しました。
負荷テストは、heyというツールを用いて実施しました。あらかじめ概算したトラフィック量を参考に、それよりもやや高負荷をかけて行いました。こちらも、検索レイテンシーに大きなデグレが発生しないことを確認しました。
リリース
リリース後は、Datadogを用いて検索レイテンシーの増大・エラー発生がないかを監視しました。レイテンシーを維持しつつ、エラー発生なしで移行できていることを確認しました。
レイテンシーのグラフ。図中線はリリースタイミング。リリース前後で検索レイテンシーが増大していないことを確認できた。
OpenSearchへのリクエスト数のグラフ。図中線はリリースタイミング。リリース後にOpenSearchへのリクエストが停止したことを確認できた。
各サービスの移行
上記の方法で無事移行できることを確認できました。ここで得られた知見を元に、各サービスを順番に移行していくことでOpenSearchの移行を無事完了させることができました(パターン1の移行)。各サービスでは、共通モジュール経由で検索機能を用いていたため、共通モジュールのアップデート&各サービスへの反映を行う必要があり、移行コストはそこそこかかることとなりました。実際には自分含めた2名のエンジニアが作業を行い、開始から約1ヶ月で移行が完了しました。
まとめ
今回の記事では、検索機能をOpenSearchからRDSに移行した際の知見や実装・実際の作業内容をまとめました。これらの経験を通じて得られた教訓は2つあります。
1つ目は、検索機能をRDSで構築することは要件次第で十分可能であるということです。ONE CAREERがOpenSearch(旧Elasticsearch)を採用したのは自分の入社以前であり、ドキュメント等も残っておらず、当時の意思決定背景は不明でした。今回のケースにおいては、企業名検索というシンプルな機能要件であったこと・大規模なトラフィックが発生しないことなどから、RDSでも十分構築可能であったということがわかりました。改めてまとめると、RDSでも十分なケースとしては以下の要素が考えられます。
検索対象のデータ量が大規模ではない
トラフィックが大規模ではない
検索機能の拡張性(より柔軟な検索)が要件にない
2つ目に得られた教訓は、共通モジュールの運用と変更容易性とのトレードオフの関係性です。
移行時に一番苦労した点は、共通化されたモジュールの更新による影響範囲の把握です。複数のサービスが共通モジュールに依存していた(サービス内でさらに様々な箇所で依存していた)ため、破壊的変更による影響範囲が非常に大きくなっていました。検索システムには様々な選択肢があり、技術移行の可能性が高いと考えられます。移行コスト削減として適切な抽象化が必要なのはもちろんのことですが、共通モジュールの運用にも気をつけないといけないなと考えさせられました。共通モジュールに依存するサービスが増えれば増えるほど、実態としての影響範囲の把握がより困難になり、変更に時間・コストがかかるというトレードオフを学びました。
おわりに
今回の開発では非常に大きな教訓を得ることができました。また、今回削減したコストの一部をデータチームの新機能開発予算として組み込むことができるようにもなりました。ガンガン開発していくぞー!
ここで共有した内容が他の人の役に立つと嬉しいです。ここまで読んでいただきありがとうございました!
▼ワンキャリアのエンジニア組織のことを知りたい方はまずこちら
▼カジュアル面談を希望の方はこちら
▼エンジニア求人票