Skip to content
Pepin
コラム2026-09-27約11分で読めます
ソロプレナー個人開発まるっと検索

Rustで日本語の検索エンジンを1人で自作した話

書いた人 個人開発者 / ソロプレナー

Rustで日本語の検索エンジンを1人で自作した話

Shopify アプリ「まるっと検索」の中で動いているのは、わたしが Rust で1人で自作した日本語の検索エンジンです。Algolia や Meilisearch、Tantivy といった既存の検索エンジンも検討しましたが、費用、日本語の揺れへの弱さ、中身を自分で調整したいこと、運用を増やしたくないこと、の4つの理由で使いませんでした。形態素解析には lindera を使い、ひらがなとカタカナ、全角と半角、旧字体、ローマ字といった揺れの扱いを、96件のゴールデンテストで守っています。速さは、1,000商品で p95 が 62.2ms です。1万商品では 854.5ms かかり、目標の 500ms を超えています。

わたしは2026年1月に開業して、3月から専業で Shopify アプリをつくっています。これまでに6本つくり、いま提供しているのは4本です。やめた2本のことは 個人開発の失敗の記録 に書きました。まるっと検索はその4本のうちの1本で、最初のコミットが2026年3月10日、App Store での公開が4月25日です。

ここに書くのは、専業7か月目、2026年9月時点の話です。インストール数や売上には触れません。検索エンジンをどう組み立てて、どう確かめているのか、その中身だけを書いておきます。例に出す検索語は、どれもゴールデンテストに書いてある入力と、そのテストが「見つかること」を求めている商品名を、そのまま写したものです。

96件
ゴールデンテスト
日本語の検索結果を確かめるテスト
27,919行
検索エンジン本体のコード
Rust、2026年9月27日時点
20,429行
テストのコード
同じく2026年9月27日時点

日本語の検索エンジンを自作したのはなぜ?

まるっと検索をつくる前に、既存の検索エンジンを使う道も考えました。名前を挙げると、Algolia、Meilisearch、Tantivy などです。それでも自作を選んだ理由は、次の4つです。

  1. 01

    費用

    まず、費用の面で、まるっと検索には合わないと判断しました。

  2. 02

    日本語の揺れに弱い

    わたしがこだわりたかったのは、ひらがなとカタカナ、全角と半角、旧字体のような日本語の書き方の揺れを、同じ語として扱えるかどうかでした。検討した道具は、そこが弱いと判断しました。

  3. 03

    中身を自分で調整したい

    どの検索語で何を出して、何を出さないかという線引きを、自分の手で細かく決めたいと考えました。このあと書く「鮭」で「酒」を出さない仕組みは、そうやって自分で足したものです。

  4. 04

    運用を増やしたくない

    1人でやっているので、面倒を見る相手を増やしたくありませんでした。別のサービスを1つ足すと、契約や設定、つながり方を気にする場所も1つ増えます。

ここに書いたのは、まるっと検索に求めた条件で見たときの、わたしの判断です。どの道具が優れているかを比べたものではありません。

Shopify の標準の日本語検索がどう動いているかは、Shopifyの日本語検索はなぜ弱い? にまとめています。この記事は、検索エンジンそのものを自分でつくった側の話です。

なぜ Rust で書いたの?

理由は2つです。

1つ目は、速度とメモリ効率です。検索は、お客さんが結果を待っているあいだに返す処理です。1回ごとの速さと、使うメモリの少なさを重く見ました。

2つ目は、日本語の解析ライブラリが揃っていたことです。最初の仕様書で使う予定だった Sudachi.rs も、いま使っている lindera も、Rust で書かれた形態素解析のライブラリです。

出典:lindera(GitHub)

出典:Sudachi.rs(GitHub)

ただ、Rust で書いたからどの規模でも速い、というわけではありません。1万商品での結果は、このあと正直に書きます。

形態素解析は、なぜ Sudachi.rs から lindera に替えたの?

形態素解析

文章を、意味を持ついちばん小さな単位(形態素)に区切って、それぞれの原形や読みを調べる処理です。たとえば「ランニングシューズ」は「ランニング」と「シューズ」に区切られます。日本語は語と語のあいだに空白を入れないので、日本語の検索エンジンはまずここから始めます。

2026年3月10日に書いた最初の技術仕様書では、形態素解析に Sudachi.rs を使う予定でした。ところが2日後の3月12日、検索エンジンの中心部分を書いたときには、lindera に替えています。辞書は IPADIC を、プログラムの中に埋め込んで使っています。

替えた理由は2つです。1つは、辞書を埋め込めるので、配るのが楽だったことです。辞書のファイルを別に置いて読み込ませる手間がありません。もう1つは、導入とビルドが簡単だったことです。

