Bleveが使えなくなったMattermost 11で日本語全文検索を復活させる
2025 年 10 月 16 日に Mattermost v11 がリリースされましたが、破壊的変更として以下が含まれています。
Breaking Changes
- Experimental Bleve Search functionality has been retired. If Bleve is enabled, search will not work until DisableDatabaseSearch is set to false. See more details in this forum post.
日本人が日本人向けに、普通にウェブ検索とかしながら Mattermost をセットアップした場合、たいていの場合 Bleve に依存していたと思うので、これは大問題です。…が、全文検索を復旧させることができたので共有します。
前提
こちらをベースに、Docker Compose + postgres + mattermost + nginx (HTTPS 用) ベースの運用をしています。小規模(ユーザ数十人)の運用であり、多少のダウンタイムは許容されるものとします。Docker ベースでない場合でも、基本方針については参考になると思います。
Postgres 17 で確認しています。多分 16 でも 18 でも動くんだろうとは思いますが未確認です。MySQL は Mattermost による対応が終了したので触れません。
方針決め
他の人が自分と同じ回り道をして時間を無駄にしないよう、検討したがダメと分かった方針も先に書いておきます(なんなら多分この記事で一番大事なのがここ)。
単に「PostgreSQL 内で日本語全文検索をする方法」を検索すると、新しめの技術から順に、以下のようなものが見つかると思います。
- ① pg_search (ParadeDB) + Lindera: Neon でも最近サポートされた新しめの技術。
- ② PGroonga: 現状では日本語検索のデファクトに近い技術。Supabase でも使えてありがたい。
- ③ pg_bigm (解説記事): bigram なので取りこぼしは少ないかも。
- ④ tsja: 個人プロジェクト? リポジトリが見つからない。
- ⑤ textsearch_ja: MeCab ベース。とても古い。
ですが結論を先に書くと、Mattermost で動作しそうなのは ④ と ⑤ だけです。検索クエリの書き方が、以下のように Go でハードコードされているからです。
この to_tsvector(...) @@ to_tsquery(...) というのは PostgreSQL 標準の全文検索の仕組みを用いたクエリの書き方です(ドキュメント)。英語も含む多くの言語では、利用言語も指定する以下のような SQL で、単なる LIKE '%keyword%' よりもマシな自然言語検索が行えるようになっています。例えば "satisfy" で検索すると "satisfied" にもマッチする、といった具合です(インデックスなしでも一応動作します)。
SELECT * FROM posts
WHERE to_tsvector('english', message) @@ to_tsquery('english', 'satisfy');
残念ながら PostgreSQL のデフォルトでは、スペースで分かち書きしない CJK 言語は考慮されていません。かつ、そもそも ① ~ ③ はこの仕組みを利用していません。それぞれ高度な検索機能を提供するなどの様々な事情のため、PostgreSQL 標準の全文検索のフレームワーク・構文には乗っからず、独自の SQL 記法で動作しています。
- ① pg_search は column @@@ '検索語'という独自の演算子を使用する。
- ② PGRoonga は column &@ '検索語'という独自の演算子を使用する(厳密には非推奨で@@も使えるが、いずれにせよクエリの書き方が異なる)。
- ③ pg_bgrm は column LIKE '%検索語%'で動作する。
- ④⑤ は Mattermost がハードコードしている @@演算子で動作する。
要するに、Mattermost では ① ~ ③ は現状利用できないということです。この結論に至るだけでかなりの時間を費やしました。
じゃあ ④⑤ を使うかー、となるところですが、困ったことにこれらはろくにメンテされていません。tsja は個人開発らしく公開リポジトリの存在すら不明ですし、textsearch_ja も 2009 年(16 年前!)に更新が止まっており、公式サイトごと行方不明です。
とはいえ textsearch_ja は、コード自体は Postgres 公式のアーカイブよりダウンロード可能であり持続性の点で多少マシと思われますし、実際オンラインでも PostgreSQL 17 と組み合わせて動作したという報告が複数あります。tar.gz を解凍すると中にそれなりの分量のドキュメントもあります。というわけで今回は ⑤ の方針で行くしかない、という判断になりました。
実際のアップグレード手順
方針さえ決まってしまえば、あとは似たようなオンライン記事もありますが、自分がうまくいった方法を書いておきます。
Dockerfile の作成
PostgreSQL の公式 Docker イメージに textsearch_ja を組み込んだカスタムイメージをビルドします。
FROM postgres:17 AS builder
RUN apt-get update && apt-get install -y \
    build-essential \
    wget \
    ca-certificates \
    mecab \
    libmecab-dev \
    mecab-ipadic-utf8 \
    postgresql-server-dev-17 \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /tmp
RUN wget https://ftp.postgresql.org/pub/projects/pgFoundry/textsearch-ja/textsearch_ja/9.0.0/textsearch_ja-9.0.0.tar.gz \
    && tar xzf textsearch_ja-9.0.0.tar.gz \
    && cd textsearch_ja-9.0.0 \
    && make USE_PGXS=1 \
    && make USE_PGXS=1 install \
    && sed -i "s/LANGUAGE 'C'/LANGUAGE C/g" textsearch_ja.sql
