Elasticsearchへ日本語の文書を投入して検索すると、たいてい最初はうまくいきません。「東京都」で検索したのに「京都」の記事が並び、「サーバー」で引いた結果に「サーバ」の文書が出てこない。原因はクエリではなく、文書を索引へ入れる前の分割処理にあります。既定のアナライザは空白と句読点で区切る前提のため、日本語では1文字ずつ切るしかなくなるからです。ここを直す道具がkuromojiプラグインで、9.5系(2026年8月時点)を前提に、その組み立て方と運用上の分岐を実装の解像度で追います。
まとめ:日本語検索の設計で先に決める5つの値
kuromojiの設定項目は多く見えますが、実務で判断を迫られるのは次の5点だけです。ここを決めずに動く設定例を貼ると、辞書を足した段階かデータ量が増えた段階のどちらかで作り直しになります。
| 決める値 | 選択肢 | 決め方の基準 |
|---|---|---|
| 分割の粒度 | normal/search/extended | 複合語を分けて拾うか |
| 複合語の扱い | 元の語を残す/捨てる | 索引の肥大を許すか |
| 表記ゆれの範囲 | 長音/全角半角/読み仮名 | 入力するのが誰か |
| 固有名詞の担保 | ユーザー辞書/同義語 | 再索引を回せるか |
| 取りこぼし対策 | 形態素のみ/N-gram併用 | 誤ヒットを許すか |
結論を先に置きます。日本語の検索フィールドは、まずkuromoji_tokenizerをsearchモードで組み、discard_compound_tokenを明示的に指定してください。既定値のまま放置すると複合語が分割後の語と元の語の両方で索引され、件数の見積もりが狂います。そのうえで、辞書に載らない固有名詞や新語が検索対象に多いなら、同じフィールドをmulti-fieldでN-gram側にも持たせてください。製品としての構造や採用可否そのものはElasticsearchとは?転置インデックスとシャード設計・ライセンス系列で決める採用可否で扱っているため、本記事は日本語解析の設定に絞ります。
既定のanalyzerのままでは日本語の検索が成立しない理由
Elasticsearchのanalyzerは、投入された文字列を検索可能な語の列へ変換する処理です。既定のstandard analyzerはUnicodeのテキスト分割規則に沿って動くため、空白で語が区切られる言語では素直に機能します。日本語は語と語の間に空白がないので、この規則に任せると意図しない切れ方になります。
語の切れ目が決まらないと転置インデックスのキーが噛み合わない
全文検索は、語をキーにして文書IDの一覧を引く転置インデックスの上で動きます。索引を作る側と検索する側が同じ規則で語を切っていれば一致しますが、規則が日本語に合っていなければ、そもそも引きたい語がキーとして存在しません。「東京都庁」が一文字ずつ索引されていれば、「京都」という並びも部分的に当たってしまう理屈です。
この仕組みそのものの説明は全文検索エンジンとは?仕組み・主要OSS比較とElasticsearch実装【2026年版】へ、日本語を語へ分ける処理の一般論は形態素解析とは?仕組み・具体例・ツールの選び方をわかりやすく解説へ譲ります。ここから先は、Elasticsearch上でその分割をどう設定するかだけを扱います。
設定ファイルを書く前に_analyzeで分割結果を目で確かめる
analyzerの設計は、設定ファイルを書く前に_analyze APIで手元の文字列を通してみるところから始めます。索引を作らずに分割結果だけを返すAPIなので、辞書を足した効果もその場で確認できます。検索がうまくいかないという報告を受けたら、真っ先にこのAPIへ現物の検索語を投げてください。
POST _analyze
{
"analyzer": "standard",
"text": "東京都庁の全文検索"
}
standard analyzerを指定すると、この文字列は一文字ずつの語に分かれて返ります。同じ文字列をkuromojiへ通せば「東京」「都庁」「全文」「検索」といった語の列になります。検証の場としては、ElasticsearchのDocker構築|単一ノードと3ノードのcompose定義・ホスト設定・永続化の実務で作った単一ノードの環境が手軽でしょう。
analyzerの三層構造|文字の前処理と語の分割と語の加工
analyzerは、char_filterとtokenizerとtoken_filterの三つの層で構成されます。この順序は固定で、入れ替えはできません。どの処理をどの層へ置くかを間違えると、設定は通るのに期待した結果にならないという状態になります。
char_filterは語へ切る前に効く文字単位の書き換え処理
char_filterはtokenizerへ渡す前の文字列そのものを書き換えます。全角の英数字を半角へ揃えるcjk_width、踊り字の「々」を展開するkuromoji_iteration_mark、指定した文字列を置換するmappingがここに入ります。語の切れ目が決まる前の段階なので、表記のばらつきはここで先に潰しておきたいところです。
tokenizerはanalyzerに一つだけ置ける中核の処理
tokenizerは語の切れ目を決める処理で、analyzer一つにつき一つしか指定できません。日本語ではkuromoji_tokenizerか、文字数で機械的に切るngram系のいずれかを選びます。この選択が検索の挙動をほぼ決めるため、後述の判断がそのまま設計の分かれ目になります。
token_filterは直列処理なので並び順が結果を左右する
token_filterは分割後の語を加工する処理で、複数を並べられます。注意すべきは、前段の出力を次段が受け取る直列構造だという点です。語形の変化を終止形へ戻すフィルタの後に品詞で落とすフィルタを置くのと、逆に置くのとでは残る語が変わります。組み込みのkuromojiアナライザは、公式リファレンスによればCJKWidthCharFilterを前段に置き、kuromoji_tokenizerのあとにkuromoji_baseform、kuromoji_part_of_speech、ja_stop、kuromoji_stemmer、lowercaseをこの順で並べた連鎖と等価とされています。独自に組む場合も、まずはこの並びを出発点にすると外しません。
kuromojiプラグインの導入|全ノードへ入れて再起動する
kuromojiはElasticsearch本体に同梱されていないため、プラグインとして追加します。公式リファレンスの導入コマンドは次の一行です。
sudo bin/elasticsearch-plugin install analysis-kuromoji
全ノードのうち1台でも入れ忘れると索引の作成が失敗する仕組み
公式リファレンスは、このプラグインをクラスタの全ノードへ入れたうえでノードを再起動する必要があると明記しています。ここは運用上の落とし穴になりやすい箇所です。3ノード構成のうち1台だけ入れ忘れると、そのノードへシャードが割り当てられた瞬間に索引が開けなくなります。検証環境の単一ノードでは問題が表面化せず、本番のクラスタで初めて露見する型の障害だと言えます。
kuromoji_tokenizerのmode|複合語をどこまで分けるか
kuromoji_tokenizerの挙動を決める中心のパラメータがmodeです。公式リファレンスは3つの値を定義しています。normalは複合語を分解しない通常の分割、searchは検索向けの分割で長い名詞を分解する動き、extendedは未知語をさらに1文字単位まで出力する動きです。
searchモードは複合語を分けて検索の取りこぼしを減らす動き
「関西国際空港」のような複合語は、normalモードでは一つの語として索引され、この状態で「空港」と検索しても一致しません。searchモードなら「関西」「国際」「空港」へ分解されるため、部分的な語での検索が通ります。日本語の検索フィールドでsearchモードが既定の選択になるのは、この取りこぼしを避けるためです。
discard_compound_tokenの既定はfalseで元の語も残る
ここに見落としやすい仕様があります。公式リファレンスによればdiscard_compound_tokenは、searchモードで元の複合語を出力から捨てるかどうかの指定で、既定値はfalseです。つまり何も指定しなければ、分解後の「関西」「国際」「空港」に加えて元の「関西国際空港」も索引されます。
語の数が増えるぶん索引は大きくなり、同じ文書が複数の語で引かれるためスコアの見積もりも変わってきます。元の複合語ごと検索したい要件があるなら残す判断で構いませんが、既定だから残っているという状態は避けたいところです。設定に明示して、どちらを選んだのかを読める形にしておきましょう。
PUT japanese-docs
{
"settings": {
"analysis": {
"tokenizer": {
"ja_tokenizer": {
"type": "kuromoji_tokenizer",
"mode": "search",
"discard_compound_token": true
}
},
"analyzer": {
"ja_text": {
"type": "custom",
"char_filter": ["kuromoji_iteration_mark"],
"tokenizer": "ja_tokenizer",
"filter": [
"kuromoji_baseform",
"kuromoji_part_of_speech",
"ja_stop",
"kuromoji_stemmer",
"lowercase"
]
}
}
}
}
}
token_filterで表記ゆれを吸収する|長音と品詞と読み仮名
分割さえ正しければ日本語検索が成立するかというと、そうはなりません。同じ語が違う書き方で入力されるからです。ここを埋めるのがtoken_filterの役目になります。
カタカナ末尾の長音記号のゆれはkuromoji_stemmerが吸収する
「サーバー」と「サーバ」、「コンピューター」と「コンピュータ」。日本語のカタカナ語では末尾の長音記号を書くか書かないかが揺れます。公式リファレンスによればkuromoji_stemmerは、末尾が長音記号で終わるカタカナ語の一般的な表記ゆれを、その文字(U+30FC)を取り除くことで正規化するフィルタです。
ただしminimum_lengthという既定値4のパラメータがあり、これより短いカタカナ語は処理されません。「コピー」のような3文字の語は末尾の長音がそのまま残ります。短い語を無条件に削ると別語になってしまうための安全弁で、この既定を下げると副作用が出やすくなります。短いカタカナ語の表記ゆれは、フィルタではなく同義語辞書で個別に扱うほうが事故は少ないでしょう。
kuromoji_part_of_speechは、指定した品詞タグの語を索引から落とすフィルタです。パラメータ名はstoptagsで、公式リファレンスによれば既定値はLuceneのkuromoji解析jarに同梱されたstoptags.txtの内容になります。助詞や助動詞のように、単独では検索の手がかりにならない語がここで消えます。
設定例に挙がっている「助詞-格助詞-一般」のような階層表記で個別指定もできますが、既定を上書きすると落とす範囲が指定分だけに変わります。部分的に足したいつもりで全部を入れ替えてしまう事故が起きやすい箇所です。
読み仮名で引きたいならkuromoji_readingformを重ねる
漢字表記の語をカタカナやローマ字の読みで引きたい要件ではkuromoji_readingformを使います。公式リファレンスの定義は、語をカタカナまたはローマ字の読み形へ置き換えるフィルタというものです。use_romajiで出力形式を切り替えますが、既定値は組み込み定義でtrue、独自に定義したフィルタでfalseと食い違う点に注意が要ります。
このフィルタは元の表記を読みへ置き換えてしまうため、通常の検索フィールドへ直接重ねると漢字での検索が効かなくなります。multi-fieldで読み専用のサブフィールドを別に持たせ、検索時に両方を対象にする組み方が現実的です。フィルタ単体の詳細な挙動はkuromoji_readingformとは何か?OpenSearchでの役割と概要をわかりやすく解説で扱っています。
ユーザー辞書で固有名詞を守る|CSV4列の書式と再索引の要否
形態素解析は辞書に基づいて語を切るため、辞書にない語は正しく切れません。社名や製品名、社内用語といった固有名詞は既定辞書に載っておらず、意図しない位置で分断されます。ここを埋めるのがユーザー辞書です。
ユーザー辞書の行は表記と分割と読みと品詞タグの4列で書き分ける
公式リファレンスが示す辞書の行書式は、表記・分割後の語の並び・読みの並び・品詞タグをカンマで区切った4列です。2列目と3列目の内部は半角空白で区切ります。公式の例は次の形になっています。
東京スカイツリー,東京 スカイツリー,トウキョウ スカイツリー,カスタム名詞
指定方法は2通りあります。user_dictionaryは設定ディレクトリ配下へ置いたCSVファイルのパスを指定する形で、user_dictionary_rulesはインデックス設定の中へ行を直接書き並べる形です。行数が少なく変更頻度も低いならインラインで足りますが、辞書が数百行規模になるならファイル管理へ寄せたほうが差分を追えます。ただしファイル方式は全ノードの同じパスへ配布する必要があり、配布漏れがそのまま解析結果の不一致になる点は、プラグイン導入時と同じ注意が要ります。
ユーザー辞書を変えても索引済みの既存文書は書き換わらないまま残る
ここが運用で最も事故になる箇所です。ユーザー辞書は索引を作る瞬間に適用されるため、辞書を足しても既に索引済みの文書には遡って反映されません。新しく投入した文書だけが新しい辞書で切られ、古い文書は古い分割のまま残ります。同じ語で検索したのに投入時期によって当たったり当たらなかったりする、という再現の難しい不具合の正体はたいていこれです。
辞書を変えたら、対象インデックスを閉じて開き直したうえで再索引を回します。運用中のシステムでは、エイリアス切り替えで新旧のインデックスを入れ替える形にしておくと、再索引のたびに検索を止めずに済みます。辞書更新が月次で発生する案件なら、この切り替えを最初から仕組みへ入れておくべきです。
形態素解析とN-gramの併用|取りこぼしと誤ヒットの交換条件
kuromojiを入れても、辞書に載らない新語や固有名詞は正しく切れません。一方で文字数で機械的に切るN-gramなら辞書に依存せず、どんな文字列でも部分一致で拾えます。両者は優劣ではなく交換条件の関係にあります。
| 観点 | 形態素解析(kuromoji) | N-gram |
|---|---|---|
| 切り方の根拠 | 辞書と品詞 | 文字数のみ |
| 未知語の扱い | 取りこぼす | 拾える |
| 誤ヒット | 少ない | 起きやすい |
| 索引サイズ | 小さい | 大きくなる |
| 運用の手間 | 辞書の保守が要る | 保守は不要 |
取りこぼしと誤ヒットのどちらを許容できないかで選択は機械的に決まる
判断基準を言い切ります。検索結果に無関係な文書が混じることを利用者が許容できない業務検索なら、形態素解析だけで組んでください。逆に、探している文書が出てこないことのほうが問題になるサイト内検索や商品検索なら、N-gramを併用します。両方を許容できないという要件は成立しないため、どちらを主にするかを先に合意しておく必要があります。
N-gram側を足す場合、index.max_ngram_diffという設定の既定値が1である点に注意してください。min_gramとmax_gramの差がこれを超える定義はエラーになります。2文字と3文字の両方を索引したいといった要件では、この設定を明示的に引き上げる必要が出てきます。
multi-fieldで一つの項目に二つのanalyzerを割り当てる
併用の実装はmulti-fieldが素直です。本文フィールドの本体にkuromojiのanalyzerを割り当て、サブフィールドにN-gramのanalyzerを割り当てます。投入側は今までどおり一つの値を送るだけで、Elasticsearchが両方の索引を作ってくれます。
"title": {
"type": "text",
"analyzer": "ja_text",
"fields": {
"ngram": {
"type": "text",
"analyzer": "ja_ngram"
}
}
}
検索側はboolのshouldで両方のフィールドを並べ、形態素側のスコアを高く重み付けしてください。こうすると、辞書で正しく切れた一致が上位に来て、N-gram側でしか拾えなかった一致が下位に続きます。クエリ本体の組み立て方はElasticsearchのクエリDSL|queryとfilterの分岐、boolの組み立てと深いページングで扱っています。
索引時と検索時でanalyzerを分ける|再索引が要る変更の境界
マッピングではanalyzerとsearch_analyzerを別々に指定できます。前者が文書を索引へ入れるときの処理、後者が検索語を解析するときの処理です。指定しなければ検索時も索引時と同じanalyzerが使われます。
同義語辞書を検索側へ置けば再索引なしで変更を反映できる仕組み
この分離が効くのは同義語の扱いです。同義語辞書を索引側へ入れると、辞書を変えるたびに全文書の再索引が必要になります。検索側へ入れれば検索語のほうを展開する形になるため、辞書の変更が再索引なしで反映されます。
ただし索引側と検索側で分割規則が食い違うと、一致するはずの語が一致しなくなります。tokenizerのmodeや辞書のような分割そのものを決める設定は、両側で揃えるのが原則です。分けてよいのは、検索語の側だけを広げる方向の加工に限られます。
整理すると、tokenizerの変更・ユーザー辞書の変更・索引側token_filterの変更は再索引が必要です。検索側の同義語追加とクエリの重み付け変更なら再索引は要りません。この線引きを設計段階で押さえておくと、運用が始まってから辞書を1語足すたびに数時間の再索引が走る、という事態を避けられます。
採用条件と見送り場面|kuromoji単体で足りる要件の境界
ここまでを踏まえて、判断を言い切ります。
kuromojiのanalyzer設計に工数をかける価値があるのは、検索対象の文書が日本語の自然文で、かつ検索結果の順位に利用者が納得を求める場面です。社内文書検索、記事検索、問い合わせ履歴の検索がこれに当たります。この条件では、標準のkuromojiアナライザから始めて、実際の検索ログを見ながら辞書とtoken_filterを足していく進め方が確実です。最初から作り込まず、_analyzeで切れ方を見て直す反復に時間を配分してください。
逆に見送ってよい場面もはっきりしています。検索対象が商品コードや型番のような識別子中心なら、形態素解析は不要でkeyword型と前方一致だけで足りるでしょう。文書数が数千件程度で、かつ全件を返して絞り込む使い方なら、analyzerの作り込みより絞り込み条件の設計に時間を使うほうが効果が出ます。日本語の文書があるという理由だけでkuromojiを入れるのは、保守すべき辞書を増やすだけの結果になりかねません。
もう一つの境界が、意味での検索を求められているかどうかです。「解約したい」で「退会手続き」の文書を出したいという要件は、表記ゆれの吸収では届きません。これは語の一致ではなく意味の近さを扱う領域で、ベクトル検索の併用が要ります。kuromojiの設定をいくら詰めても解けない要件なので、早い段階で切り分けておくべきです。dense_vectorとkNN検索の設定はElasticsearchのベクトル検索にまとめています。
日本語検索の要件を持ったまま、analyzer設計と辞書運用をどこまで自前で抱えるか判断に迷う場合は、データ分析基盤構築・MLOps構築支援で検索基盤の設計から運用体制まで相談を受け付けています。取りこぼしと誤ヒットのどちらを許容するかという線引きは、要件定義の段階で決めておくほど手戻りが減る箇所です。
よくある質問
kuromojiはElasticsearchに最初から入っていますか?
入っていません。analysis-kuromojiという別プラグインを追加し、クラスタの全ノードへ入れたうえでノードを再起動します。マネージドサービスでは最初から使える構成の場合もあるため、環境ごとに確認してください。
modeはnormalとsearchのどちらを選べばよいですか?
日本語の全文検索フィールドではsearchを選びます。複合語を分解するため、部分的な語での検索が通るようになるからです。あわせてdiscard_compound_tokenも明示してください。既定はfalseで、指定しなければ元の複合語まで索引されます。
ユーザー辞書を更新したら再索引は必要ですか?
必要です。辞書は文書を索引へ入れる瞬間に適用されるため、更新前に投入済みの文書へは反映されません。インデックスを閉じて開き直し、再索引を回してください。運用中はエイリアスで新旧を切り替える構成が扱いやすいでしょう。
「サーバー」と「サーバ」は自動で同じ扱いになりますか?
kuromoji_stemmerを入れていれば同じ扱いになります。末尾の長音記号を取り除いて正規化するフィルタだからです。ただしminimum_lengthの既定が4のため、3文字以下のカタカナ語は対象外になります。短い語のゆれは同義語辞書で個別に扱ってください。
N-gramと形態素解析はどちらか一方に決めるべきですか?
片方に決める必要はなく、multi-fieldで両方を持たせる構成が現実的です。誤ヒットを避けたい業務検索は形態素解析を主に、取りこぼしを避けたいサイト内検索はN-gramを併用します。検索時はboolのshouldで両フィールドを並べ、形態素側の重みを高くしてください。