IPADIC の辞書は、検索にとって素直なことばかりではありません。たとえば、かなで書いた「なし」を、動詞「ない」の活用として読むことがあります。このクセとのつき合い方は、下の「見つけすぎないために、何をしている?」で書きます。

日本語の揺れは、どう吸収しているの?

検索窓に語を打ち込んで商品を探すイメージ

考え方はシンプルで、検索語と商品の両方を同じ手順で崩し、別の書き方の候補を増やしてから突き合わせます。商品を登録するときに、商品名や説明から語と別の書き方を控えておきます。検索語が来たら、同じ手順で候補をつくって照らし合わせます。

検索語が入ってから結果が並ぶまでを、順番に書くとこうなります。

  1. 01

    文字の形をそろえる

    Unicode の NFKC という正規化で、全角の英数字や半角カタカナの形をそろえます。英字は小文字にして、全角スペースは半角にします。

  2. 02

    lindera で語に切る

    形態素解析で、検索語を語に分けます。「-」のように文字も数字も含まない記号は、語として扱いません。

  3. 03

    かなの語は、打たれた形のまま使う

    辞書は、かなだけの語を動詞の活用として読むことがあります。かなの語については辞書の原形を使わず、打たれた形を信じます。

  4. 04

    別の書き方の候補を広げる

    ひらがなとカタカナ、漢字の読み、長音符を抜いた形、小書き文字の形、旧字体をまとめた形を足します。検索語の全体がローマ字なら、ひらがなに直した形も足します。

  5. 05

    突き合わせて、段に分けて並べる

    商品の側に控えておいた語と突き合わせて、候補を集めます。並べるときは先に段で分けて、その中を点数で並べます。同音異義語は、ここで落とします。

出典:UAX #15: Unicode Normalization Forms(Unicode)

揺れの種類ごとに、ゴールデンテストの例を並べます。「」の検索語で、その商品が見つかることをテストで確かめています。商品名は、どれもテストのためにつくったものです。

  • 全角と半角、大文字と小文字:NFKC で文字の形をそろえ、英字を小文字にします。「ABC」「abc」で「ABCヘッドフォン Pro」、「500」で「オレンジジュース 500ml」、「カタカナ」で「カタカナ商品」が見つかります。
  • ひらがなとカタカナ:互いに変換した形も登録します。「じゅーす」で「オレンジジュース 500ml」、「リンゴ」で「青森産りんごジュース 1L」が見つかります。
  • 漢字の読み:辞書の読みも登録します。「あおもり」「アオモリ」で「青森産りんごジュース 1L」、「とうきょう」で「東京限定チョコレート」が見つかります。
  • 小書き文字:22組の対応表で、大きい字で打たれた形にも届かせます。「ランニングシユーズ」で「軽量ランニングシューズ」、「クツシヨン」で「クッション 45cm」が見つかります。
  • 旧字体:16文字について、新しい字体にまとめた形も持ちます。「髙島屋」で「高島屋ギフトセット」、「高島屋」で「髙島屋ギフトセット」が見つかります。
  • 辞書にない語:2〜8文字の部分文字列も控えておきます。「ワイヤレス」で「ワイヤレスイヤフォン」が見つかります。
  • ローマ字:199組の対応表で、ひらがなに直します。「ringo」で「青森産りんごジュース 1L」、「ko-hi-」で「コーヒーメーカー」、「fasshon」で「ファッション雑貨」が見つかります。
  • 品番・SKU・バーコード:語に切らずに登録し、完全一致を最上位に置きます。途中までなら前方一致で拾います。「WD650DR」「WD650DR」「4901234567894」で、SKU が WD650DR の「ロングドレス ネイビー」が1位に来ます。「wd650」でも同じ商品が見つかります。

ここに並べたのは、ゴールデンテストの例をそのまま示せるものだけです。このほかに、長音符(ー)を抜いた形も登録しています。読みを続けて打つ人のために、フレーズ全体をつなげた読みも登録しています。

いくつか、補足しておきます。

小書き文字は、「ッ」や「ュ」を大きく打ってしまう入力のための工夫です。1語なら、商品の側に大きい字の形も控えておけば届きます。「ランニングシユーズ」のような複合語は、そのままだと辞書で切れない1語になってしまいます。そこで検索語の側で小さい字に戻す候補をつくり、辞書で「ランニング」と「シューズ」に切れる形を探してから検索します。

旧字体は、検索のために新しい字体にまとめた形を足しているだけで、商品名の文字そのものは書き換えていません。検索窓の候補として表示する語も、書かれたとおりに残しています。人の名前の「髙」を勝手に「高」に直すと、名前の持ち主には誤字に見えてしまうからです。

