ククログ

株式会社クリアコード > ククログ > PGroongaでGroongaのリソースを確認する(n_buffer_segmentsの値)

PGroongaでGroongaのリソースを確認する(n_buffer_segmentsの値)

PostgreSQLのautovacuumと格闘中の阿部です。

(という出だしで始めましたが本記事ではPostgreSQLのautovacuumについては触れないので、興味があれば公式ドキュメントをご覧ください。)

今回はPGroongaからGroongaのリソースを確認する方法を紹介します。

PGroongaはインデックスとしてGroongaを使うPostgreSQLの拡張機能です。 PostgreSQLにてお手軽にゼロETLで全言語対応の超高速全文検索機能を使えるようになりますが、より専門的なチューニングなどをする場合は内部で利用しているGroongaの状況も確認する必要があります。

今回はその一例としてGroongaのn_buffer_segmentsの値を確認する方法を紹介します。

はじめに

この記事はPGroongaでGroongaのリソースを確認するイメージをつかんでもらうことを目的に書いています。 本文中に登場するGroongaコマンドやPGroongaの関数について細かく説明しすぎると目的から外れてしまうので簡単に紹介するに留めます。

この記事では次のGroongaコマンドやPGroongaの関数が登場します。

以降は次の流れで説明します。

  1. Groongaの n_buffer_segments とは?
    • なぜ確認する必要があるのかも含めて説明
  2. Groongaのn_buffer_segmentsを取得
    • いくつか手順があるので実行例を示しながら説明します
  3. Groongaのn_buffer_segmentsをまとめて取得
    • 2の方法でn_buffer_segmentsは確認できるのですが、PGroongaのインデックスが大量にあると確認するのが大変なのでまとめて取得する例を紹介します

1. Groongaの n_buffer_segments とは?

ドキュメント によると

調査対象のインデックスカラム内の「buffer」に使っている最大セグメント数。 「buffer」はポスティングリストを保存する場所です。

というものらしいです。 何のことかわからないといますが、わからなくて大丈夫です。

だいたい何の値でもそうだと思いますが…、この値も上限値を超えるとGroongaにやばいことが起こる、ということだけ抑えておけば良いです。

PGroongaなり、Groongaなりをふつうに使っていれば基本的には上限値を超えることはないので、ふつうに使っている場合は気にしなくて良いです。

PGroongaをふつうに使ってないとき

ふつうに使ってないときは気にしたほうが良いです。 例えば何らかの理由でautovacuumは止めて運用している、などです。

autovacuumが動いているともろもろお掃除されるので、n_buffer_segmentsは一定の値からあまり動かないのですが、お掃除されないと増える一方で上限値を超えてしまう場合があります。

ということで、上限値を超える前に検知して手動でお掃除処理を実行するためにn_buffer_segmentsの値を監視する必要があります。

このあたりの運用の話題は本題からずれるので割愛しますが、詳しく知りたい場合はお問い合わせよりご連絡ください。

2. Groongaのn_buffer_segmentsを取得

次の3ステップでn_buffer_segmentsの値が取得できます。

  • 対象のPostgreSQLのインデックス名と、対象インデックスのカラム名を確認
  • PGroongaのインデックス名をGroongaのインデックスカラム名に変換
  • Groongaのインデックスカラム名を元にobject_inspectを使って、n_buffer_segmentsの値を取得

PGroongaのpgroonga_index_column_name関数のページにある「使い方」の内容のほぼ再掲になるのですが、補足説明を入れつつGroongaのn_buffer_segmentsを取得するまでの手順を具体的に説明していきます。

説明の準備: サンプルスキーマ

CREATE TABLE memos (
  id integer,
  title text,
  content text,
  tag text
);
CREATE INDEX pgroonga_index
ON memos
USING pgroonga (title, content, tag);

このテーブルとインデックスを例に説明します。

対象のPostgreSQLのインデックス名と、対象インデックスのカラム名を確認

pgroonga_indexインデックスのtagカラムを使って説明します。

PGroongaのインデックス名をGroongaのインデックスカラム名に変換

