Chapter 7. ページファイル

データベースファイルのページフォーマットの説明です。

この節では PostgreSQL のテーブルおよびインデックスで使われるページフォーマットの概略について説明します。 (インデックスアクセスメソッドはこのページフォーマットを使用する必要はありません。 現在では、すべてのインデックスメソッドがこの基本フォーマットを使用してはいます。しかし、インデックスメタページに保持されるデータは通常、アイテムレイアウトルールに正確には従っていません。) TOAST のテーブルとシーケンスは、通常のテーブルと同様にフォーマットされています。

以下の説明では 1 バイト は 8 ビットからなることを前堤としています。 更に、アイテムという単語は、ページに格納される個別のデータ値のことを指しています。 テーブル内では、アイテムはタプル(行)です。インデックス内では、アイテムはインデックスのエントリです。

Table 7-1 はページの基本レイアウトを示しています。 各ページには 5 つの部分があります。

Table 7-1. サンプルページレイアウト

アイテム説明
PageHeaderData長さは 20 バイト。フリースペースポインタを含む、ページについての一般情報です。
ItemPointerData実際のアイテムを指す(オフセットと長さの)ペアの配列です。
フリースペース割り当てられていない空間です。 すべての新規タプルはここから割り当てられます(通常は最後から)。
アイテム実際のアイテムそのものです。
特別な空間インデックスアクセスメソッド特有のデータです。 異なるメソッドは異なるデータを格納します。 通常のテーブルでは空です。

それぞれのページの最初の 20 バイトはページヘッダ(PageHeaderData)から構成されています。 そのフォーマットを Table 7-2 に説明してあります。 最初の 2 つのフィールドは WAL に関連する項目を扱います。 その後に、2 バイトの整数フィールドが 3 つ続きます(pd_lowerpd_upperpd_special)。 これらは、割り当てられていない空間の始まり、割り当てられていない空間の終わり、そして特別な空間の始まりのバイトオフセットを表しています。

Table 7-2. PageHeaderData のレイアウト

フィールド長さ説明
pd_lsnXLogRecPtr8 バイトLSN: xlog の最後のバイトのつぎのバイト
pd_suiStartUpID4 バイト最終変更の SUI (現在ではヒープ AM でのみ使用)
pd_lowerLocationIndex2 バイトフリースペースの始まりに対するオフセット。
pd_upperLocationIndex2 バイトフリースペースの終わりに対するオフセット。
pd_specialLocationIndex2 バイト特別な空間の始まりに対するオフセット。
pd_pagesize_versionuint162 バイトページサイズおよびレイアウトのバージョン番号の情報。

詳細情報については src/include/storage/bufpage.h を参照してください。

特別な空間は、ページ初期化時にページの最後に割り当てられる区域で、アクセスメソッド特有の情報を持っています。 ページヘッダの最後の 2 バイトである pd_pagesize_version は、ページサイズとバージョンインジケータの両方を格納します。 PostgreSQL 7.3 以降のバージョン番号は 1 です。それより前のリリースのバージョン番号は 0 です。(基本的なページレイアウトやヘッダのフォーマットは変更されていませんが、ヒープタプルヘッダのレイアウトが変更されました。) ページサイズは基本的に、照合用としてのみ存在しています。同一インストールでの複数のページサイズはサポートされていません。

ページヘッダに続くのはアイテム識別子(ItemIdData)です。識別子ごとに 4 バイトを必要とします。アイテム識別子は、アイテムが開始されるバイトオフセット、バイトで表されたその長さ、そして表示に影響する属性ビットの集合を持っています。 新しいアイテム識別子は必要に応じて、割り当てられていない空間の最初から割り当てられます。 存在するアイテム識別子の数は、新しい識別子を割り当てるために増加される pd_lower を見ることで判断できます。 アイテム識別子は解放されるまで動かされることがないので、アイテム自体がフリースペースをコンパクトにするためにページ上で移動される場合でも、そのインデックスはアイテムを参照するために長期にわたって使うことができます。 実際、PostgreSQLが作る、アイテムへのポインタ(ItemPointerCTIDともいいます)はページ番号とアイテム識別子のインデックスによって構成されています。

