このチュートリアルでは、共有メモリを使ったオンメモリクエリキャッシュを試す方法を説明します。
必要なのは、Linux マシン 1 台で、PostgreSQL と pgpool-II がインストールされているものとします。 なお、pgpool-II はどのモードで稼働していても、オンメモリクエリキャッシュを利用することができます。
オンメモリクエリキャッシュ機能を有効にする「memory_cache_enabled」を on にします。
# If on, use the memory cache functionality, off by default memory_cache_enabled = on
メモリキャッシュでは、どの DB のどのテーブルのどのキャッシュ、という情報をファイルに保存していて、 これを「OID マップ」と呼んでいます。 このファイル群を配置するディレクトリを「memqcache_oiddir」で指定します。
# Temporary work directory to record table oids memqcache_oiddir = '/var/log/pgpool/oiddir'
キャッシュを記録する先を指定する「memqcache_method」は、デフォルトのまま shmem(共有メモリ)を指定します。
# Cache storage method. either 'shmem'(shared memory) or 'memcached'. 'shmem' by default memqcache_method = 'shmem'
オンメモリクエリキャッシュの動作確認に便利なため、どのバックエンドノードでどんなクエリが実行されたかを 出力するフラグ「log_per_node_statement」を on にします。
log_per_node_statement = on # Log all statements # with node and backend informations
pgpool-II を起動します。 なお、ログで動作確認するため、-n オプションをつけて非デーモンモードで起動し、 出力されるログメッセージを pgpool.log ファイルにリダイレクトします。
$ {installed_dir}/bin/pgpool -n -f {installed_dir}/etc/pgpool.conf > pgpool.log 2>&1
データベース、テーブルを作成します。
$ createdb -p 9999 test $ psql -p 9999 test test=> CREATE TABLE table_cache_ok (a INT); CREATE TABLE test=> INSERT INTO table_cache_ok VALUES (1), (2), (3); INSERT 0 3 test=> SELECT * FROM table_cache_ok ORDER BY a; a --- 1 2 3 (3 rows)
SELECT を実行してみます。
test=> SELECT * FROM table_cache_ok WHERE a = 1; a --- 1 (1 row)
ログの log_per_node_statement の出力から、SELECT が実行されていることがわかります。
LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1;
では、もう一度同じ SELECT を実行します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; a --- 1 (1 row)
ログを見ると今度は以下のメッセージがあり、キャッシュから結果を取得してきたことがわかります。
LOG: query result fetched from cache. statement: SELECT * FROM table_cache_ok WHERE a = 1;
キャッシュした SELECT 結果は、そのテーブルが INSERT/UPDATE/DELETE されたとき、 その SELECT 結果が古くなる可能性があります。 デフォルトでは、INSERT/UPDATE/DELETE が行なわれたら、そのテーブルのキャッシュを削除します。
ここではその動作を確認します。
「memqcache_auto_cache_invalidation」で指定します。デフォルトでは on になっています。
# If on, invalidation of query cache is triggered by corresponding # DDL/DML/DCL(and memqcache_expire). If off, it is only triggered # by memqcache_expire. on by default. memqcache_auto_cache_invalidation = on
設定を変更した場合は、pgpool-II を再起動します。
SELECT がキャッシュされていることを確認します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: query result fetched from cache. statement: SELECT * FROM table_cache_ok WHERE a = 1;
テーブルに INSERT を実行します。
test=> INSERT INTO table_cache_ok VALUES (5); INSERT 0 1
再度 SELECT を実行し、キャッシュから取得されず、実際に SELECT が実行されることを確認します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1;
そのほか、一定秒数が経過したキャッシュも、自動的に削除します。
「memqcache_expire」パラメータでその秒数を指定します。 デフォルトは 0 秒ですが、ここでは動作確認のため 5 秒にしてみます。
# Memory cache entry life time specified in seconds. # 0 means infinite life time. 0 by default. memqcache_expire = 5
設定を変更したら、pgpool-II を再起動します。
SELECT がキャッシュされていることを確認します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: query result fetched from cache. statement: SELECT * FROM table_cache_ok WHERE a = 1;
そのまま 5 秒以上待ちます。
再度 SELECT を実行し、キャッシュから取得されず、実際に SELECT が実行されることを確認します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1;
一部テーブルだけキャッシュしたい場合、「white_memqcache_table_list」にそのテーブルを指定します。 TEMP テーブルや UNLOGGED テーブルも、ここに指定すればキャッシュされるようになります。
テーブルの名前を直接指定することも、正規表現を使う(^ と $ が補完されて使われる)こともできます。
white_memqcache_table_list = '.*_cache_ok' # Comma separated list of table names to memcache # that don't write to database # Regexp are accepted
設定を変更したら、pgpool-II をリロードします。
$ {installed_dir}/bin/pgpool reload
キャッシュ不可とするテーブルを作成します。
$ psql -p 9999 test test=> CREATE TABLE table_cache_ng (a INT); CREATE TABLE test=> INSERT INTO table_cache_ng VALUES (1), (2), (3); INSERT 0 3
ホワイトリストに指定したテーブルの SELECT が、キャッシュされることを確認します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT oid FROM pg_database WHERE datname = 'test' test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: query result fetched from cache. statement: SELECT * FROM table_cache_ok WHERE a = 1;
ホワイトリストに指定していないテーブル の SELECT が、キャッシュされないことを確認します。
test=> SELECT * FROM table_cache_ng WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1; test=> SELECT * FROM table_cache_ng WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1;
一部テーブルだけキャッシュしたくない場合、「black_memqcache_table_list」にそのテーブルを指定します。 TEMP テーブルや UNLOGGED テーブルも、ここに指定したもの以外がキャッシュされるようになります。
テーブルの名前を直接指定することも、正規表現を使う(^ と $ が補完されて使われる)こともできます。
black_memqcache_table_list = '.*_cache_ng' # Comma separated list of table names not to memcache # that don't write to database # Regexp are accepted
設定を変更したら、pgpool-II をリロードします。
$ {installed_dir}/bin/pgpool reload
ブラックリストに指定したテーブルの SELECT が、キャッシュされないことを確認します。
test=> SELECT * FROM table_cache_ng WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1; test=> SELECT * FROM table_cache_ng WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT * FROM table_cache_ok WHERE a = 1;
ブラックリストに指定していないテーブル の SELECT が、キャッシュされることを確認します。
test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: DB node id: 0 backend pid: 11203 statement: SELECT oid FROM pg_database WHERE datname = 'test' test=> SELECT * FROM table_cache_ok WHERE a = 1; LOG: query result fetched from cache. statement: SELECT * FROM table_cache_ok WHERE a = 1;
キャッシュできる SELECT 結果のサイズには限界があります。 これを指定しているのが「memqcache_maxcache」パラメータです。
# Maximum SELECT result size in bytes. # Must be smaller than memqcache_cache_block_size. Defaults to 400KB. memqcache_maxcache = 409600
このサイズを超過しているときは、以下のログメッセージが出力されます。 これは、「memqcache_maxcache」を 1024 バイトと非常に小さくして、10 MB 以上のテーブルを全件 SELECT した例です。
LOG: DB node id: 0 backend pid: 17749 statement: SELECT * FROM pgbench_accounts ; LOG: pool_add_temp_query_cache: data size exceeds memqcache_maxcache. current:983 requested:110 memq_maxcache:1024
SELECT の可否をテーブルごとの指定ではなく、より細かく指定したい場合には、 クエリの頭に「/* NO QUERY CACHE */」という接頭辞をつけることで対処することができます。
例えば、以下のようにキャッシュされている SELECT でも、
LOG: statement: SELECT * FROM table_cache_ok WHERE a = 1; LOG: query result fetched from cache. statement: SELECT * FROM table_cache_ok WHERE a = 1;
「/* NO QUERY CACHE */」をつけて実行すると、キャッシュされないようになります。
LOG: statement: /* NO QUERY CACHE */ SELECT * FROM table_cache_ok WHERE a = 1; LOG: DB node id: 0 backend pid: 18070 statement: /* NO QUERY CACHE */ SELECT * FROM table_cache_ok WHERE a = 1; LOG: statement: /* NO QUERY CACHE */ SELECT * FROM table_cache_ok WHERE a = 1; LOG: DB node id: 0 backend pid: 18070 statement: /* NO QUERY CACHE */ SELECT * FROM table_cache_ok WHERE a = 1;
なおこれは、現実的にはアプリケーションに手を入れることになるでしょう。