Elasticsearchにおける「ユーザー辞書」の利用方法を整理する — Serverlessならインライン辞書

こんにちは!Legalscape の Labs チームで AI エンジニアを務めている白水(@sorami)です。

Legalscape は AI 技術で「法情報の調査」を革新するサービスを開発しており、Labs チームでは情報検索や文章生成など、AI・データに関する取り組みを進めています。

法律のような専門ドメインには独特の用語が多く、これらを適切に扱えるかが検索品質を大きく左右します。多くのサービスで採用されている検索エンジン Elasticsearch(または OpenSearch)でこの要請に応える仕組みのひとつが ユーザー辞書 です。

本記事では、特に Serverless 環境でも利用できる インライン辞書(user_dictionary_rules について概要や使い方、実用性について解説します。

TL;DR

  • 形態素解析器 Kuromoji - ユーザー辞書には「ファイル方式」だけでなく「インライン方式」もある
  • Serverless では辞書ファイルが使えないが、インライン(user_dictionary_rules)なら利用できる
  • 数千件規模なら全く問題ない。数十万件規模になるとインデックス作成タイムアウトのリスクがある
"tokenizer": {
  "my_kuromoji": {
    "type": "kuromoji_tokenizer",
    "mode": "search",
    "user_dictionary_rules": [
      "金融商品取引法,金融商品 取引法,キンユウショウヒン トリヒキホウ,カスタム名詞"
    ]
  }
}

日本語の全文検索とトークナイザー

全文検索では、まずテキストを単語のような単位(トークン)に分けて扱います。この分割を担うのがトークナイザーで、Elasticsearch で日本語を扱う場合、主に以下の選択肢があります。

金融商品取引法 というテキストを例に見てみましょう。

n-gram は、Elasticsearch にビルトインのトークナイザーで、テキストを「決まった文字数」で分割する方式です。これには辞書を必要としません。

  • 2文字(bigram): 金融 / 融商 / 商品 / 品取 / 取引 / 引法
  • 3文字(trigram): 金融商 / 融商品 / 商品取 / 品取引 / 取引法

Kuromoji は、Atilika が開発した形態素解析器です。Elasticsearch には公式プラグインとして提供されており、Elastic Cloud では追加インストールなしで使えます。同梱された辞書(IPADIC)を用いて分割します。言語として意味の通る単位になりますが、辞書に載っていない語への対応が課題となります。

  • 金融 / 商品 / 取引 /

Sudachi はワークスアプリケーションズの徳島人工知能NLP研究所が開発する形態素解析器で、Elasticsearchプラグインが提供されています。Sudachi辞書は専門家により定期的に更新され、表記の正規化(「附属」→「付属」、「打込む」→「打ち込む」など)も備えています。3段階の分割粒度に対応しており、例えば検索用途では「短い単位」と「長い単位」を同時に用いることで、「粗い粒度での完全一致」と「細かい粒度での部分一致」を両立できます。

  • 短い単位(A): 金融 / 商品 / 取引 /
  • 長い単位(C): 金融商品取引法
  • 検索(A + C): 金融商品取引法 / 金融 / 商品 / 取引 /

このように、トークナイザーによって分割結果は大きく異なります。そして、それが「検索で何がヒットするか」を決めます。

実際の検索システムでは、文字フィルター(ICU normalizer による正規化など)やトークンフィルター(同義語展開、基本形への正規化、ストップワード除去など)を組み合わせたり、形態素解析と n-gram を併用するといった工夫がなされます。関心のある方は以下の記事をご覧ください。

形態素解析器のユーザー辞書

先述したように、Kuromoji はデフォルトの内蔵辞書(IPADIC)に基づいてテキストを分割します。しかし、デフォルト辞書の収録語彙には限りがあり、専門用語や固有名詞を適切に扱えるとは限りません。こうした語をトークナイザーに教えるための仕組みが「ユーザー辞書」です。

ユーザー辞書は、トークナイザーに「この語はこう扱ってほしい」という追加情報を渡す仕組みです。専門用語や固有名詞、新語などをエントリとして登録することで、デフォルト辞書では対応しきれない語を意図通りに分割できます。

先ほどの例に戻りましょう。金融商品取引法 は Kuromoji のデフォルトでは 金融 / 商品 / 取引 / に分割されます。この状態で 金融商品 と検索すると、金融商品 がそれぞれ独立したトークンとして評価されるため、金融商品 とは無関係な文脈(たとえば 商品レビュー金融政策)にもヒットし、検索スコアが散ってしまいます。これが過分割の問題です。

では、金融商品取引法 をまるごと1つのトークンとしてユーザー辞書に登録すればよいでしょうか。今度は逆に、金融商品取引法 で検索してもこの文書がヒットしなくなります。これが過統合のリスクです。

このように、どんな語をどのような分割形式でユーザー辞書に登録するかは、検索のユースケース次第で、一律の正解はありません。

Kuromoji のユーザー辞書

Kuromoji には2つの方式があります。

ファイル(user_dictionary — 辞書ファイルをサーバーの config/ ディレクトリに配置し、そのパスを指定する方式です。クラスター内の全ノードに同一のファイルを配布する必要があります。

インライン(user_dictionary_rules — Elasticsearch 7.4(2019年)で導入された方式で、インデックス設定の JSON に直接辞書エントリを記述します。ファイル配布が不要で、API だけで完結します(詳細後述)。

いずれの方式でも、Kuromoji のユーザー辞書に登録した語は「エントリで指定した通りに強制的に分割」されます。つまり、文脈に応じて分割を変えるといった柔軟な制御はできません。

# エントリの例(表層形,分割,読み,品詞の4列CSV)
東京スカイツリー,東京 スカイツリー,トウキョウ スカイツリー,カスタム名詞

フォーマットの詳細は Elastic 公式の kuromoji_tokenizer ドキュメントを参照してください。

Sudachi のユーザー辞書

Sudachi のユーザー辞書では、連接コストや分割モードごとの分割方法を定義できます。複数粒度での分割をユーザー辞書の語にも適用できるため、Kuromoji よりも精密な制御が可能です。

ただし、その分だけ辞書エントリの記述は複雑になり、辞書のビルド工程も必要になります。エムスリー社が Kuromoji から Sudachi へ移行した事例では、この表現力の高さが医療用語の検索品質向上に大きく貢献した一方、管理コストの増加も報告されています。

# エントリの例(連接コスト・粒度別分割・正規化形などを含む多数の列を持つ CSV)
東京スカイツリー,4789,4789,5000,東京スカイツリー,名詞,固有名詞,一般,*,*,*,トウキョウスカイツリー,東京スカイツリー,*,C,東京 スカイツリー,*,*,*

フォーマットやビルド手順は Sudachi 本体のユーザー辞書ドキュメント を、Elasticsearch での設定方法は elasticsearch-sudachi の README を参照してください。

ユーザー辞書の環境ごとの制約

実際にどの方式でユーザー辞書が使えるかは、動作環境によって異なります。

Elasticsearch / OpenSearch の動作環境は、大きく以下の3つに分類できます。

オンプレミス / セルフマネージド — 自前でサーバーを管理する構成です。ファイルシステムに自由にアクセスできるため、辞書ファイルの配置もカスタムプラグインの導入も制約なく行えます。

マネージドサービスElastic Cloud(Hosted)AWS OpenSearch Service など、クラウド事業者が管理する環境です。辞書ファイルの配置には各サービス固有の仕組みが必要になります。Elastic Cloud では Extensions(Bundle)、AWS OpenSearch では Custom Packages(S3 連携)を経由して辞書ファイルを配布します。

サーバーレスElastic Cloud ServerlessAWS OpenSearch Serverless など、ノードの管理が完全に抽象化された環境です。ファイルシステムへのアクセスができず、カスタムプラグインの導入もできません。辞書ファイルを配置する手段がないため、ファイルベースのユーザー辞書は使えません。なお、Kuromoji プラグイン自体はどちらの Serverless 環境でもデフォルトで同梱されています。

以下の表に、環境ごとのユーザー辞書の利用可否をまとめます(2026年5月時点)。

環境 Kuromoji(ファイル方式) Kuromoji(インライン方式) Sudachi
オンプレミス / セルフマネージド
Elastic Cloud(Hosted)
AWS OpenSearch Service
Elastic Cloud Serverless
AWS OpenSearch Serverless

Serverless 環境では、Kuromoji のインライン辞書が唯一の選択肢です。

Serverless でのカスタム辞書サポートの見通し

2026年5月時点で、公式ロードマップに Serverless でのカスタム辞書サポートに関する言及はありません。Serverless でカスタム辞書(ファイルベース辞書やプラグイン)を使えないか、というコミュニティの問い合わせは時折見られますが、いずれも明確な対応予定の回答は示されていないようです。

Kuromoji インライン辞書の概要

インライン辞書(user_dictionary_rules)は、インデックス設定の JSON に辞書エントリを直接記述する方式です。

PUT /my_index
{
  "settings": {
    "index": {
      "analysis": {
        "tokenizer": {
          "my_kuromoji": {
            "type": "kuromoji_tokenizer",
            "mode": "search",
            "user_dictionary_rules": [
              "金融商品取引法,金融商品 取引法,キンユウショウヒン トリヒキホウ,カスタム名詞"
            ]
          }
        },
        "analyzer": {
          "my_analyzer": {
            "type": "custom",
            "tokenizer": "my_kuromoji"
          }
        }
      }
    }
  }
}

この機能は Elasticsearch 7.4(2019年8月)で導入されました(GitHub PR #45489)。Synonym Token Filter が synonyms(インライン)と synonyms_path(ファイル)の両方をサポートしていたのと同じ設計パターンを、Kuromoji トークナイザーに適用したものです。

先述したように、これは現状の Serverless 環境でユーザー辞書を扱う唯一の方法です。

なお、Elasticsearch 7.4 でこの機能が導入された直後の解説記事として Kuromojiのカスタム辞書をインデックスの設定で指定 - johtani があり、設定例とあわせて読むと理解が深まります。

使い方

辞書エントリのフォーマット

各エントリは CSV 形式の文字列で、以下の4つのフィールドをカンマ区切りで記述します。

<表層形>,<分割後のトークン列(スペース区切り)>,<読み(カタカナ・スペース区切り)>,<品詞>

いくつかの例を示します。

"user_dictionary_rules": [
  "金融商品取引法,金融商品 取引法,キンユウショウヒン トリヒキホウ,カスタム名詞",
  "機械学習,機械学習,キカイガクシュウ,カスタム名詞",
  "東京スカイツリー,東京 スカイツリー,トウキョウ スカイツリー,カスタム名詞"
]

1行目の 金融商品取引法金融商品取引法 の2トークンに分割されます。2行目の 機械学習 は分割せず1トークンのまま保持します。3行目の 東京スカイツリー東京スカイツリー に分割します。

_analyze API での動作確認

辞書エントリが意図通りに機能しているかは、_analyze API で確認できます。explain: true を指定すると、各トークンの品詞情報や読みも取得でき、ユーザー辞書由来のトークンかどうかを判別できます。

POST /my_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": "金融商品取引法についての解説",
  "explain": true
}

たとえば前掲の辞書(金融商品取引法金融商品 + 取引法 に分割)を設定したインデックスで 金融商品取引法についての解説 を解析すると、次のように分割されます。

金融商品 / 取引法 / について / の / 解説

金融商品 がひとつのトークンになっており、ユーザー辞書が効いていることが確認できます。

注意点

user_dictionary との排他 — ファイルベースの user_dictionary とインラインの user_dictionary_rules は同時に指定できません。両方を設定すると IllegalArgumentException が発生します。これは意図的な設計判断です。

重複エントリの禁止 — 同じ表層形のエントリを複数記述するとエラーになります。Lucene の内部実装(UserDictionary)が重複を許容しないためです。

Blue-Green 方式による辞書更新

Kuromoji のインライン辞書を更新するには、本来であればインデックスを閉じて設定を書き換え、開き直す必要があります(analysis 設定は静的設定のため、オープン中は変更できません)。

POST /my_index/_close
PUT  /my_index/_settings    ← user_dictionary_rules を更新
POST /my_index/_open

ところが、Elastic Cloud Serverless では _close / _open API が利用不可です(Differences from other Elasticsearch offerings で明記)。インフラを Elastic 側が管理する Serverless の設計上の制約です。

ちなみに、_reload_search_analyzers API を使えばホットリロードできるのでは、と思うかもしれませんが、この API は同義語フィルター(synonym / synonym_graph)専用で、トークナイザーレベルの設定であるユーザー辞書には対応していません。

したがって Serverless で辞書を更新するには、インデックスを作り直すしかありません。 ダウンタイムを避けるには、エイリアスを使った Blue-Green 方式が有効です。新しい辞書設定で新規インデックス(my_index_v2)を作成し、_reindex API で旧インデックスからデータをコピーした上で、エイリアスをアトミックに切り替えます。

POST /_aliases
{
  "actions": [
    { "remove": { "index": "my_index_v1", "alias": "my_index" } },
    { "add":    { "index": "my_index_v2", "alias": "my_index" } }
  ]
}

アプリケーションはエイリアス my_index を参照し続けるだけで、切り替え中もダウンタイムなく検索を継続できます。なお、辞書を変えても既存ドキュメントの転置インデックスは古い辞書のままなので、_reindex(=全ドキュメントの再インデックス)が必要になる点も忘れないでください(参考: 辞書の更新についての注意点 - johtani)。_reindex 中に登録・更新されるデータの扱いは、事前に設計しておく必要があります。

インライン辞書はどこまで使えるか — Serverless 上でのスケール検証

インライン辞書は手軽に使える一方で、辞書エントリがインデックス設定としてクラスターステート(Cluster State)に格納されるという特性があります。クラスターステートはクラスター内の全ノードに複製されるメタデータで、エントリ数が増えるほど肥大化し、ノードの起動やリカバリのコストに影響します。

では、実際にどの程度のエントリ数まで実用的なのでしょうか。Elastic Cloud Serverless 上で計測してみました。

検証方法

テストデータには、Sudachi の辞書(full 版)から抽出した複合語エントリを使いました。約140万件あり、特定のドメインに依存しない汎用的な語の集合です。これを 100 件から 500,000 件まで段階的に増やしながら、各段階でインデックスを作成し、作成時間・_settings のサイズ・_analyze のレスポンスタイム・辞書更新の可否を計測しました(各段階3回、中央値)。環境は Elastic Cloud Serverless(GCP us-central1、Elasticsearch 9.5.0)です。

検証結果

エントリ数 作成時間(中央値) _settings サイズ _analyze 作成成功
100 1.9秒 11 KB 524ms
1,000 2.5秒 105 KB 521ms
10,000 4.6秒 1.0 MB 519ms
50,000 39秒 5.1 MB 504ms
100,000 71秒 10.2 MB 488ms
200,000 159秒 20.4 MB 473ms
500,000

※ 500,000 件はこの計測では失敗しましたが、後日の追試では同じデータが36秒で作成成功しています(詳細後述)。

ここから見えたことを4点に整理します。

1. 作成時間はクラスタ状態に強く依存し、エントリ数だけでは決まらない。 スケールテストでは、1万件で4.6秒、5万件で39秒、10万件で71秒、20万件で159秒と、エントリ数に応じて伸びていきました。

ただしこれらは「ある時点のクラスタ状態でのスナップショット」にすぎません。同じデータの後日の追試では、50万件が36秒で作成成功(25万=19秒、35万=24秒、40万=30秒、45万=33秒)と別の傾きの結果になっており、同じ規模での測定間の差は最大約9倍ほどです。

なお、この追試では30万件のみ約43秒で Broken pipe 失敗(前後の 25万・35万件は問題なく成功)しています。これは320秒のタイムアウトとは別種の、一過性の接続エラーと見られます。

作成時間は条件依存で大きく変動する。スケールテスト(青)とタイムアウト追試(橙)の比較

2. 失敗の要因はエントリ数の上限ではなくタイムアウト。 1回目のスケールテストで50万件が3回とも失敗しましたが、エラーは Broken pipe(接続切断)で、3回とも約320秒でほぼ同一でした。これは「辞書の件数上限」のような明示的なエラーではなく、ゲートウェイのリクエストタイムアウト(約320秒)に作成処理が間に合わなかったことを示しています。エントリ数に固定の上限があるわけではなく、その時のクラスタ状態で作成が約320秒を超えれば失敗する、という条件付きの制約です。

3. _settings サイズはエントリ数にほぼ線形。 約107バイト/件で、20万件では20MBを超えました。これがそのままクラスターステートに格納され、ノード間で複製されます。大規模辞書ではメタデータ経路への負荷が無視できなくなる、ということです。

settings サイズはエントリ数に厳密に線形(約107バイト/件)

4. _analyze の速度は辞書サイズにほぼ影響されない。 全段階で470〜530msとほぼ一定でした。辞書はインデックス作成時に一度だけ FST(トライ木)にコンパイルされ、_analyze はその完成済み FST を辿るだけだからです。FST のルックアップは入力長が支配的で、辞書サイズの影響は小さく抑えられます。辞書を大きくするコストは、クエリ時ではなくインデックス作成時に集中して発生するということです。

念のため、インデックスを最大90分放置してから再計測する追試(cold start による劣化の検出を狙ったもの)も行いましたが、放置時間にも辞書サイズにも依存する変化は観測されませんでした。

また、辞書を実際に引くクエリで _searchtook(サーバ側処理時間)を 100 回ずつ計測した分布が下図です。中央値は 100件で2ms、200,000件で4ms と、辞書サイズが2,000倍違って約2倍(極めて sublinear)にとどまります。外れ値として 10〜20ms 程度のスパイクが稀に出るものの、辞書サイズと相関しているわけではありません。実用上、クエリ時のトークナイズコストは無視できる範囲だと考えてよいでしょう。

クエリ時レイテンシの分布(各サイズ 100 回ずつ計測)。上段: `analyze` elapsed はクライアント側で RTT 支配(~500ms 前後)、辞書サイズに依存しない。下段: `search` `took` はサーバ側処理時間で、中央値は数 ms

まとめ

本記事では、Elasticsearch における日本語検索のユーザー辞書について、トークナイザーの基本から環境ごとの制約、そしてインライン辞書の実践まで解説しました。

要点を振り返ります。

  • 日本語の全文検索では、トークナイザーによる分割がそのまま検索精度を左右する
  • ユーザー辞書は専門用語や固有名詞の分割を制御する仕組みだが、過分割と過統合のバランスを考慮した設計が必要
  • Kuromoji のユーザー辞書にはファイルベースとインラインの2方式があり、Serverless 環境ではインライン辞書(user_dictionary_rules)が唯一の選択肢
  • インライン辞書は API 完結で手軽。だが辞書はメタデータ経路(クラスターステート)に乗るため、ドキュメントのように「いくらでも積める」性質ではない
  • Elastic Cloud Serverless では _close が使えず、辞書の更新はインデックス再作成 + alias 切り替え(Blue-Green 方式)が唯一の手段

Elastic Cloud Serverless での計測結果を現実の辞書規模に当てはめると、以下が目安になります。

  • 業務ドメインの専門用語辞書(数百〜数千件)— 問題なし。 作成は数秒、_settings も 1MB 未満
  • 大きめの辞書(数十万件)— 本番には不向き。 変動が大きく、タイムアウトに当たることがある
  • NEologd 級の大規模辞書(数百万件)— 事実上不可。 _settings が数百MB級になり、メタデータとして現実的でない

Serverless 環境の普及に伴い、インライン辞書の重要性は今後も増していくと考えられます。一方、大規模な辞書が必要なケースでは、オンプレ/マネージド環境のファイルベース辞書や、Sudachi の複数粒度での分割といった選択肢も引き続き有効です。自分たちの環境と要件に合った方式を選ぶ際に、本記事が参考になれば幸いです。

Enjoy searching!