確認したいのはGroongaのn_buffer_segmentsの値です。 PostgreSQLのテーブル名などから直接それを得ることはできないので、まずはGroongaのインデックスカラム名を確認します。 Groongaのインデックスカラム名がわかるとそこからn_buffer_segmentsの値が取得できます。

ということでpgroonga_index_column_name関数を使ってGroongaのインデックスカラム名を確認します。

クエリ例(インデックス名とカラム名を渡すと取得できます):

SELECT pgroonga_index_column_name('pgroonga_index', 'tag');

結果例:

 pgroonga_index_column_name 
----------------------------
 Lexicon17478_2.index
(1 行)

得られたLexicon17478_2.indexがGroongaのインデックスカラム名です。

Groongaのインデックスカラム名を元にobject_inspectを使って、n_buffer_segmentsの値を取得

確認したLexicon17478_2.indexからn_buffer_segmentsの値を取得します。

PGroongaのpgroonga_command関数を使って、Groongaのobject_inspectコマンドを実行して取得します。

クエリ例:

SELECT jsonb_pretty(
  pgroonga_command('object_inspect', ARRAY['name', 'Lexicon17478_2.index'])::jsonb
);

結果例は長いので割愛します。valueの中のstatistics内にn_buffer_segmentsが確認できると思います。

で、終わると物足りないので、ピンポイントでn_buffer_segmentsの値を取得するクエリ例を掲載しつつ結果例も提示します。

ピンポイントでn_buffer_segmentsの値を取得するクエリ例:

SELECT
  (pgroonga_command('object_inspect', ARRAY['name', 'Lexicon17478_2.index'])::jsonb)
  ->1->'value'->'statistics'->'n_buffer_segments' AS n_buffer_segment;

結果例:

 n_buffer_segment
------------------
 0
(1 行)

これでPGroongaからGroongaのn_buffer_segmentsが確認できました。

3. Groongaのn_buffer_segmentsをまとめて取得

「2. Groongaのn_buffer_segmentsを取得」の通りに実行すると、Groongaのn_buffer_segmentsが確認できるのですが、対象のPGroongaのインデックスが大量にあると、ひとつひとつ実行していくのは大変です。

ということで、SQLを1回実行してまとめてGroongaのn_buffer_segmentsを取得するクエリ例も紹介します。

クエリ例:

SELECT
  i.index_column,
  (pgroonga_command(
    'object_inspect',
    ARRAY[
      'name', i.index_column
    ]
  )::jsonb)->1->'value'->'statistics'->'n_buffer_segments' AS n_buffer_segments
FROM (
  SELECT
    array_to_string((string_to_array(cmd, ' '))[2:3], '.') AS index_column
  FROM (
    SELECT
      regexp_split_to_table(
        pgroonga_command('dump', ARRAY['dump_records', 'no']),
        '\n'
      ) AS cmd
  ) AS c
  WHERE cmd LIKE 'column_create%COLUMN_INDEX%'
) AS i;

結果例:

     index_column     | n_buffer_segments
----------------------+-------------------
 Lexicon17478_0.index | 0
 Lexicon17478_1.index | 0
 Lexicon17478_2.index | 0
(3 行)

クエリの解説

いくつかのサブクエリを実行しているので、それぞれ分けて実行するとわかりやすいです。

SELECT
  regexp_split_to_table(
    pgroonga_command('dump', ARRAY['dump_records', 'no']),
    '\n'
  ) AS cmd;
                                                       cmd
-----------------------------------------------------------------------------------------------------------------
 config_set alias.column Aliases.real_name

 table_create Aliases TABLE_HASH_KEY ShortText
 column_create Aliases real_name COLUMN_SCALAR ShortText

 table_create IndexStatuses TABLE_HASH_KEY UInt32
 column_create IndexStatuses max_record_size COLUMN_SCALAR UInt32
 column_create IndexStatuses wal_applied_position COLUMN_SCALAR UInt64

 table_create Lexicon17478_0 TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto

 table_create Lexicon17478_1 TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto

 table_create Lexicon17478_2 TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto

 table_create Sources17478 TABLE_HASH_KEY UInt64
 column_create Sources17478 content COLUMN_SCALAR|COMPRESS_ZSTD LongText
 column_create Sources17478 tag COLUMN_SCALAR|COMPRESS_ZSTD LongText
 column_create Sources17478 title COLUMN_SCALAR|COMPRESS_ZSTD LongText

 column_create Lexicon17478_0 index COLUMN_INDEX|WITH_POSITION Sources17478 title
 column_create Lexicon17478_1 index COLUMN_INDEX|WITH_POSITION Sources17478 content
 column_create Lexicon17478_2 index COLUMN_INDEX|WITH_POSITION Sources17478 tag
