PostgreSQL + QueryCache

last update: 24 Dec 2005

contents |index |previous |next

本題



2.1 QueryCache(クエリーキャッシュ)とは
QueryCache(クエリーキャッシュ)とはMySQLに実装されている機能のひとつで、

「実行されたSELECT文の結果をキャッシュ(query_cache)に保存しておき、同じSELECT文が要求された場合は(検索を行わずに)キャッシュに保存されている結果を返す機能」のこと。

詳しくは次のURLを参照のこと。

クエリーキャッシュ

2.2 動機
動機は :

  1. 仕事がぽしゃって暇だったから
  2. しばらくPostgreSQLから離れてほとんど忘れていたから、リハビリの意味で
  3. むしゃくしゃしてたから
  4. QueryCacheが好きだから
  5. 秘める裏の目的があるから
のうちのどれか(複数選択可)。
2.3 結果
成功半分、失敗半分。

成功は

  1. QueryCacheが実装できた
  2. PostgreSQLの内部を今までよりも深く理解できた
  3. 秘める裏の目的に進むことができた

失敗は
  1. QueryCacheを実装したことで、PostgreSQLの処理が数割遅くなった
  2. 大量のSELECT処理を行なうと(多分、実装ミスが原因)たまにpostgres<->clientの接続が切れる

ということで、PostgreSQLにQueryCacheを実装するのは逆効果、 地道にbufferまわりのアルゴリズムを改良していくほうがいいんじゃないという、 (私見による)結論を得たわけだが、それもありだろう。
私見: 今はLWLockのQueryCacheLocKがかなり足枷(あしかせ)になっているのは事実。 querycache領域についてもっと粒度の細かいLockをかけないと、 せっかくの同時実行性が大きく損なわれている。
しかし、そこを改良していくより、buffer関連のアルゴリズムを改良し、 querycacheに使うメモリをbufferにまわすほうがよいはずだ。

ところで、今回の実装にはいくつか制約がある。

  1. Transaction処理には(厳密には)対応していない。
    ソース上では実装しているが、仮のもの。それらしい動きはするが、詳しい検証はしていない。

  2. SELECT,INSERT,UPDATE,DELETE,およびBEGIN,COMMIT/ROLLBACKだけ対応。
    DROP TABLEとかTRUNCATEとか、DROP DATABASEには未対応。

  3. ユーザ名は"postgres"、データベース名"sample"に決め打ちしている。
    つまり、権限チェックしていないということ。簡単に解決できるが、これ以上このソースを改良する意義が見出せないので、やってない。

2.4 実行例
簡単な実行例を示す。

"sample=# set query_cache_check_level = 4;"で
サーバがQueryCacheの各種情報を標準エラーに表示するようになる。


$ /usr/local/pgsql/bin/psql sample
Welcome to psql 8.1.0, the PostgreSQL interactive terminal.
Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

sample=# set query_cache_check_level = 4;
SET

psqlの起動


まず、SELECT文"select * from accounts where aid < 3;"を2回実行する。
sample=# select * from accounts where aid < 3;

 aid | bid | abalance |                                        filler
-----+-----+----------+----------------------------------------------------------
   1 |   1 |       10 |
   2 |   1 |       10 |

(2 rows)

sample=# select * from accounts where aid < 3;
 aid | bid | abalance |                                        filler
-----+-----+----------+----------------------------------------------------------
   1 |   1 |       10 |
   2 |   1 |       10 |

(2 rows)

SELECT文の実行


1回目のSELECT文では、SELECT文と結果が(共有)メモリ上にキャッシュされたことが確認できる(check_query blocksの部分)。

*******************************************
 LOG: 
*******************************************
============ check_table blocks ===========
oid             oid/qid (p)prev (p)next qid_num qid_next
-------------------------------------------
1(TABLE)        16389   0       2
2(QUERY)        1       0       0
============ check_query blocks ===========
qid     prev    next    hashkey next    last    query/data(len)
-------------------------------------------
1(QID)  0       0       369     2       2       select * from accounts where aid < 3;
2(DID)                          0               (337)
=============== check_header ==============
table_block_num = 2, max_table_block_num = 4092
query_block_num = 2, max_query_block_num = 5424
table_free = 3  query_free = 3
-- qc_oid_space -----------------------------
        (101)   1       1
-- qc_query_space -----------------------------
        (369)   1       1
=============== statistics ==============
all queries = 2
        store = 1       hits = 0        not_cached = 0
     

2回目のSELECT文では、キャッシュされたデータが返されたことがわかる(statisticsパートのhits = 1の部分)

*******************************************
 LOG: 
*******************************************
============ check_table blocks ===========
oid             oid/qid (p)prev (p)next qid_num qid_next
-------------------------------------------
1(TABLE)        16389   0       2
2(QUERY)        1       0       0
============ check_query blocks ===========
qid     prev    next    hashkey next    last    query/data(len)
-------------------------------------------
1(QID)  0       0       369     2       2       select * from accounts where aid < 3;
2(DID)                          0               (337)
=============== check_header ==============
table_block_num = 2, max_table_block_num = 4092
query_block_num = 2, max_query_block_num = 5424
table_free = 3  query_free = 3
-- qc_oid_space -----------------------------
        (101)   1       1
