Slide 1

Slide 1 text

OpenSearchでレガシーな検索処理の 大幅改善をやってやろう PHPカンファレンス小田原2025

Slide 2

Slide 2 text

自己紹介 ● 堂薗 伸樹(どうぞの のぶき) / DPon(@DPontaro) ○ 󰢄園 󰢐薗 ● 職業:エンジニア ● 所属:スターフェスティバル株式会社 ● 家族:妻󰠀 子👦👦 犬 ● ゲーム好き ● 大阪からきました ● 🎉 カンファレンス初登壇 🎉

Slide 3

Slide 3 text

会社とサービスの話 ● https://gochikuru.com/ ● 法人・団体向けの宅配弁当 ケータリング・オードブルの デリバリーサービス。

Slide 4

Slide 4 text

前段 プロジェクトの説明

Slide 5

Slide 5 text

プロジェクトの特徴 ● ごちクルとは別の特定業界向けのお弁当デリバリーサービス ● Initial Commitは2015年 ● コロナ禍で需要減。リソース都合もあり、しばらく専任のエンジニアがついていない 状態。 ● ときどき改善依頼があった際、手が空いてる人がスポットでアサインされる

Slide 6

Slide 6 text

パフォーマンスの課題 ● 2024年春、サイトのパフォーマンスは少し触れただけでも体感できるくらいに悪い ● 需要が戻りつつあるなか、マイナスの印象を与えるわけにもいかない

Slide 7

Slide 7 text

レガシーな環境

Slide 8

Slide 8 text

初手の改善

Slide 9

Slide 9 text

ボトルネックをみつける 商品検索のController。 リクエストされる頻度高く、かつ重い ページ。 中央値が 1.9 sec これに加え、ページ表示までに画像や cssのダウンロードなども発生する

Slide 10

Slide 10 text

初手の改善 スロークエリに手を入れてみる ● INDEXの追加 ● 該当箇所のコードを調整し、発行されるクエリを修正 結果200msec くらいは改善したが焼け石に水。

Slide 11

Slide 11 text

さてどうしたものか 長年専任がついていないプロジェクト、コードはツギハギで無駄も多い。 複雑に絡み合ったコードの解消のために、検索ロジック全体を組み直す必要がありそ う。 → それならもう OpenSearch に移行してもいいのでは?

Slide 12

Slide 12 text

OpenSearchとは ● 分散型の検索エンジン。Elasticsearchのフォーク版 ● クラスタ構成、複数のノードにデータ分散。 ● 高速な全文検索が可能

Slide 13

Slide 13 text

簡単に用語解説:Document ● Document:データを格納する単位。JSON形式。 ○ 学生のデータベースでは、 Documentは1人の学生を表せる ○ RDBにおける行に相当

Slide 14

Slide 14 text

簡単に用語解説:Index ● Index:Documentの集まり ○ RDBでいうテーブル ○ 学生のデータベースでは、 Indexはすべての学生を表す

Slide 15

Slide 15 text

簡単に用語解説:Clusterとか ● Cluster, Node ○ Clusterは、Nodeの集まり。 ○ Nodeはデータを保存、検索リクエストを処理 するサーバ。 ● Shard ○ いわゆる水平分割。 ○ Indexのdocumentを行単位でシャード分割し て分散 ○ シャードの分割目安:シャードのサイズを 10 ~ 50 GB に制限することです。(公式いわく)

Slide 16

Slide 16 text

OpenSearchへの移行

Slide 17

Slide 17 text

OpenSearch公式のPHPクライアント まぁバージョン下げたら対応してるやつあるやろ。 https://github.com/opensearch-project/opensearch-php

Slide 18

Slide 18 text

おっかしいなー

Slide 19

Slide 19 text

真面目にどうするか 理想はPHPのバージョンアップ。そうはいっても ● スポット的なアサインでもあり、メインは別プロジェクトの人員 ● 元のミッションはパフォーマンス改善。 →バージョンアップはスコープ外では? ● 認知負荷の高いコードの理解、スロークエリの対応など既にある程度時間もかけて いる。 →このタイミングからバージョンアップまでやる? ※言わずもがなテストはメンテされておらず

Slide 20

Slide 20 text

戦略的後回し // TODO PHPバージョンアップ 結局公式のPHP7.3が対応してるバージョンのクライアントを落としてから、7.1で動くよう にエラーが出る箇所をつぶして対応させました。 ※バージョンアップはパフォーマンス改善後に対応しました(7.4までですけど...

Slide 21

Slide 21 text

バッチの実装 毎時 RDS -> OpenSearch へデータ投入するバッチを実行。 その際”YYYYMMDD-HH”のサフィックスを持つINDEXが作成される。  例:product-index-20250412-15 INDEX作成後、アプリケーションが参照している OpenSearchのINDEXのエイリアスを変更。  実行前:product_index -> product-index-20250412-14  実行後:product_index -> product-index-20250412-15

Slide 22

Slide 22 text

マッピングの話 マッピングはOpenSearchに、ドキュメントとそのフィールドの保存方法とインデックスを指示します。 各フィールドのデータ型を指定することで (例えば、年を日付にするなど )、保存やクエリをより効率的に行うこと ができます。 Dynamic mapping (動的マッピング)  新しいデータやフィールドが自動的に追加される。 Explicit mapping (明示的マッピング)  推奨。正確な構造とデータ型を前もって定義できる。  パフォーマンスや正確性を高めることができる

Slide 23

Slide 23 text

マッピング一例 "id": map[string]string{"type": "integer"}, "product_name": map[string]interface{}{ "type": "text", "analyzer": "product_index_analyzer", "search_analyzer": "product_search_analyzer", },

Slide 24

Slide 24 text

analyze 文字列を検索に使えるように「分かち書き(トークン化)」&「正規化」する処理

Slide 25

Slide 25 text

analyzer と search_analyzer "product_name": map[string]interface{}{ "type": "text", "analyzer": "product_index_analyzer", "search_analyzer": "product_search_analyzer", }, analyzer:インデックス時(保存時)に使用する analyzer を指定。 search_analyzer:検索時に使用する analyzer を指定

Slide 26

Slide 26 text

synonym_filter search_analyzer には上記のsynonym_filterという同義語の設定がされている。 この設定により曖昧検索に対応できる "synonym_filter": map[string]interface{}{ "type": "synonym", "synonyms": []string{ "ウナギ,ウナジュウ", "子供,お子様",

Slide 27

Slide 27 text

やってみてどうだった

Slide 28

Slide 28 text

🎉結果発表🎉 before 中央値:1.9sec after 中央値:0.92sec

Slide 29

Slide 29 text

売上につながった? ここ1年で売上の数値はしっかり伸びている 🎉 複数要因があるものではありますが、パフォーマンス改善で下支えが出来た ※余談 社内の営業の方々から直接 DMでめちゃ助かったと何人か声かけていただいて、好感触

Slide 30

Slide 30 text

他改善するなら ● 毎時のバッチ実行 ○ 日々商品のデータは更新されているが、最大 1時間の遅延を許容している状態 ○ よりリアルタイムに更新したい場合、イベント駆動な仕組みを取り入れたり ● インフラのあいのり ○ ごちクルと同じインスタンスなので障害発生した際は、どちらも検索処理が機能しなくなる(今のとこ ろ発生はしていない) ○ 切り離せば懸念はなくなるが、コスト増にはなるのでバランス考えて ● synonym_filterなどがコード中に定義されている ○ 変更の際にエンジニアの手が入る状態となっているので、できれば外部ファイルに逃がしたほうが 良い