4.1 実行パラメータの追加 |
4.1.1 guc.c
まず、設定したい項目を保存するグローバル変数を定義する。#ifdef _QUERY_CACHE bool query_cache = false; bool query_cache_local = true; int query_cache_check_level = 0; int query_cache_oblock_num = 4092; int query_cache_qblock_num = 1024; int query_cache_max_store_block_num = 20; #endif |
query_cacheははbool値なのでconfig_boolに追加。
static struct config_bool ConfigureNamesBool[] = { { {"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD, gettext_noop("Enables the planner's use of sequential-scan plans."), NULL }, &enable_seqscan, true, NULL, NULL }, … #ifdef _QUERY_CACHE { {"query_cache", PGC_POSTMASTER, DEVELOPER_OPTIONS, gettext_noop("Query Cache (experimental)."), NULL }, &query_cache, false, NULL, NULL }, { {"query_cache_local", PGC_USERSET, DEVELOPER_OPTIONS, gettext_noop("Query Cache Local (experimental)."), NULL }, &query_cache_local, true, NULL, NULL }, #endif … |
{"query_cache", /* パラメータ名 */ PGC_POSTMASTER, /* コンテキスト名 -> 後述 */ DEVELOPER_OPTIONS, gettext_noop("Query Cache (experimental)."), NULL }, &query_cache, /* パラメータに対応するグローバル変数 / false, /* デフォルト値 */ NULL, /* 変更時に実行したい関数名 */ NULL /* 変更値をLOGに出す? */ |
const char *const GucContext_Names[] = { /* PGC_INTERNAL */ "internal", // 設定も変更もできない /* PGC_POSTMASTER */ "postmaster", // postmasterがつかう。postgresql.confに設定化、変更不可 /* PGC_SIGHUP */ "sighup", // ? /* PGC_BACKEND */ "backend", // postgresがつかう。postgresql.confに設定化、変更不可 /* PGC_SUSET */ "superuser", // SETでスーパーユーザのみ変更可能 /* PGC_USERSET */ "user" // SETで一般ユーザが変更可能 }; |
int型なども同様。
"query_cache_check_level", PGC_USERSET, RESOURCES_MEM, gettext_noop("Sets the maximum number of data block."), NULL }, &query_cache_check_level, 0, // デフォルト値 0, // min 4, // max NULL, NULL |
変更時に実行できる関数を設定することもできるようだ。
4.1.2 guc.h
#ifdef _QUERY_CACHE extern bool query_cache; extern bool query_cache_local; extern int query_cache_check_level; extern int query_cache_oblock_num; extern int query_cache_qblock_num; extern int query_cache_max_store_block_num; #endif |
4.2 共有メモリでの領域確保 |
4.2.1 下準備
共有メモリで領域を確保するには、まず src/backend/storage/ipc/ipci.cの CreateSharedMemoryAndSemaphores()を改造しておく。
CreateSharedMemoryAndSemaphores(bool makePrivate, int port) { … #ifdef _QUERY_CACHE size += QueryCacheShmemSize(); #endif … InitFreeSpaceMap(); #ifdef _QUERY_CACHE /* * Set up query-cache space */ InitQueryCacheShmem(); #endif … |
4.2.2 メモリ領域の確保
サイズを求めるのは簡単だから省略し、メモリ領域の確保についてだけ。
簡単に言ってしまえば、ShmemAlloc()とMemSet()というPostgreSQLが準備した関数を使って
メモリを確保しろ、ということ。
void InitQueryCacheShmem(void) { if (query_cache) { create_header (); create_qc_oid_space (); create_qc_query_space (); } } /* * The initialization routine of QueryCache Header */ static void create_header (void) { size_t size = header_size (); /* * Create query cache header */ QueryCacheHeader = (QueryCache_Header *) ShmemAlloc(size); if (QueryCacheHeader == NULL) ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("insufficient shared memory for query cache header"))); MemSet(QueryCacheHeader, 0, size); /* * Initialize: header data */ init_header_data (); } /* * */ static void create_qc_oid_space (void) { size_t size; size = qc_oid_space_size (); /* */ QueryCacheOidSpace = (char *) ShmemAlloc (size); /* * */ if (QueryCacheOidSpace == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("insufficient shared memory for qc_oid_space"))); } MemSet(QueryCacheOidSpace, 0, size); /* メモリ領域内部の初期化 */ init_blocks (query_cache_oblock_num, (Oid)0, (void (*)(int *, block_type *, void *, bid *, bid *))(set_table_block)); } /* * */ static void create_qc_query_space (void) { size_t size; query c_query; size = qc_query_space_size (); /* */ QueryCacheQuerySpace = (char *) ShmemAlloc (size); /* */ if (QueryCacheQuerySpace == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("insufficient shared memory for qc_query_space"))); } MemSet(QueryCacheQuerySpace, 0, size); /* メモリ領域内部の初期化 */ init_current_query (&c_query); init_blocks (query_cache_qblock_num, &c_query, (void (*)(int *, block_type *, void *, bid *, bid *))(set_query_block)); } |
確保したメモリ領域でhashを使いたい場合には、freespace/freespace.cを参考にするとよい。
手順はShmemInitStruct()で領域を確保し、ShmemInitHash()でハッシュを構築する。
* NOTES: * (a) There are three kinds of shared memory data structures * available to POSTGRES: fixed-size structures, queues and hash * tables. Fixed-size structures contain things like global variables * for a module and should never be allocated after the process * initialization phase. Hash tables have a fixed maximum size, but * their actual size can vary dynamically. When entries are added * to the table, more space is allocated. Queues link data structures * that have been allocated either as fixed size structures or as hash * buckets. Each shared data structure has a string name to identify * it (assigned in the module that declares it). |
4.2.3 共有メモリ領域へのアクセス
いかにもポインタのように扱えるマクロがあるようだが、 今回は車輪の再発明というか、PostgreSQLの内部を深く知ることが目的の一つなので、 memcpyでちまちま関数を書いている。先のメモリ領域確保の関数で、次のようにした。
QueryCacheOidSpace = (char *) ShmemAlloc (size); |
static char* QueryCacheOidSpace = NULL; |
では、以下が共有メモリ領域に読み書きする関数たち。
もっとスマートな方法もあるだろうが、PostgreSQLのソースのジャングルを探索をしながら作っていたら、
こうなってしまった。全部、直接アドレス計算をして読み書きする。
注: (id - 1)としているのは、論理的にはidを1から数えているから。
これはこのプログラムでの内部規約であり、一般的な話ではない。
static void write_table_block (oblock *ob, bid id) { memcpy((void *) (QueryCacheOidSpace + sizeof(oblock) * (id - 1)), (const void *)ob, sizeof(oblock)); } static void read_table_block (oblock *ob, bid id) { memcpy((void *)ob, (const void *) (QueryCacheOidSpace + sizeof(oblock) * (id - 1)), sizeof(oblock)); } |
ブロック、つまり構造体の任意の要素に読み書きするには、マクロoffsetofを利用する。
struct oblock { … } qid; }; bid prev, next; }; #define offset_oblock_prev offsetof (oblock, prev) #define offset_oblock_next offsetof (oblock, next) static void get_next_table_block (bid id, bid *next) { memcpy((void *)next, (const void *) (QueryCacheOidSpace + sizeof(oblock) * (id - 1) + offset_oblock_next), sizeof(bid)); } static void set_next_table_block (bid id, bid next) { memcpy((void *)(QueryCacheOidSpace + sizeof(oblock) * (id - 1) + offset_oblock_next), (const void *) &next, sizeof(bid)); } |
4.3 メモリコンテキスト |
要するに、適したメモリコンテキストを選んで、そこでpalloc()で領域を確保すればよい。
予めコンテキスト毎に十分な量のメモリが確保されているので、
効率を気にすることなく、がんがんpallocを使ってもよい(OSがalloc()しているわけでは*ない*)。
またほとんどのコンテキストは、クエリが終了したり(QueryContext, ErrorContext…)、
トランザクションが終了すると、
(勝手に)メモリ領域を解放してくれるので、かなり便利。
4.3.1 独自のメモリコンテキスト生成
独自のメモリコンテキストの生成も簡単。以下のはTopMemoryContextの子どもとして生成する。
言い忘れていたが、メモリコンテキストはTopMemoryContxtを根とする木構造で、
どんどん子どもを作れる。
途中のメモリコンテキストが削除されると、その子どもたちも自動的に削除される。
static MemoryContext query_cache_context = NULL; static void create_query_cache_context (void) { query_cache_context = AllocSetContextCreate(TopMemoryContext, "query cached data cell ", ALLOCSET_DEFAULT_MINSIZE, sizeof (struct data_cell), sizeof (struct data_cell) * ceil((double) query_cache_max_store_block_num / MAX_DATA)); } |
メモリコンテキストの使い方は、いろいろなところに書かれているとおり。
struct cell *c; … MemoryContext oldcontext = MemoryContextSwitchTo(CurTransactionContext); … c = (struct cell *) palloc (sizeof (struct cell)); MemoryContextSwitchTo(oldcontext); |
4.3.2 CurTransactionContextを使った例
トランザクション処理の対応は仮実装なのだが、 CurTransactionContextの例として、分かりやすいと思うので、概略を書く。
以下、簡単にトランザクション処理への対応を説明する。
ポイントはメモリコンテキストの中のCurTransactionContext。
BEGIN文を受け取ったら、CurTransactionContextにstruct *Tx_info tx_infoというデータ領域を生成する。
そして、COMMIT文かROLLBACK文を受け取ると、
CurTransactionContextは初期化され、*Tx_info tx_info = NULLに戻る。
Remark: プログラム中では、struct *Tx_info == NULLならトランザクション外、!=ならトランザクション内という判断をしている。 論理的でなく実装依存の汚い部分。
COMMIT文が実行されると、tx_infoに追加されたoid_cellを辿りつつ、
格納されていたOIDを(共有)メモリ上から削除していく。
これにより、Transaction処理内で操作されたであろうテーブルの情報は、一旦キャッシュ上から削除され、
ACIDのCIが保証されていると(勝手に)思っている。
ちなみに、勝手にトランザクションがアボートされた場合、CurTransactionContextは初期化されるので、
勝手にOIDの削除が行なわれる心配はない。
と、まあ、こんな感じで実装してみた。
contents |index |previous |next