ローマ字は、検索語の全部の語がローマ字として読めたときだけ、ひらがなに直します。「aomori ringo」なら2語ともローマ字なので、「青森産りんごジュース 1L」が見つかり、「オレンジジュース」は出ません。数字の混ざった「ringo123」では変換しないので、「りんごジュース」は出ません。全部の語がそろったときだけにしているのは、英語の単語の中にも、ローマ字として読めてしまうもの(tote なら「とて」)があるからです。語ごとにばらばらに直すと、英語の商品名で探す人の検索に、関係のないかなが混ざってしまいます。なお、「-」は長音として読むので、「ko-hi-」は「こーひー」になります。

品番や SKU は、どう扱っている?

品番や SKU、バーコードは、ふつうの言葉とは別に扱っています。言葉と同じように手を加えると、ひとつの品番が別の品番に変わってしまうおそれがあるからです。SKU とバーコードは、文字の形をそろえて小文字にするだけで、そのまま登録します。検索語の全体が SKU やバーコードと一致した商品は、ほかの商品より先に、いちばん上に置きます。

全角で打った「WD650DR」でも、NFKC で形をそろえるので、SKU が WD650DR の商品が1位に来ます。

途中まで打った品番は、前方一致で拾います。テストでは、「wd650」で SKU が WD650DR の「ロングドレス ネイビー」が見つかり、説明文に「wd650 シリーズ」と書いてあるだけのカタログより上に来ることを確かめています。

Shopify の標準の検索で品番がどう扱われるかは、Shopifyで品番・SKUが検索できない理由と対処 にまとめています。

見つけすぎないために、何をしている?

揺れを吸収するほど、関係のない商品まで拾いやすくなります。そこで、候補を足すのと同じくらい、落とす仕組みにも手をかけています。3つ書いておきます。

1つ目は、かなの語の原形です。辞書の IPADIC は、かなだけの語を動詞の活用とみなすことがあります。「なし」を「ない」、「あん」を「ある」、「とり」を「とる」として読む、といった具合です。その原形をカタカナにして前方一致で探すと、「ナイフ」「アルバム」「トルコ」のような外来語に当たってしまいます。そこで、かなの語は辞書の原形を使わず、打たれた形のまま探します。テストでは、「なし」で「梨 幸水」が見つかって「ナイフ ペティ」は出ないこと、「あん」で「こしあん 300g」が見つかって「アルバム 写真用」は出ないこと、「とり」で「とり肉 むね」が見つかって「トルコ ランプ」は出ないことを確かめています。

2つ目は、同音異義語です。漢字の検索語は、かなで書かれた商品にも届くように、読みでも探しています。「鞄」で「かばん 帆布」が見つかるのは、そのためです。ただ、それだけだと「鮭」で「酒」が、「箸」で「橋」が出てしまいます。そこで、読みだけで当たった商品のうち、その読みが商品のどこにも書かれていないものは、同音異義語として落としています。テストでは、「鮭」で「北海道産 鮭 切り身」が見つかって「純米大吟醸 酒」は出ないこと、「箸」で「若狭塗 箸」が見つかって「橋 模型キット」は出ないことを確かめています。ただし、表記どおりに当たる商品がお店に1つもないときは、読みで当たった商品を落としません。

3つ目は、並べる順番です。点数だけで並べると、説明文に同じ語をたくさん書いた商品が、商品名で当たった商品より上に来てしまうことがあります。そこで、先に段を決めます。いちばん上は、SKU やバーコードが検索語と一致した商品と、商品名が検索語そのものの商品です。次が商品名に検索語を含む商品で、その次がそれ以外の項目で当たった商品です。点数で並べるのは、同じ段の中だけです。テストでは、「りんご」で商品名が「りんご」の商品が「青森りんごジュース」より上に来ること、説明文に「りんご」を5回書いた「夏用トートバッグ」が「青森りんごジュース」を追い越さないことを確かめています。

検索の品質は、どうやって守っている?

守りの中心は、ゴールデンテストです。検索語と、その検索語で見つかるべき商品、あるいは見つかってはいけない商品を組にしたテストで、いまは96件あります。3月12日に検索エンジンの中心部分を書いた時点では、50件でした。テストのファイルの冒頭には、こう書いてあります。

テストを変えるな、実装を直せ。

原文は英語です。テストを通すためにテストを書き換えてはいけない、直すのは実装のほう、という決まりです。AI に渡している作業の決まり(CLAUDE.md)にも、「ゴールデンテストは検索の仕様そのもので、仕様を変えるときはわたしの承認が要る」「新しい揺れを見つけたら、先にテストを足してからコードを直す」と書いています。検索エンジンのコードを変えると、GitHub の CI で、ゴールデンテストを含む Rust のテストがすべて走ります。

もうひとつが、品質の約束です。テスト用の商品の一覧と検索語をファイルに固定して、日本語36件、英語42件の検索語で、狙った商品が何位に出るかを測ります。合格の線は、言語ごとに次のとおりです。