(23 行)

↑ではGroongaのdump --dump_records noの結果を分割してテーブルとして取得しています。 その結果から↓でインデックスカラム名のみを抽出しています。

SELECT
  array_to_string((string_to_array(cmd, ' '))[2:3], '.') AS index_column
FROM (
  SELECT
    regexp_split_to_table(
      pgroonga_command('dump', ARRAY['dump_records', 'no']),
      '\n'
    ) AS cmd
) AS c
WHERE cmd LIKE 'column_create%COLUMN_INDEX%';
     index_column
----------------------
 Lexicon17478_0.index
 Lexicon17478_1.index
 Lexicon17478_2.index
(3 行)

あとは抽出したインデックスカラム名を元にobject_inspectを実行してn_buffer_segmentsを得ています。

まとめ

(ふつうに使っていればn_buffer_segmentsの値を参照する必要はないはずですが、)PGroongaからGroongaのn_buffer_segmentsの値を確認する方法を紹介しました。

n_buffer_segmentsの値を監視しないといけないような特殊な環境下でのPGroongaやGroongaを運用するときのサポートもいたしますので、何かお困りの際はお問い合わせよりご連絡ください。

補足

「3. Groongaのn_buffer_segmentsをまとめて取得」ではGroongaのdumpの結果からインデックスカラム名を得ました。

実は次のようなSQLでもインデックスカラム名を得ることができ、合わせてn_buffer_segmentsも得ることができます。

クエリ例:

SELECT
  i.relname AS index_name,
  t.relname AS table_name,
  am.amname AS index_type,
  (pgroonga_command(
    'object_inspect',
    ARRAY[
      'name', pgroonga_index_column_name(i.relname::cstring, a.attname)
    ]
  )::jsonb)->1->'value'->'statistics'->'n_buffer_segments' AS n_buffer_segments
FROM
  pg_class t
  JOIN pg_index ix ON t.oid = ix.indrelid
  JOIN pg_class i ON i.oid = ix.indexrelid
  JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
  JOIN pg_am am ON i.relam = am.oid
WHERE
  t.relkind = 'r' AND
  am.amname = 'pgroonga';

結果例:

   index_name   | table_name | index_type | n_buffer_segments
----------------+------------+------------+-------------------
 pgroonga_index | memos      | pgroonga   | 0
 pgroonga_index | memos      | pgroonga   | 0
 pgroonga_index | memos      | pgroonga   | 0
(3 行)

例に使ったテーブルではこの方法でも期待通りに結果が得られましたが、そうではない場合があったので、上述のdumpの方式にしました。 どういう場合に期待通りの結果が得られないかは、ぜひみなさん考えてみてください!

おまけ: pgroonga ((ARRAY[title, content, tag])) のとき

次のインデックスを考えます。式インデックスの場合です。

CREATE INDEX pgroonga_array_index
ON memos
USING pgroonga ((ARRAY[title, content, tag]));

このときpgroonga_index_column_name関数の引数には何を渡せばよいでしょうか。 インデックス名はpgroonga_array_indexですぐわかりますが、カラム名は何でしょうか。ARRAY[title, content, tag]

正解はarrayです。

SELECT pgroonga_index_column_name('pgroonga_array_index', 'array');

ただし直感的ではないので、本記事では触れませんでしたが、第2引数にカラムの位置を指定する構文を利用するのが無難です。

上述の例だと最初のカラムなので0を指定します。

SELECT pgroonga_index_column_name('pgroonga_array_index', 0);