はじめに
エンジニア2年目、現在稼働中のシステムを刷新するリプレイスプロジェクトにバックエンドエンジニアとして参加しています。
これまでの業務経験ではRDB(SQL)しか触ったことがなく、「データといえばテーブル管理、検索といえば SELECT ... WHERE ...」 という世界で生きてきました。
そんな中、新システムの検索基盤として OpenSearch を導入することになり、初めてNoSQLの技術に触れることになりました。
本記事では、RDBしか知らなかった私が、OpenSearchの実装を通して感じた「RDBとの違い」や、実装時にハマって解決した「Wildcard型」の知見について共有します。
導入背景
私が参画した時点で既にOpenSearchの採用は決定していました。
当時の私は「なぜわざわざ新しい技術を使うの?」と思っていましたが、背景には「データ増大に伴うRDB(LIKE検索など)のパフォーマンス限界」を打破する狙いがありました。
実際にRDBでは、部分一致(LIKE %hoge%)の検索においてインデックスが効きづらく、レスポンス速度が課題になりがちだからです。
OpenSearch とは?(RDB脳での理解)
OpenSearchは、検索や分析を行うための分散型全文検索エンジンです。
詳細な概要は↓
RDBしか知らなかった私が最初に戸惑ったのが、「データの持ち方」と「用語」の違いでした。
RDBとは何が違うのか?
RDBが「テーブルに行(Row)と列(Column)でデータを管理する」のに対し、OpenSearch は「JSON形式のドキュメント」としてデータを保存します。
学習し始めの頃は、以下のようにRDBの用語に置き換えて理解していました。
| RDB (SQL) | OpenSearch | 備考 |
|---|---|---|
| Database | Cluster | データの集合体全体 |
| Table | Index | データの種類ごとの入れ物 |
| Row | Document | 1件ごとのデータ(JSON形式) |
| Column | Field | データの中の項目(key) |
なぜ検索が早いのか?
RDBでいう「本の最初から最後までページをめくって探す(フルスキャン)」のとは異なり、OpenSearch は「転置インデックス(Inverted Index)」という仕組みを持っています。
これは、「本の巻末にある索引」のようなものです。
「どの単語が、どのドキュメントに含まれているか」があらかじめ整理されているため、膨大なデータの中からでも一瞬で目的のデータを見つけることができます。
技術的にハマった点と解決策:Wildcard型の活用
概念は理解できたものの、実装当初、RDBと同じ感覚で進めていた私は「アンダーバー(_)を含む文字列」の検索精度で躓きました。
直面した課題
標準の text 型フィールドでも部分一致検索は可能ですが、アナライザーによって単語が分割(Tokenize)されてしまい、「アンダーバーを跨ぐような検索がヒットしない」という問題や「Text型へのワイルドカード検索はスキャンが重く、パフォーマンスが出ない」という課題がありました。
解決策:Wildcard型フィールド
そこで採用したのが Wildcard型フィールドです。
これを適用することで、SQLのLIKE検索に近い感覚で、かつ高速に特殊文字を含むパターンマッチングが可能になりました。
「こんな便利な型があるのか!」と、RDBにはない柔軟さに驚きました。
{
"mappings": {
"properties": {
"product_name": {
"type": "wildcard"
},
"category": {
"type": "keyword"
},
"status": {
"type": "keyword"
}
}
}
}
RDB脳のための「クエリ翻訳ガイド」
OpenSearchのクエリ(Query DSL)はJSON形式で記述するため、SQLしか書いてこなかった私は最初かなり戸惑いました。
自分の整理も兼ねて、よく使う条件の対応表と書き方の違いをまとめました。
| 条件 | RDB (SQL) | OpenSearch (Query DSL) |
|---|---|---|
| AND条件 | WHERE A AND B | bool > must (または filter) |
| OR条件 | WHERE A OR B | bool > should |
| NOT条件 | WHERE A != B | bool > must_not |
| LIKE検索 | WHERE name LIKE '%abc%' | wildcard (または match) |
具体的なクエリ比較
SELECT * FROM products
WHERE status = 'active'
AND product_name LIKE '%product%'
{
"query": {
"bool": {
"filter": [
{
"term": {
"status": "active"
}
}
],
"must": [
{
"wildcard": {
"product_name": {
"value": "*product*"
}
}
}
]
}
}
}
Point: filterとmustの使い分け
SQLの AND はすべて同じ扱いですが、OpenSearchには must(スコア計算あり)と filter(スコア計算なし・キャッシュ有効)があります。
「単に条件に一致するか(Yes/No)」だけの条件は filter に入れることで、検索速度がさらに向上します。これを知ったとき、OpenSearchの奥深さを感じました。
業務で使用して感じたメリット
実際に新システムで運用してみて、検索速度以外にも開発体験としてのメリットを強く感じました。
仕様変更に強い
開発途中、「やっぱりこの条件でも絞り込みたい」「権限によって表示を変えたい」という要望が頻発しました。
RDBではSQLの組み直しが大変ですが、OpenSearchのDSLなら、JSONの配列に条件を追加・削除するだけで済みます。
経験の浅い私でも、「クエリ構築の柔軟性」のおかげで、度重なる変更にスピーディに対応できました。
また、今回はバックエンド言語にJavaを使用しましたが、OpenSearch はREST API ベースなので、Python や Go 、Node.js など他の言語でも同様に扱えます。
final String status = request.getStatus
if (ObjectsUtils.isNotEmpty(status)) {
boolQuery.filter(termQuery("status", status))
}
文字列を操作するのではなく、ブロックを組み立てるようにクエリを構築できるため、複雑な条件分岐があってもバグを生みにくく、アジャイル開発と非常に相性が良いと感じました。
権限まわりのフィルタリングが容易
「特定の部署の人には、このデータを見せない」といった複雑な権限ロジックも、検索クエリの filter 部分に条件を差し込むだけで完結します。アプリケーション側でデータを全件取得してから間引く必要がないため、パフォーマンスを落とさずにセキュリティ要件を満たすことができました。
まとめ
導入の結果、検索処理が高速化され、ユーザーからも「検索結果が出るのを待たなくて良くなった」と非常に良いフィードバックをいただくことができました。
2年目の私にとって、RDB以外のデータストアを扱うのは大きな挑戦でしたが、「Wildcard型」や「Filterコンテキスト」など、適切な機能を使いこなすことでRDBでは実現できないパフォーマンスが出せることを学びました。
今後も食わず嫌いせず、新しい技術に挑戦していきたいと思います。