-- qc_query_space -----------------------------
        (369)   1       1
=============== statistics ==============
all queries = 3
        store = 1       hits = 1        not_cached = 0
     

次、
UPDATE文を実行する。

sample=# update accounts set abalance = 0 where aid < 3;
UPDATE 2

UPDATE文の実行


すると、いままでキャッシュされていたデータがきれいに(共有)メモリ上から消えていることがわかる。

*******************************************
 LOG: 
*******************************************
============ check_table blocks ===========
oid             oid/qid (p)prev (p)next qid_num qid_next
-------------------------------------------
============ check_query blocks ===========
qid     prev    next    hashkey next    last    query/data(len)
-------------------------------------------
=============== check_header ==============
table_block_num = 0, max_table_block_num = 4092
query_block_num = 0, max_query_block_num = 5424
table_free = 1  query_free = 1
-- qc_oid_space -----------------------------
-- qc_query_space -----------------------------
=============== statistics ==============
all queries = 4
        store = 1       hits = 1        not_cached = 0
     

2.5 実行環境

    2.5.1 ソース+パッチ

    対応バージョンはPostgreSQL 8.1.0。
    パッチ postgresql-8.1.0-querycache-alpha-sh001.patchと、 querycache.cquerycache.h。 querycache.cはsrc/backend/tcop以下に、querycache.hはsrc/include以下に置く。

    パッチは必然的にBSDライセンス。 querycache.hはライセンスフリー(煮るなり焼くなり、お好きに)。 querycache.cはインスパイヤライセンス。思い付きで書き殴ったソースで、汚いし未完成だが(正直恥ずかしい)、好きにインスパイヤしてください。
    特にelog()挿入は大歓迎

    /*-------------------------------------------------------------------------
     *
     * querycache.c
     *	  Query Cache for PostgreSQL
     *
     * NOTES
     *	  
     *	  インスパイヤ・ライセンス 
     *
     * 配布条件:
     * 
     *  以下に示す条件の下、各自が自由にインスパイヤできる。
     *
     *  (1)このヘッダーファイルを必ず添付し、直接的にインスパイヤされたリスペクトファイルと、
     *  以下に示すインスパイヤ度を新たに更新する。
     *
     *	リスペクト・ファイル www.buena-idea.net/postgresql/querycache/querycache.c
     *
     *	インスパイヤ度 = max(0, org - dif) / org 
     *
     *	ここで
     *		org => リスペクトファイルの行数 - このヘッダー部分の行数
     *		dif  => 次のスクリプトで得た、追加による変更箇所の行数
     *			"diff -uibw querycache.c.orig querycache.c | grep "^+[^+]" | wc -l"
     *
     *
     *		Ex: 関数名をstore_data からdata_storeに変更すると、以下に示すように5行が変更されたことになる。
     *
     *			# diff -uibw querycache.c querycache.c.orig | grep "^+[^+]" 
     *			+static bool store_data (void);
     *			+      create_qc_query_space ();
     *			+      ret = store_data ();
     *			+store_data (void)
     *			+   * This function is called from only store_data (). 
     *			# diff -uibw querycache.c.orig querycache.c | grep "^+[^+]"  | wc -l
     *			     5
     *
     *			dif = 5。 
     *                      リスペクトファイルの総行数は3149で、このヘッダーは77行なのでorg = 3149-77 = 3072、
     *                      インスパイヤ度は (3072 - 5)/ 3072 = 0.9983 => 99.8372396%である。
     *                                                                                                       
     *                                                                                                       
     *
     *  (2)Aが作ったファイルをリスペクトしたBは、Aのファイルをリスペクトファイルと明記しなければならない。
     *  BがAをリスペクトして作ったファイルを、Cがさらにリスペクトする場合は、CはBのファイルのみリスペクトすればよい。
     *
     *  (3)インスパイヤ度が0となった時点で、そのファイルのインスパイヤ・ライセンスは消失する。
     *
     *
     *  ライセンスの適用地域:
     *  カタカナ文化圏のみ有効。
     *  英語を含む他の言語圏へ輸出する場合は、このヘッダファイルを削除した上で、ライセンスフリーとする。
     *  理由は、"インスパイヤ"に相当する用語への翻訳が不可能なため。
     *
     *  ライセンスの適用における問題点と、現時点での対処:
     *  diff による変更行数の検出には問題があり、例えばindentでファイル整形するとか、
     *  関数定義を並び換えるだけで、インスパイヤ度が0%になってしまう。
     *  このような巧妙なインスパイヤに対して、Ave×ライセンスを策定準備中であるが、商標の問題から
     *  実現には長い時間がかかると予想されるため、上に示したインスパイヤ方法も、許容することとする。
     *
     * リミックス・ライセンスとの関係:
     * インスパイヤ・ライセンスは、リミックス・ライセンスに含まれるので、
     * リミックス・ライセンスの下でソースをサンプリングするのは自由。
     * ここでリミックス・ライセンスとは、適当に部分部分を摘んで新しいファイルにカット&ペイストしたものを
     * 新たに作品と呼ぶことを保証するライセンス。カット&ペイストは条文中ではサンプリングといわれる。         
     *
     *  /\ /\
     * ( ^ A^) 
     * (      ) 
     * |  |  |
     * (___)__)
     * インスパイヤ・ライセンスのキャラクターは、"モナー"をインスパイヤしたものです。
     * リスペクト先 : 2ch.net
     * インスパイヤ度 = 99%
     *-------------------------------------------------------------------------
     */
    

    インスパイヤ・ライセンス


    パラメータ設定(guc.c)、メモリコンテキスト関連、 共有メモリ上での領域確保とアクセス、LOCKの使い方など、 これからPostgreSQLをHackしようという優秀な人の参考になれば、 恥ずかしいソースでも少しは世の為になるかも、とWEB上に公開する。そのうちGoogleさんがCacheしてくれるだろう。

    ちなみに開発はRedHat9 Linuxで行ない、動作確認もそれでしか行なっていない。

    2.5.2 postgresql.conf

    以下のパラメータを追加。

    query_cache = true		# デフォルトはfalseで、querycache機能が無効になっている
    query_cache_oblock_num = 4092   # 
    query_cache_qblock_num =  5424  #
    query_cache_max_store_block_num = 28 #
    
    

    postgresql.confの一部


    2.5.3 ベンチマーク

    ベンチマークをやってみた。マルチクライアントでがんがんSELECT文を投げられると、 当然ながら遅くなる。ぜんぜんQueryCacheの意味がない。
    あと、最悪なことに、ときどきクライアントとの接続が切れている。DB失格。
    $ /usr/local/pgsql/bin/pgbench -S sample
    starting vacuum...end.
    transaction type: SELECT only
    scaling factor: 1
    number of clients: 1
    number of transactions per client: 10
    number of transactions actually processed: 10/10
    tps = 245.074012 (including connections establishing)
    tps = 356.011250 (excluding connections establishing)
    
    
    $ /usr/local/pgsql/bin/pgbench -S -c 10 sample
    starting vacuum...end.
    transaction type: SELECT only
    scaling factor: 1
    number of clients: 10
    number of transactions per client: 10
    number of transactions actually processed: 100/100
    tps = 228.147465 (including connections establishing)
    tps = 317.170333 (excluding connections establishing)
    
    
    $ /usr/local/pgsql/bin/pgbench -S -c 10 -t 20 sample
    starting vacuum...end.
    Client 4 aborted in state 1: Client 1 aborted in state 1: Client 8 aborted in state 1: transaction type: SELECT only
    scaling factor: 1
    number of clients: 10
    number of transactions per client: 20
    number of transactions actually processed: 166/200
    tps = 217.282891 (including connections establishing)
    tps = 262.587180 (excluding connections establishing)
    
    
    $ /usr/local/pgsql/bin/pgbench -S -c 10 -t 20 sample
    starting vacuum...end.
    Client 9 aborted in state 1: Client 1 aborted in state 1: Client 0 aborted in state 1: transaction type: SELECT only
    scaling factor: 1
    number of clients: 10
    number of transactions per client: 20
    number of transactions actually processed: 165/200
    tps = 212.956808 (including connections establishing)
    tps = 257.021757 (excluding connections establishing)
    
    

    pgbench: querycacheあり


    ここで統計情報をみると、530件ちかくのSELECT文を処理し、わずかに7件しかキャッシュにヒットしていない。

    *******************************************
     LOG: 
    *******************************************
    =============== check_header ==============
    table_block_num = 521, max_table_block_num = 4092
    query_block_num = 1038, max_query_block_num = 5424
    table_free = 522        query_free = 1039
    =============== statistics ==============
    all queries = 614
            store = 519     hits = 7        not_cached = 0
    >>>
    

    統計情報


    作為的なベンチマークも可能だが、pgbenchを改造する気もないので、ここで辞めておく。

    ちなみに、 querycacheなしの結果も示す。

    $ /usr/local/pgsql/bin/pgbench -S sample
    starting vacuum...end.
    transaction type: SELECT only
    scaling factor: 1
    number of clients: 1
    number of transactions per client: 10
    number of transactions actually processed: 10/10
    tps = 118.207501 (including connections establishing)
    tps = 126.035063 (excluding connections establishing)
    
    
    $ /usr/local/pgsql/bin/pgbench -S -c 10 -t 20 sample
    starting vacuum...end.
    transaction type: SELECT only
    scaling factor: 1
    number of clients: 10
    number of transactions per client: 20
    number of transactions actually processed: 200/200
    tps = 455.719969 (including connections establishing)
    tps = 536.595836 (excluding connections establishing)
    

    pgbench: querycacheなし



contents |index |previous |next


since 04/Oct/2004