はじめに
本稿では、openGaussデータベースのストレージエンジンにおけるB木(B-tree)インデックス処理モジュールを対象とし、cbtree.cpp ファイル内に定義された主要な関数群について詳細に解説する。特に、インデックス走査の可否判定、オプション処理、タプル取得、およびバッチ挿入処理の実装ロジックに焦点を当てる。
B-treeインデックスの基本構造
B-treeは、大規模データセットに対する効率的な検索・範囲照会を実現するバランス型マルチウェイ探索木である。データベースシステムにおいて、等値比較や範囲条件(=, >, <, >=, <=)に対して高い性能を発揮するため、PostgreSQL系譜のopenGaussでもデフォルトのインデックスタイプとして採用されている。
主な特徴:
- すべてのリーフノードが同じ深さを持つ(平衡性)
- ノードあたり最大
2t-1個のキーを持ち、内部ノードはt個以上の子を持つ(t:最小次数) - ディスクI/Oを最小化するため、ブロックサイズに基づいてノード構造が最適化される
- 検索、挿入、削除の計算量はいずれも
O(log N)
関数実装の詳細
1. インデックス走査の可否を返す関数
この関数は、B-treeインデックスがタプルの取得をサポートしているかどうかを示すフラグを返す。現在の実装では常に真を返すことで、標準的な走査操作が可能であることを宣言している。
Datum cbtree_can_support_scan(PG_FUNCTION_ARGS)
{
PG_RETURN_BOOL(true);
}
2. インデックスオプションの処理関数
インデックス作成時に指定されたオプションパラメータを検証・変換する役割を持つ。バリデーションが必要な場合はdefault_reloptions関数を通じて形式チェックを行い、結果をバイナリ形式(bytea*)で返却する。
Datum process_cbtree_options(PG_FUNCTION_ARGS)
{
Datum raw_options = PG_GETARG_DATUM(0);
bool need_validation = PG_GETARG_BOOL(1);
bytea* processed_opts = default_reloptions(raw_options, need_validation, RELOPT_KIND_CBTREE);
if (processed_opts == NULL)
PG_RETURN_NULL();
PG_RETURN_BYTEA_P(processed_opts);
}
3. インデックスからのタプル取得関数
スキャンコンテキストと走査方向を受け取り、内部関数_bt_gettuple_internalを呼び出して次の一致タプルを取得する。スキャン記述子が無効な場合はエラーを発生させる。
Datum fetch_index_tuple(PG_FUNCTION_ARGS)
{
IndexScanDesc scan_context = (IndexScanDesc)PG_GETARG_POINTER(0);
ScanDirection direction = (ScanDirection)PG_GETARG_INT32(1);
if (scan_context == nullptr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid scan descriptor in B-tree tuple retrieval")));
bool success = _bt_gettuple_internal(scan_context, direction);
PG_RETURN_BOOL(success);
}
4. ベクターバッチからのB木挿入処理
列指向実行エンジンから渡されたベクターバッチを処理し、各レコードをB-treeスプール領域へ挿入する。NULL値の判定、Datumへの変換、TID(タプル識別子)の抽出を一括で行う。
static void insert_batch_into_btree(
VectorBatch* batch,
BTBuildState& state,
IndexInfo* index_info,
double& total_tuples,
Datum* temp_values,
bool* null_flags,
ScalarToDatum* converters)
{
int row_count = batch->m_rows;
ScalarVector* columns = batch->m_arr;
ScalarVector* tid_vector = batch->GetSysVector(SelfItemPointerAttributeNumber);
total_tuples += row_count;
for (int row_idx = 0; row_idx < row_count; ++row_idx) {
// 各インデックス対象カラムを処理
for (int i = 0; i < index_info->ii_NumIndexAttrs; ++i) {
int col_index = index_info->ii_KeyAttrNumbers[i] - 1;
if (columns[col_index].IsNull(row_idx)) {
null_flags[i] = true;
} else {
null_flags[i] = false;
temp_values[i] = converters[i](columns[col_index].m_vals[row_idx]);
}
}
// TIDを取得してスプールに登録
ItemPointer tuple_id = reinterpret_cast<ItemPointer>(&(tid_vector->m_vals[row_idx]));
_bt_spool(state.spool, tuple_id, temp_values, null_flags);
state.indtuples += 1;
}
}
Datum型の役割と内部表現
openGaussのC言語レベルAPIでは、異なるデータ型を統一的に扱うためにDatum型が広く使用される。その定義は以下の通り。
typedef uintptr_t Datum;
uintptr_tはポインタサイズの符号なし整数型であり、ポインタやスカラー値を直接格納できる。これにより、関数間での値渡しがポインタ参照ではなく整数コピーとして行われ、処理効率が向上する。
主な特徴
- 汎用性:整数、浮動小数点数、文字列ポインタなどを同一型で扱える
- 型変換マクロ:
DatumGetInt32()、Float8GetDatum()など豊富な変換関数が提供される - 関数インターフェースの統一:演算子関数やインデックスメソッドが共通のシグネチャで実装可能
- 拡張性:ユーザー定義型に対してもDatum経由で操作が可能
例えば、真偽値をDatumに変換するマクロは以下のように定義される。
#define BoolGetDatum(x) ((Datum)((x) ? 1 : 0))
この設計により、コンパイル時展開によるオーバーヘッド低減と、柔軟なデータ処理が両立されている。