はじめに
前回のブログでは、**rewriteheap.cpp**の学習を開始しました。今回は引き続きrewriteheap.cppの学習とコード解説を進めます。ファイルパス: opengauss-server\\src\\gausskernel\\storage\\access\\heap\\rewriteheap.cpp
一、技術概要
Heap Only Tuple技術概要
Heap Only TupleはPostgreSQL 8.3バージョンで導入された技術であり、update操作の効率化を目的としています。PG9.2.4S上で構築されたOpenguassも同様にこの技術をサポートしています。
テーブルに複数のフィールドがあり、その中にインデックスを持つフィールドが存在する場合、フィールドの値を変更するとPostgreSQLのMVCCメカニズムにより新しいtupleが追加され、前のtupleはdeadとマークされます。このとき、各tupleの物理アドレスctidが変更されます。インデックスはctidアドレスに基づいてデータを検索するため、フィールドの変更はインデックスの変更も意味します。テーブルに多くのインデックスがある場合、すべてのインデックスを変更する必要があり、これにより書き込みが増幅する問題が発生します。hot技術を使用すると、すべてのインデックス属性が変更されておらず、新しいバージョンが元のバージョンと同じページ上に存在する場合、新しいインデックスレコードが生成されません。これらのレコードはhot(heap only tuple)と呼ばれます。以下では、従来の更新とhot更新をそれぞれ簡単に説明します。
従来の更新
tuple1をtuple3にupdateする場合、新しい行へのアクセスパスは以下の通りです。
hot更新
古い行へのポインタlinp1は、古い行のtuple1位置を指します。古い行のtuple1のt_cidは新しい行tuple4の位置を指しており、tuple1内にはこのチェーンが終了していないことを示すフラグ(infomask2)があります。新しい行tuple4内には、このチェーンの末端であることを示すフラグがあり、tupleデータが返されます。
しかし、古いtupleはdead tupleであり、vacuumによって回収されるため、このチェーンに問題が発生します。そのため、pruning剪枝操作が行われ、行ポインタのリダイレクトが実行されます。
Pruning後の更新
古いtupleのポインタが新tupleのポインタに再指向されます。tuple1をtuple4に更新する例で、新しい行へのアクセスパスは以下の通りです。
古い行へのポインタlinp1は、新しい行ポインタlinp4の位置を指します。新しい行ポインタlinp4は、新しい行tuple4の位置を指し、tupleデータが返されます。適切な时机で古いtuple1が回収されます。
二、用語解説
fillfactor
テーブルの填充因子(フィルファクター)は**10から100のパーセンテージ**です。100(完全に埋める)がデフォルト値です。より小さい填充因子を指定すると、INSERT操作はテーブルを指定されたパーセンテージのみでページ埋めします。各ページに残りのスペースを残し、そのページ上の行の更新に使用します。これにより、UPDATEが行の更新コピーを同じページ上に配置する機会が得られ、別のページに配置するよりも効率的になります。**更新されないエントリを持つテーブルでは、填充因子を100(完全に埋める)に設定するのが最適ですが、大量の更新があるテーブルでは、より小さい填充因子が適切です。このパラメータは列ストレージテーブルには意味がありません**。
テーブルのfillfactor値を変更することは、HOTの使用率に影響を与えます。HOTを使用しないupdateは通常2つのデータブロックを更新する必要がありますが、HOTを使用したupdateは1つのデータブロックのみを更新します。以下の条件が満たされた場合にHOT特性がトリガーされます:
- updateがインデックスフィールドを変更しない
- update後、新しいtupleが同じheapページ内にある
fillfactor値を下げることの影響:
- HOTの使用率が向上する
- ページ内の更新が容易になる
- ストレージ使用効率が若干低下する可能性がある
三、コード解説
関数名:rewrite_write_one_page
この関数は主に**データベースシステムのデータページ書き換えプロセス**で使用され、データページが変更された後、変更されたデータページをディスクに書き戻す必要があります。このプロセスでは、暗号化、ログ記録などの特殊な操作が含まれる場合があります。
/*
* 関数名:rewrite_write_one_page
* 説明:この関数は主にデータベースシステムのデータページ書き換えプロセスで使用されます。
* データページが変更された後、変更されたデータページをディスクに書き戻す必要があります。
* このプロセスでは、暗号化、ログ記録などの特殊な操作が含まれる場合があります。
*/
static void rewrite_write_one_page(RewriteState state, Page page)
{
TdeInfo tde_info = {0};
if (RelationisEncryptEnable(state->rs_new_rel)) {
GetTdeInfoFromRel(state->rs_new_rel, &tde_info);
}
/*
* 新しいリレーション(state->rs_new_rel)で暗号化が有効な場合、
* このリレーションからTDE情報を取得します。
*/
if (IsSegmentFileNode(state->rs_new_rel->rd_node)) {
//新しいリレーションがセグメントファイルノードかどうかをチェック
Assert(state->rs_use_wal);
Buffer buf = ReadBuffer(state->rs_new_rel, P_NEW);
//新しいリレーションのバッファを読み取る
#ifdef USE_ASSERT_CHECKING
BufferDesc *buf_desc = GetBufferDescriptor(buf - 1);
Assert(buf_desc->tag.blockNum == state->rs_blockno);
#endif
//USE_ASSERT_CHEKINGが定義されている場合、rs_blocknoをチェック
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
XLogRecPtr xlog_ptr = log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, page, true,
&tde_info);
//このバッファをロック
errno_t rc = memcpy_s(BufferGetBlock(buf), BLCKSZ, page, BLCKSZ);
//ページデータをバッファブロックにコピー
securec_check(rc, "\0", "\0");
PageSetLSN(BufferGetPage(buf), xlog_ptr);//バッファブロックのLSNを設定
MarkBufferDirty(buf);
UnlockReleaseBuffer(buf);
} else {
/* 新しいファイルを拡張する際のテーブルスペースサイズ制限をチェック */
STORAGE_SPACE_OPERATION(state->rs_new_rel, BLCKSZ);
if (state->rs_use_wal) {
log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, page, true, &tde_info);
}
// WALが使用されている場合、新しいページをログ記録
RelationOpenSmgr(state->rs_new_rel);
//新しいリレーションのストレージマネージャを開く
char *bufToWrite = NULL;
if (RelationisEncryptEnable(state->rs_new_rel)) {
bufToWrite = PageDataEncryptIfNeed(page, &tde_info, true);
} else {
bufToWrite = page;
}
//新しいリレーションで暗号化が有効な場合、ページデータを暗号化する必要がある
PageSetChecksumInplace((Page)bufToWrite, state->rs_blockno);
//正しい位置にページチェックサムを設定
rewrite_flush_page(state, (Page)bufToWrite);
//ページ状態をフラッシュ
}
}
ローカル変数の初期化
TdeInfo tde_info = {0};
その構造体定義は以下の通りです:
typedef struct {
char dek_cipher[DEK_CIPHER_LEN];
char cmk_id[CMK_ID_LEN];
uint8 iv[RANDOM_IV_LEN];
uint8 tag[GCM_TAG_LEN];
uint8 algo;
uint8 res[RES_LEN];
} TdeInfo;
| フィールド | 機能 |
|---|---|
| dek_cipher | 秘密鍵の保存 |
| cmk_id | CMK IDの保存 |
| iv | ランダム情報 |
| tag | 識別子 |
| algo | 暗号化アルゴリズム |
| res | 戻り値の予約情報 |
WAL位置を記録するポインタ
XLogRecPtr xlog_ptr = log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, page, true,
&tde_info);
XLogRecPtrはPostgreSQLの型で、WAL(Write-Ahead Logging)内の位置ポインタを格納するために使用されます。書き込みが成功した後にログにコミットされ、データベースの復旧に使用されます。
暗号化処理
char *bufToWrite = NULL;
if (RelationisEncryptEnable(state->rs_new_rel)) {
bufToWrite = PageDataEncryptIfNeed(page, &tde_info, true);
} else {
bufToWrite = page;
}
この書き換えプロセスでは、書き込み内容の復号化と暗号化が考慮されています。**rs_new_rel**構造体には、暗号化が必要かどうかのフラグ情報が記録されています。
四、まとめ
今回のブログでは、**rewriteheap.cpp**の動作メカニズムについてより深く理解しました。hot技術は、頻繁なデータ挿入を行うデータベースのストレージ効率を大幅に向上させます。fillfactor填充因子は、hot技術の使用により細かい選択肢を提供します。
次回のブログでは、これまでの作業をレビューまとめ、より体系的に学習を展開していきます。これにより、ストレージエンジンの理解を深め、SQLエンジンとの連携を実現していきます。