FROM postgres:17
RUN apt-get update && apt-get install -y \
    mecab \
    libmecab2 \
    mecab-ipadic-utf8 \
    && rm -rf /var/lib/apt/lists/*
COPY  /usr/lib/postgresql/17/lib/textsearch_ja.so /usr/lib/postgresql/17/lib/
COPY  /usr/share/postgresql/17/extension/textsearch_ja* /usr/share/postgresql/17/extension/
COPY  /tmp/textsearch_ja-9.0.0/textsearch_ja.sql /docker-entrypoint-initdb.d/01-textsearch_ja.sql
ビルドとテスト
Docker Compose で、新しいデータディレクトリで DB 環境を準備します。
services:
  postgres-ja:
    build:
      context: ./postgres-ja # ここに上述の Dockerfile を置く
    image: postgres-ja:17
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      PGUSER: ${POSTGRES_USER}
      PGDATABASE: ${POSTGRES_DB}
    volumes:
      - pgdata_ja:/var/lib/postgresql/data
    restart: unless-stopped
volumes:
  pgdata_ja: # 最初は空ディレクトリで始めること
# イメージをビルド
docker compose build postgres-ja
# バックグラウンドで起動
docker compose up -d postgres-ja
# ログを確認(初期化スクリプトが実行されていることを確認)
docker compose logs -n200 -f postgres-ja
# psqlで接続
docker compose exec postgres-ja psql
psql コンソール内で以下を実行して、一回切断します。
-- デフォルトの検索言語を日本語に設定
ALTER DATABASE mattermost SET default_text_search_config = 'pg_catalog.japanese';
-- \q で一旦終了して再接続
psql コンソールに再接続して、日本語関連機能が無事有効化されていることを確認します。
-- さっきの設定が適用されたことを確認
-- これがMattermostが検索に使う言語になる
SHOW default_text_search_config;
-- 結果: pg_catalog.japanese
-- MeCabによる分かち書きの動作確認
SELECT ja_wakachi('日本語のテキストを分割します');
-- 結果: 日本語 の テキスト を 分割 し ます
-- to_tsvectorの動作確認
SELECT to_tsvector('english', '日本語のテキストを分割します');
-- 結果: '日本語のテキストを分割します':1
SELECT to_tsvector('日本語のテキストを分割します');
-- 結果: 'する':4 'テキスト':2 '分割':3 '日本語':1
-- ちなみに裏でMeCabが動いているのでこんなことも可能
SELECT furigana('いい話だなー');
-- 結果: イイハナシダナー
次に既存の Mattermost データをインポートしたあと、日本語によるインデックスを作成します。mattermost-dump.sql ファイルは既存の PostgreSQL から pg_dump でダンプしたものです(バックアップをとるときと同じ手順)。
# ダンプファイルからインポート
cat mattermost-dump.sql | docker compose exec -T postgres-ja psql
# あらためてpsqlで接続
docker compose exec postgres-ja psql
psql で以下のような感じでインデックスの動作確認と作成を行います。
-- ひとまず既存の英語インデックスでは検索がうまく動作しないことを確認
-- 検索単語は適当に調整のこと
SELECT count(*) FROM posts
WHERE to_tsvector('english', message) @@ to_tsquery('english', '日本語');
-- 偶然分かち書きされている部分を除きあまり検索がヒットしないはず。
-- ここからこれを修正する。
-- 日本語設定では正しく検索できることを確認
SELECT count(*) FROM posts
WHERE to_tsvector('japanese', message) @@ to_tsquery('japanese', '日本語');
-- 適切にヒットするが、インデックスがないためまだ遅いはず。
-- 仮の名前で日本語版GINインデックスを作成
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_posts_message_txt_japanese
ON posts USING gin(to_tsvector('japanese', message));
-- インデックスが使われることを確認
EXPLAIN ANALYZE
SELECT * FROM posts
WHERE to_tsvector('japanese', message) @@ to_tsquery('japanese', '日本語');
-- 実行計画に "Bitmap Index Scan on idx_posts_message_txt_japanese" が表示されるはず
-- もういちどさっきの検索をやってみる
SELECT count(*) FROM posts
WHERE to_tsvector('japanese', message) @@ to_tsquery('japanese', '日本語');
-- 超高速になっているはず。
-- 重要:既存の英語インデックスを削除し、日本語インデックスをリネーム
DROP INDEX CONCURRENTLY IF EXISTS idx_posts_message_txt;
ALTER INDEX idx_posts_message_txt_japanese RENAME TO idx_posts_message_txt;
最後のインデックスのリネームですが、こちらで紹介されている公式の手順です。マイグレーションでの不整合などで将来の問題を起こさないため、確実にやっておきましょう。
ここまで OK なら、後は仕上げ作業です。
- Docker Compose 上で新しい DB を使うようにもろもろ設定を書き換え、コンテナを再起動。
- まだ v10 以前を使用している場合は、Mattermost のシステムコンソールで Bleve の利用をオフにする(v11 にすればこの設定自体が消える)。
- システムコンソールで、"Disable database search" というオプションが True になっている場合は、これを False にする。
- 安定してきたら Bleve 関連のインデックスファイルや設定項目を削除する。
検索性能は?
気になる検索精度について、ちゃんと統計はとっていませんが、体感ではむしろ向上しています。Bleve を使用しているとここで説明されているような過剰なヒット (false positive) が多発していたのですが、面倒なので放置していました。
今回の対策を行ったことでこれが消え、真にその単語が含まれるメッセージだけが検索でひっかかるようになりました。
終わりに
一旦これで動作していますが、textsearch_ja がいくらなんでも古いことが気になるので、Mattermost が pg_search 対応になるといった未来がくるといいなと思います。


Discussion