アイテムそれ自体は、割り当てられていない空間の最後から順番に割り当てられた空間に格納されます。 正確な構造は、テーブルに何を含めるかによって異なります。 テーブルとシーケンスの両方が、以下で説明する HeapTupleHeaderData という構造を使用します。

最後のセクションは、アクセスメソッドが格納しようとするものを何でも含めることのできる「特別なセクション」です。 通常のテーブルでは、これはまったく使用されません(ページサイズを同じにするために pd_special を設定することで示されます)。

テーブルのタプルはすべて、同じ方法で構成されています。 固定サイズのヘッダ(ほとんどのマシンで 23 バイトを占有します)があり、その後にオプションのヌルビットマップ、オプションのオブジェクト ID フィールド、および他のユーザデータが続きます。 ヘッダについては Table 7-3 で説明しています。 実際のユーザデータ(タプルのフィールド)は、常にプラットフォームの MAXALIGN 距離の倍数である t_hoff で示されるオフセットから始まります。 ヌルビットマップは HEAP_HASNULL ビットが t_infomask 内で設定されている場合にのみ存在します。 存在する場合は、固定ヘッダのすぐ後ろから始まり、データ列ごとに 1 ビットとするのに十分なバイト数を占有します(合計すると、t_natts のビット数となります)。 このビットのリスト内では、1 ビットはヌルでないことを示します。0 ビットはヌルです。 このビットマップが存在しない場合、すべての列がヌルでないとみなされます。 オブジェクト ID は HEAP_HASOID ビットが t_infomask 内で設定されている場合にのみ存在します。 存在する場合、これは t_hoff 境界の直前に現れます。 t_hoff を MAXALIGN の倍数とするために必要なパッドはすべて、ヌルビットマップとオブジェクト ID の間に現れます。(このことにより、オブジェクト ID のアライメントが確実に適切になります。)

Table 7-3. HeapTupleHeaderData のレイアウト

フィールド長さ説明
t_xminTransactionId4 バイトXID スタンプを挿入
t_cminCommandId4 バイトCID スタンプを挿入(t_xmax とオーバーレイ)
t_xmaxTransactionId4 バイトXID スタンプを削除
t_cmaxCommandId4 バイトCID スタンプを削除(t_xvac とオーバーレイ)
t_xvacTransactionId4 バイトVACUUM 操作移動タプルの XID
t_ctidItemPointerData6 バイトこのタプルまたは最新タプルの現在の TID
t_nattsint162 バイト属性の数
t_infomaskuint162 バイトさまざまなフラグ
t_hoffuint81 バイトユーザデータに対するオフセット

詳細情報については src/include/access/htup.h を参照してください。

実際のデータの解釈は、他のテーブル(ほとんどの場合、pg_attribute)から取得された情報でのみ行うことができます。 特定のフィールドは、attlen および attalign です。 フィールドの幅が固定されていて NULL が存在しない場合を除き、特定の属性を直接取得する方法はありません。 この仕組みはすべて、heap_getattrfastgetattr および heap_getsysattr の関数にラップされています。

データを読むためには、それぞれの属性を順番に検査する必要があります。 まず、ヌルビットマップに従ってフィールドが NULL かどうかをチェックします。 もし NULL であれば、次に進みます。 次に、アライメントが正しいことを確認してください。 フィールドの幅が固定されていれば、すべてのバイトが単純に配置されます。 可変長のフィールド(attlen == -1)の場合はもう少し複雑で、varattrib 可変長構造が使用されます。 フラグによって、データはインライン、圧縮済み、または別のテーブル(TOAST)のいずれかとなります。