★PostgreSQLカンファレンス2024 12月6日開催/チケット販売中★
他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

68.6. データベースページのレイアウト

本節ではPostgreSQLのテーブルおよびインデックスで使われるページ書式の概略について説明します。 [15] TOASTのテーブルとシーケンスは、通常のテーブルと同様に整形されています。

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

テーブルとインデックスはすべて、固定サイズ(通常8キロバイト。サーバのコンパイル時に異なるサイズを設定可能)のページの集まりとして格納されます。 テーブルでは、すべてのページは論理上等価です。 したがって、あるアイテム(行)はどのページにでも格納することができます。 インデックスでは、初めのページは通常、制御用の情報を保持するメタページとして予約されます。 また、インデックスではインデックスアクセスメソッドに依存した様々なページ種類があります。

表 68.2はページの全体的なレイアウトを示しています。 各ページには5つの部分があります。

表68.2 ページレイアウト全体

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

それぞれのページの最初の24バイトはページヘッダ(PageHeaderData)から構成されています。 その書式を表 68.3にて説明します。 最初のフィールドは、このページに関連する最も最近のWAL項目を表しています。 2番目のフィールドにはdata checksumsが有効な場合にページチェックサムが格納されています。 次にフラグビットを含む2バイトのフィールドがあります。 その後に2バイトの整数フィールドが3つ続きます(pd_lowerpd_upperpd_special)。 これらには、割り当てられていない空間の始まり、割り当てられていない空間の終わり、そして特別な空間の始まりのバイトオフセットが格納されています。 ページヘッダの次の2バイトであるpd_pagesize_versionは、ページサイズとバージョン指示子の両方を格納します。 PostgreSQL 8.3以降のバージョン番号は4、PostgreSQL 8.1と8.2のバージョン番号は3、PostgreSQL 8.0のバージョン番号は2、PostgreSQL 7.3と7.4のバージョン番号は1です。 それより前のリリースのバージョン番号は0です (ほとんどのバージョン間で基本的なページレイアウトやヘッダの書式は変更されていませんが、ヒープ行ヘッダのレイアウトが変更されました)。 ページサイズは基本的に照合用としてのみ存在しています。 同一インストレーションでの複数のページサイズはサポートされていません。 最後のフィールドはそのページの切り詰めが有益かどうかを示すヒントです。 これはページ上で切り詰められていないもっとも古いXMAXが追跡するものです。

表68.3 PageHeaderDataのレイアウト

フィールド長さ説明
pd_lsnPageXLogRecPtr8バイトLSN: このページへの最終変更に対応するWALレコードの最後のバイトの次のバイト
pd_checksumuint162バイトページチェックサム
pd_flagsuint162バイトフラグビット
pd_lowerLocationIndex2 バイト空き領域の始まりに対するオフセット
pd_upperLocationIndex2バイト空き領域の終わりに対するオフセット
pd_specialLocationIndex2バイト特別な空間の始まりに対するオフセット
pd_pagesize_versionuint162バイトページサイズおよびレイアウトのバージョン番号の情報
pd_prune_xidTransactionId4バイトページ上でもっとも古い切り詰められていないXMAX。存在しなければゼロ。

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

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

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

最後のセクションは、アクセスメソッドが格納しようとするものを何でも含めることのできる特別なセクションです。 例えば、B-treeインデックスは、そのページの両隣のページへのリンク、ならびに、インデックス構造体に関連したその他の何らかのデータを持ちます。 通常のテーブルではこれはまったく使用されません(ページサイズを同じにするためにpd_specialを設定することで示されます)。

図 68.1は、これらの部分がページ内でどのようにレイアウトされているかを図解しています。

図68.1 ページレイアウト


68.6.1. テーブル行のレイアウト

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

表68.4 HeapTupleHeaderDataのレイアウト

フィールド長さ説明
t_xminTransactionId4バイト挿入XIDスタンプ
t_xmaxTransactionId4バイト削除XIDスタンプ
t_cidCommandId4バイト挿入、削除の両方または片方のCIDスタンプ(t_xvacと共有)
t_xvacTransactionId4バイト行バージョンを移すVACUUM操作用XID
t_ctidItemPointerData6バイトこの行または最新バージョンの行の現在のTID
t_infomask2uint162バイト属性の数と各種フラグビット
t_infomaskuint162バイト様々なフラグビット
t_hoffuint81バイトユーザデータに対するオフセット

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

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

データを読むためには、それぞれの属性を順番に検査する必要があります。 まず、NULLビットマップに従ってフィールドがNULLかどうかを検査します。 もしNULLであれば、次に進みます。 次に、位置揃えが正しいことを確認してください。 フィールドの幅が固定されていれば、すべてのバイトが単純に配置されます。 可変長のフィールド(attlen == -1)の場合はもう少し複雑です。 可変長のデータ型はすべて、格納する値の長さといくつかのフラグビットを持つstruct varlenaという共通ヘッダ構造体を共有します。 フラグによって、データは行内、または別のテーブル(TOAST)のいずれかとなったり、圧縮済みとなったりします (68.2を参照してください)。



[15] 実際には、テーブルアクセスメソッドもインデックスアクセスメソッドも、このページ書式を使用する必要はありません。 heapテーブルアクセスメソッドは常にこの書式を使用します。 既存のすべてのインデックスメソッドも、この基本書式を使用しています。しかし、インデックスメタページに保持されるデータは通常、アイテムレイアウト規則に従っていません。