パフォーマンスガイド
このガイドでは、MygramDB のベンチマーク結果、性能を見るときの前提、最適化の考え方、本番環境でのサイジングを説明します。
ベンチマーク結果
ベンチマーク表、グラフ、計測条件、メモリ使用量は、ベンチマークと同じ計測結果スナップショットから表示しています。このスナップショットでは verify_text: all を使用し、MygramDBのクエリキャッシュを無効にしています。
計測条件
1,100,000件のWikipedia記事、MySQL 8.4.10 FULLTEXT(ngram パーサー)、MygramDB v1.9.0、verify_text: all、クエリキャッシュ無効。レイテンシは10回計測のp50です。
検索レイテンシ(SORT id LIMIT 100)
検索レイテンシ(SORT id LIMIT 100) (p50, log scale)
| クエリタイプ | マッチ数 | MySQL | MygramDB | 高速化 |
|---|---|---|---|---|
| Multi-word ("quantum physics") | 104 | 2928.44ms | 15.19ms | 193x |
| Medium-freq ("quantum") | 1,961 | 2072.03ms | 62.75ms | 33x |
| Low-freq ("algorithm") | 2,498 | 375.14ms | 12.78ms | 29x |
| Rare term ("fibonacci") | 84 | 1172.29ms | 53.66ms | 22x |
CJK検索レイテンシ(SORT id LIMIT 100)
CJK検索レイテンシ(SORT id LIMIT 100) (p50, log scale)
| クエリ | マッチ数 | MySQL | MygramDB | 高速化 |
|---|---|---|---|---|
| 日本 | 32,282 | 917.03ms | 20.64ms | 44x |
| 東京 | 6,989 | 201.51ms | 4.39ms | 46x |
| 科学 | 1,551 | 3.35ms | 2.49ms | 1x |
COUNT性能
COUNT性能 (p50, log scale)
| クエリタイプ | カウント | MySQL | MygramDB | 高速化 |
|---|---|---|---|---|
| Medium-freq ("quantum") | 1,961 | 2000.08ms | 82.76ms | 24x |
| Low-freq ("algorithm") | 2,498 | 469.95ms | 14.06ms | 33x |
結果一致性
| クエリ | MySQL | MygramDB | 一致 |
|---|---|---|---|
| quantum | 1,961 | 1,961 | 一致 |
| algorithm | 2,498 | 2,498 | 一致 |
| 日本 | 32,282 | 32,282 | 一致 |
| 科学 | 1,551 | 1,551 | 一致 |
並列スループット
並列スループット — QPS
クエリ: "algorithm"、接続数ごとに10秒。
| 接続数 | MySQL QPS | MygramDB QPS | MySQL p50 | MygramDB p50 |
|---|---|---|---|---|
| 1 | 2.51 | 83 | 399.71ms | 11.86ms |
| 4 | 7.09 | 245 | 569.23ms | 16.45ms |
メモリ使用量
| ドキュメント数 | インデックス | ドキュメント+テキスト | RSS合計 | 100万件あたり |
|---|---|---|---|---|
| 1,100,000 | 152MB | 1.78GB | 3.41GB | ~3.1GB |
Docker Desktopには32 GiBを割り当て、コンテナからは31.29 GiBが利用可能でした。
パフォーマンス分析
MySQL が遅くなりやすい理由
- ディスクベースのB-tree: FULLTEXT インデックスは各クエリでディスクI/Oが必要
- 圧縮なし: 転置インデックスが圧縮されておらず、より多くのディスク読み込みが必要
- キャッシュ依存: コールドとウォームキャッシュで2-3倍のパフォーマンス差
- ソートのオーバーヘッド:
ORDER BYには追加の処理とI/Oが必要 - 高頻度語句: 短く一般的な語句は大きな転置リストのスキャンが必要
- 並行性のボトルネック: 高負荷な並行環境では、ディスクI/Oの待ち行列が伸びやすい
MygramDB が速い理由
- インメモリインデックス: 検索インデックスをRAM上に保持し、検索時のディスクI/Oを避ける
- 圧縮転置リスト: ハイブリッド Delta エンコーディング + Roaring ビットマップ
- 最適化された交差演算: SIMD アクセラレーション付きビットマップ演算
- ソート:
SORTはプライマリキーと設定済みフィルタカラムを検証してから結果を並べ替える - verify_text: 正確性が必要な場合に、ポストフィルタで偽陽性を除去できる。保持テキスト分のメモリは使う
- 対象を絞ったキャッシュ無効化: 行が変わったとき、その依存関係が影響を受けうるキャッシュエントリだけを調べる
パフォーマンス特性
クエリ時間計算量
| 操作 | MySQL FULLTEXT | MygramDB |
|---|---|---|
| 単一語句検索 | O(n log n) + ディスクI/O | O(n) インメモリ |
| AND 交差演算 | O(n * m) + ディスクI/O | O(n + m) SIMD付き |
SORT id | 比較ソート | 結果件数に応じた比較ソート(部分または完全) |
| COUNT | フルスキャン | ビットマップ基数 |
スケーラビリティ
MygramDB が線形にスケールするもの:
- 検索語句の数(効率的なビットマップ交差演算)
- 結果セットのサイズ(圧縮ビットマップ)
- 同時クエリ数(スレッドプールアーキテクチャ)
MygramDB がスケールしないもの:
- 利用可能なRAMを超えるデータセットサイズ(インメモリのみ)
現行の実行経路
大きなクエリやキャッシュの負荷で、広い走査や長いロック保持が起きないように、検索と保守は次の構造を使います。
- 候補フィルタはポスティングリストとソート済み候補リストをそれぞれ1回だけ進めるため、処理量は調べた候補数とポスティング数に比例します。
- 候補テキストは上限付きのチャンクで取り出します。BM25スコアリング、出現頻度の集計、テキストのポストフィルタを始める前にDocumentStoreのロックを解放します。
- 重複を除いた term info の参照結果を、論理式の評価、NOTの除外、同義語の展開、照合で共有します。NOTを含まない式では、全DocIDを走査しません。
- フィルタカラム名はストアごとに1回だけインターンし、ドキュメントごとの値はそのカラム ID をキーにしたコンパクトなベクトルで持ちます。
- キャッシュ無効化には、N-gram、フィルタ、テキスト変化に敏感な依存関係ごとの逆引きインデックスがあります。LRU保守は1回の排他ロックでキャッシュ全体を走査せず、各 tick で上限付きの範囲から再開します。
- テーブルごとの統計は上限付きスナップショットへ集約し、
INFO、/metrics、Prometheusへ返します。スクレイプのたびに全テーブルをロックしません。
最適化のヒント
1. 適切な ngram_size を選択
tables:
- name: "articles"
ngram_size: 2 # ASCII/英数字: バイグラム(推奨)
kanji_ngram_size: 1 # CJK文字: ユニグラム(推奨)推奨事項:
- バイグラム (2) ASCII/英語用: 精度とインデックスサイズのバランスが良い
- ユニグラム (1) CJK用: 各文字が意味を持つ
- トライグラム (3): より精密だがインデックスが大きく、クエリが遅い
2. メモリ設定
memory:
hard_limit_mb: 16384 # 予約済み / 現在は未強制
soft_target_mb: 8192 # 予約済み / 現在は未強制
roaring_threshold: 0.18 # Delta→Roaring 変換閾値推奨事項:
hard_limit_mbとsoft_target_mbは互換性のための予約フィールドとして 扱ってください。現時点ではプロセスメモリ上限を強制しませんroaring_thresholdはメモリが逼迫していない限りデフォルト(0.18)のまま
メモリ上限はOS側でも設定
MygramDBの hard_limit_mb は現時点で強制的なプロセスメモリ制限ではありません。Docker、systemd、Kubernetesなどで実行する場合は、コンテナやサービス側のメモリ制限と監視も設定してください。
3. フィルタで候補を絞る
tables:
- name: "articles"
filters:
- name: "status"
type: "int"
- name: "category_id"
type: "int"検索対象を早い段階で絞り込み、結果セットを小さくします。
SEARCH articles tech FILTER status=1 FILTER category_id=5 LIMIT 1004. クエリパターンの最適化
高速なクエリ:
SEARCH table term SORT id LIMIT 100- プライマリキーで並べ替えるCOUNT table term- ビットマップ基数演算SEARCH table term1 AND term2- 効率的なビットマップ交差演算
やや遅いクエリ:
SEARCH table term LIMIT 100- それでも高速だが、明示的にソートする場合より多くスキャンする可能性- 非常に大きな LIMIT 値(>1000) - 返すIDが多い
5. OPTIMIZE コマンドの使用
定期的に実行して転置リストのストレージを最適化:
OPTIMIZEこれにより、密度に基づいて Delta エンコーディングリストを Roaring ビットマップに変換し、メモリ使用量を10-30%削減します。
本番環境デプロイの推奨事項
1. メモリサイジング
経験則: verify_text の設定によって必要なRAMが大きく変わります。verify_text: off なら100万ドキュメントあたり約1GB前後、verify_text: all なら約2.3GBを目安にしてください。
サイジング例:
- 100万ドキュメント: 2GB RAM 最小、4GB 推奨(
verify_text: allの場合) - 1000万ドキュメント: 24GB RAM 最小、32GB以上推奨(
verify_text: allの場合) - 1億ドキュメント: 複数インスタンスへのシャーディングを検討
2. 高可用性セットアップ
ロードバランサーの背後に複数の MygramDB インスタンスをデプロイ:
3. モニタリング
INFO コマンドで主要メトリクスを監視します。
INFO見るべき主な項目:
doc_count: インデックスされたドキュメント数index_size: インデックスが使用するメモリtotal_requests: 処理された総クエリ数connections: 現在のアクティブ接続数uptime: サーバー稼働時間(秒)
4. バックアップ戦略
DUMP SAVE コマンドでスナップショットを作成:
DUMP SAVE /path/to/snapshot.dmp定期的なスナップショットをスケジュール:
# 毎日のスナップショット
0 2 * * * dump_date=$(date +\%Y\%m\%d); printf 'AUTH \%s\nDUMP SAVE /backup/mygramdb-\%s.dmp\n' "$MYGRAM_API_ADMIN_TOKEN" "$dump_date" | mygram-cliこの cron の例では AUTH と DUMP SAVE を1つの接続に送ります。api.admin_token が空の場合だけ AUTH は不要です。非ループバックのデプロイではトークンを設定してください。
トラブルシューティング
クエリが期待より遅い
インデックスが最適化されているか確認:
mygramOPTIMIZEメモリ使用量を確認:
mygramINFOindex_sizeとプロセスRSSを確認してください。hard_limit_mbは予約済みで、 現時点ではプロセスメモリ上限を強制しません。デバッグモードを有効化:
mygramDEBUG ON SEARCH table term LIMIT 100query_time、index_time、optimizationフィールドを確認。
高メモリ使用量
OPTIMIZE を実行:
mygramOPTIMIZE密な転置リストを Roaring ビットマップに変換(10-30%削減)。
roaring_threshold を調整:
yamlmemory: roaring_threshold: 0.15 # 低い = より積極的な圧縮シャーディングを検討: データを複数の MygramDB インスタンスに分割。
代替手段との比較
vs MySQL FULLTEXT
MygramDB の利点:
- キャッシュ無効の実測値は、このページ上部のベンチマークスナップショットに集約
- ディスクキャッシュの暖まり具合に左右されにくい
- COUNTを含む実測値はベンチマークスナップショットを参照
verify_textによりn-gramの偽陽性を除去できる- 読み取り中心の並列負荷でスループットを伸ばしやすい
MySQL の利点:
- 別の検索基盤が不要
- 既存の MySQL データで動作
- メモリ要件が低い
- マッチ数が少ない単純なクエリでは十分な速度
vs Elasticsearch
MygramDB の利点:
- デプロイ構成が小さい(単一バイナリ)
- 運用の複雑さが低い
- 直接 MySQL レプリケーション(ETL不要)
- シンプルなクエリでのレイテンシが低い
Elasticsearch の利点:
- ノード間の分散検索
- 高度な分析と集計
- 全文検索機能(ハイライト、ファジー検索)
- 単一ノードRAMに制限されない
ベンチマークの再現
付属のベンチマークスクリプトで結果を再現できます:
# ベンチマーク環境の起動(MySQL + MygramDB + データ投入)
make bench-up
# ベンチマーク実行
make bench-run独自データでベンチマークする場合は、サーバーを起動します:
./mygramdb -c config.yaml自動スナップショットを無効にしている場合は、同じ認証済み CLI 接続で同期を開始し、状態を確認してデバッグを有効にし、テストクエリを実行します:
$ mygram-cli
127.0.0.1:11016> AUTH <token>
OK AUTHENTICATED
127.0.0.1:11016> SYNC table
127.0.0.1:11016> SYNC STATUS
127.0.0.1:11016> DEBUG ON
127.0.0.1:11016> SEARCH table common_term LIMIT 100
127.0.0.1:11016> COUNT table common_termMySQL と比較します:
mysql -e "SELECT COUNT(*) FROM table WHERE MATCH(column) AGAINST('common_term')"
mysql -e "SELECT id FROM table WHERE MATCH(column) AGAINST('common_term') ORDER BY id LIMIT 100"まとめ
性能差はクエリ、データ、ハードウェア、キャッシュ設定で変わります。キャッシュ無効のスナップショットに載るレイテンシ、メモリ、並列スループットを、ここでの基準値として扱ってください。
verify_text: all を有効にした今回の計測では、MygramDBはMySQL FULLTEXTと同じ件数を返しました。CJKの低頻度語のようにMySQL側が短時間で完了するケースでは、差が小さくなります。
数百万件の読み取り中心ワークロードでは、付属ベンチマークを自分のデータで実行して評価してください: make bench-up && make bench-run。