| 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