⭐️

[PostgreSQL]PGroongaを利用した日本語全文検索 - Docker環境構築から性能比較まで

に公開
  • RDBの全文検索において、ElasticsearchやAlgoliaなどの専用サービスを利用することが多いですが、別途基盤を構築・運用する必要もあります。
  • PostgreSQL単体で実現したい場合、標準で全文検索機能(ts_vectorts_query)が備わっていますが、英語をメインとした設計のため、日本語のような単語の区切りがない言語では十分な検索精度が得られません。
  • 今回は、PostgreSQL拡張機能として利用できるPGroongaという日本語での全文検索エンジンをDockerでの構築から性能比較するまでを記録いたします。

環境

  • macOS 15.7.1
  • Docker version 28.3.2
  • Docker Compose version v2.39.1

手順

コンテナ設定

  • まず必要なディレクトリ及びファイルを作成します。
# 任意ディレクトリ作成及び移動
mkdir pgroonga-sample
cd pgroonga-sample

# 設定ファイル作成
touch compose.yaml Dockerfile .env
  • 次に以下の内容をcompose.yamlに記述します。
services:
  db:
    build:
      context: .
    ports:
      - "5430:5432"
    tty: true
    volumes:
      - db_data:/var/lib/postgresql
      - ./init_data/pg:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
  db_data:
  • 次に以下の内容をDockerfileに記述します。
FROM postgres:18.0-bookworm

WORKDIR /tmp

# PGroongaインストール
RUN apt-get update -yqq && \
    apt-get install -y -V --no-install-recommends \
    ca-certificates lsb-release wget && \
    wget --progress=dot:giga https://packages.groonga.org/debian/groonga-apt-source-latest-"$(lsb_release --codename --short)".deb && \
    apt-get install -y -V --no-install-recommends ./groonga-apt-source-latest-"$(lsb_release --codename --short)".deb && \
    rm -f ./groonga-apt-source-latest-*.deb && \
    apt-get update -yqq && \
    apt-get install -y -V --no-install-recommends postgresql-18-pgdg-pgroonga && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
  • 次に以下の環境変数情報を.envに記述します。
    • ※.envは秘匿情報のため、GitHub等にあげない
POSTGRES_USER=sample
POSTGRES_PASSWORD=sample
  • 次にコンテナ起動時にデータを流し込むため、以下で必要なディレクトリとファイルを作成します。
mkdir -p init_data/pg
touch init_data/pg/01_init.sql
  • init_data/pg/01_init.sqlに以下の内容を記述します。
-- PGroonga拡張機能を有効化
CREATE EXTENSION IF NOT EXISTS pgroonga;

-- booksテーブル作成(検索対象)
CREATE TABLE IF NOT EXISTS books (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  author VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- PGroonga全文検索インデックス追加
CREATE INDEX IF NOT EXISTS idx_books_title ON books USING pgroonga (title pgroonga_varchar_full_text_search_ops_v2);
CREATE INDEX IF NOT EXISTS idx_books_author ON books USING pgroonga (author pgroonga_varchar_full_text_search_ops_v2);

-- 10万件のテストデータ挿入
INSERT INTO books (title, author)
SELECT
  CASE
    WHEN i = 1 THEN '検索タイトル'
    WHEN i = 50000 THEN '検索タイトル'
    ELSE '通常タイトル'
  END,
  CASE
    WHEN i = 1 THEN '佐藤太郎'
    WHEN i = 50000 THEN '佐藤花子'
    ELSE '山田一郎'
  END
FROM generate_series(1, 100000) AS i;

コンテナ起動

  • 設定の記述後、以下でコンテナをバックグラウンドで起動します。
# 起動
docker compose up -d
  • 起動後、以下で確認します。
# テーブル確認
docker compose exec -T db psql -U sample -d sample -c "select * from books LIMIT 1;"

 id |    title     |  author  |         created_at         |         updated_at         
----+--------------+----------+----------------------------+----------------------------
  1 | 検索タイトル | 佐藤太郎 | 2025-10-26 05:42:34.549124 | 2025-10-26 05:42:34.549124

実行計画比較

  • 起動したので、単純な「通常検索」と「PGroonga検索」を比較していきます。
    • ※詳細な使用方法はこちらをご確認ください。
  • 以下でまず通常のLIKE検索の実行計画を確認します。
docker compose exec -T db psql -U sample -d sample -c "EXPLAIN ANALYZE SELECT * FROM books WHERE title LIKE '%検索%' OR author LIKE '%検索%';"



 Seq Scan on books  (cost=0.00..2531.00 rows=1 width=52) (actual time=0.009..19.311 rows=2.00 loops=1)
   Filter: (((title)::text ~~ '%検索%'::text) OR ((author)::text ~~ '%検索%'::text))
   Rows Removed by Filter: 99998
   Buffers: shared hit=1031
 Planning:
   Buffers: shared hit=114
 Planning Time: 75.761 ms
 Execution Time: 19.377 ms
  • 次にPGroongaの構文を利用した検索の実行計画を確認します。
docker compose exec -T db psql -U sample -d sample -c "EXPLAIN ANALYZE SELECT * FROM books WHERE title &@ '検索' OR author &@ '検索';"


 Bitmap Heap Scan on books  (cost=0.10..21.39 rows=200 width=52) (actual time=1.337..1.350 rows=2.00 loops=1)
   Recheck Cond: ((title &@ '検索'::character varying) OR (author &@ '検索'::character varying))
   Heap Blocks: exact=2
   Buffers: shared hit=2
   ->  BitmapOr  (cost=0.10..0.10 rows=4 width=0) (actual time=1.317..1.318 rows=0.00 loops=1)
         ->  Bitmap Index Scan on idx_books_title  (cost=0.00..0.00 rows=4 width=0) (actual time=0.970..0.970 rows=2.00 loops=1)
               Index Cond: (title &@ '検索'::character varying)
               Index Searches: 0
         ->  Bitmap Index Scan on idx_books_author  (cost=0.00..0.00 rows=1 width=0) (actual time=0.346..0.347 rows=0.00 loops=1)
               Index Cond: (author &@ '検索'::character varying)
               Index Searches: 0
 Planning:
   Buffers: shared hit=115
 Planning Time: 77.745 ms
 Execution Time: 1.580 ms
  • 通常検索と比べて、大幅に高速化されていることが確認できます。

まとめ

  • PGroongaを利用することで、PostgreSQL単体で高速な全文検索が実現できました。
  • PGroongaは形態素解析を利用した柔軟な検索が可能なため、検索精度と速度の両立が期待できます。
  • コンテナで簡単に環境構築できるので、アプリケーションでの手軽な導入や運用も可能と感じました。

参考

Discussion