指標合格の線
1位に狙った商品が出る割合(Top1)80%以上
3位以内に狙った商品が出る割合(Top3)100%
見つかるはずの検索語で、0件になってしまう割合0%

どれか1つでも線を割れば、不合格です。

自作した日本語の検索エンジンは、どれくらい速い?

検索の速さを測るイメージ

商品数を変えて、検索の速さを測りました。p50 は、検索の半分がこの時間より速く返るという値です。p95 は、検索の95%がこの時間より速く返るという値で、遅いほうの検索がどれくらい待たせるかを見るのに使います。

商品数検索 p50検索 p95目標(p95 が 500ms 以下)
1,00025.4ms62.2ms満たしている
10,000351.7ms854.5ms満たしていない

計測の条件です。2026年8月9日に、手元の arm64 のマシンで、最適化してビルドした(release build の)検索エンジンを動かして測りました。商品は、日本語と英語の商品名・説明・タグ・SKU を持つ、合成したデータです。検索エンジンを直接呼んでいて、アプリ側のキャッシュは通していません。検索の回数は、それぞれ105回です。本番の環境で測った数字ではありません。

1,000商品なら、p95 でも 62.2ms で返ります。1万商品になると、p95 は 854.5ms です。目標にしている 500ms を超えていて、1万商品の規模では目標に届いていません。

届いていないところも含めて、いまの実力として置いておきます。

このコードは、AI がどれくらい書いたの?

わたしは、まるっと検索を AI と一緒に書いています。git の履歴を2026年9月27日に数えると、マージを除いたコミット671件のうち432件、64.4%に、AI(Claude)が共同作者として記録されていました。検索エンジンの部分を触ったコミットに絞ると、118件のうち89件、75.4%です。

64.4%
AI が共同作者のコミット
リポジトリ全体、671件中432件
75.4%
検索エンジンの部分では
118件中89件

ここでいう共同作者は、コミットに「Co-Authored-By: Claude」の記録が残っているもののことです。記録のないコミットでも AI を使ったものはありうるので、この割合は「少なくとも」の数字として読んでください。

AI が書いたコードも、わたしが書いたコードも、同じゴールデンテストと品質の約束のテストにかけています。AI と1人でアプリをつくるときの進め方は、AI と1人で Shopify アプリをつくるやり方 にまとめています。

よくある質問

最初のコミットは2026年3月10日で、Shopify の App Store に公開したのは4月25日です。公開のあとも手を入れ続けていて、ゴールデンテストは最初の50件から、いまは96件に増えました。

理由は4つです。費用がかかること、日本語の書き方の揺れに弱いと判断したこと、中身を自分で調整したかったこと、1人なので運用を増やしたくなかったことです。まるっと検索に求めた条件で見たときの判断で、道具の良し悪しを比べたものではありません。

lindera を使っていて、辞書は IPADIC をプログラムに埋め込んでいます。最初の仕様書では Sudachi.rs の予定でしたが、辞書を埋め込めて配るのが楽なこと、導入とビルドが簡単なことから、2026年3月12日に lindera に替えました。

全角と半角、大文字と小文字、ひらがなとカタカナ、漢字の読み、長音符の有無、小書き文字、旧字体16文字、辞書にない語、ローマ字です。品番・SKU・バーコードは言葉とは別に扱い、完全一致を最上位にして、途中までの入力は前方一致で拾います。

目標には届いていません。2026年8月9日に手元の arm64 のマシンで合成データを使って測ったところ、1,000商品では p95 が 62.2ms でしたが、1万商品では 854.5ms で、目標の 500ms を超えていました。本番の環境で測った数字ではありません。

2026年9月27日に数えたところ、マージを除いたコミット671件のうち432件、64.4%に AI が共同作者として記録されていました。検索エンジンの部分では118件のうち89件、75.4%です。記録のないコミットでも AI を使ったものはありうるので、少なくともこれだけ、という数字です。

→ まるっと検索の紹介ページを見る

→ ストアのサイト内検索を無料で診断する

→ 検索結果を JSON で受け取れる検索APIを見る

→ AI と1人で Shopify アプリをつくるやり方を読む

→ わたし(SHIN)について

この記事は、Shopify予約アプリ「まるっと予約」の開発者、大島慎平(SHIN)が書いています。

Share
大島慎平(SHIN)

この記事の執筆者

個人開発者 / ソロプレナー

日本のShopifyストア向けアプリ「まるっとシリーズ」を、AIのコーディングツールと一緒に1人で企画・開発・運営しています。書籍編集者からWebエンジニアに転じて約10年。YouTubeでは、1人で事業をつくる過程を話しています。

まずは、課題の共有から。

アプリの導入も、Shopifyそのものの相談も歓迎です。内容が固まっていなくても問題ありません。 通常2営業日以内に返信します。