
2026/06/29 21:59
Postgres の内部構造を読む:データベースクラスタ、データベース、およびテーブル
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本テキストは、河野昇氏らの参考書籍を通じてクラスター設計、ストレージ配置、オブジェクト管理を学び、PostgreSQL の内部アーキテクチャに対する理解を深める方法を説明しています。著者は、システムオブジェクトとユーザーオブジェクトの重要な区別(内蔵データベースに使用される予約された OID、および OID 処理における歴史的な変更:必須(≤8.0)、オプション(8.1–12)、削除(≥12))を強調し、$PGDATA 内の標準的なファイルディレクトリ構造について詳述しています。これは、データを格納する
base/ ディレクトリや、テーブルスペース用シンボルリンクによって命名されたサブディレクトリなどです。また、タプルストレージのメカニズムについても明確にしており、2KB より大きな値は pg_toast にある TOAST で管理され、タUPLE はページの末尾に配置され、空き領域と可視性を追跡するための補助ファイル(*_fsm, _vm)が存在することなどを説明しています。これらの内部構造を理解することは、生源代码を読むことなく、パフォーマンスボトルネックを予測したり、VACUUM FULL のような重負荷なメンテナンスを実行するタイミングを判断したり、カスタムテーブルスペースを作成したり、インデックス戦略を最適化したりといった管理者の意思決定に役立ちます。本文
ポストgreSQL 内部構造探求:学習ノートとコード検証記録
ポストgreSQL の内部構造(Internals)を深く理解するための学習ノートです。著者の学習へのコミットメントを保証し、読んだ内容を内面化するために記述されました。広瀬弘之氏による素晴らしい参照資料に基づいています。
元の情報は InterDB の PostgreSQL 解説 を元としています。
1. データベースクラスタの論理的構造
クラスタの定義
- 意味: 「複数のノード」という意味ではなく、単一の PostgreSQL インスタンスによって管理されるデータベースのグループを指します。
- データベース: テーブル、インデックス、ビューなどのオブジェクトの集合体です。また、データベース自体も OID(Object ID) で識別される一種のオブジェクトです。
組み込みオブジェクトと OID の割り当てルール
PostgreSQL では、OID はオブジェクトに自動的に割り当てられます。
| オブジェクトタイプ | 特徴 | OID 範囲の例 |
|---|---|---|
| 組み込み(初期化) | ハードコードされた低い値 | : 1: 4: 5 |
| ユーザー作成 | テーブル、インデックスなど | 開始は通常 16384 以上 |
- OID 1〜16383: 初期化オブジェクト用または予約済みです。
- 共有カタログ: クラスタ全体で共有されるため、
はクラスタに 1 つだけ存在します。pg_database
システムカタログ(内部構造)の役割
すべてのオブジェクトは独自の OID で識別され、システムテーブル(カタログ)に格納されています。代表的なテーブルと目的は以下の通りです。
: テーブル、ビュー、インデックスなどの「関係(Relation)」を格納します。pg_class
: クラスタ内のすべてのデータベース情報を格納します(共有)。pg_database
: インデックスに関する情報を格納します(汎用的な情報ではなく、詳細データ用)。pg_index
事実:
pg_class は汎用情報用であり、インデックスの詳細などを格納する別テーブル(pg_index)を作ることで、将来的な拡張のリスクを分散しています。
拡張機能と OID
- 例:
のような拡張機能をインストールすると、自動的にpgvector
に追加され、新しい OID が生成されます。pg_extension - 履歴:
- PG <= 8.0: すべてのテーブルに OID が割り当てられた。
- 8.1 ~ 12: オートOID は「オプション(Opt-in)」になり、
で有効化する必要があった。WITH OIDS - PG >= 12: この機能は完全に削除された。
2. データベースクラスタの物理構造
データディレクトリとパス
PostgreSQL クラスタは、データディレクトリに全データを格納します。このパスは環境変数
$PGDATA で指定されます。
- デフォルト場所:
または/var/lib/pgsql/data/var/lib/postgresql/<version>/main - 初期化ツール:
がディレクトリを作成・設定する役割を担います。initdb
ファイルシステムの構成:
$PGDATA/ ├── base/ # データベースごとに 1 つ (base/{OID}) │ └── {OID}/ # テーブル & インデックスの物理ファイル ├── global/ # クラスタ全体のカタログ(例:pg_class のデータ) ├── pg_wal/ # WAL セグメントファイル(復元用) ├── pg_xact/ # トランザクションコミット状態 (clog) ├── pg_tblspc/ # テーブルスペースへのシンボリックリンク ├── PG_VERSION # 現在のメジャーバージョン番号 └── postgresql.conf # メイン設定ファイル
OID と物理ファイル名の関係:Relfilenode
PostgreSQL では、論理 ID(OID)と物理ファイル名(
relfilenode)が密接に関連しています。
- パスの形式:
base/{Database_OID}/{Table_RelFileNode} - 同一性:
直後は、テーブルの OID とファイル名(Relfilenode)は一致します。CREATE TABLE
確認手順例
-
テーブル作成後の状態:
SELECT oid, relname, relfilenode, relkind FROM pg_class WHERE relname IN ('orders', 'orders_pkey'); -- 結果: orders (OID: 16386) のファイル名も base/.../16386 となる -
による変化:VACUUM FULL
は空きスペース整理を行い、物理ファイルを再配置します。VACUUM- 結果: OID は変更されないが(永続 ID)、Relfilenode(ファイル名)は変化する。
| プロパティ | 説明 | VACUUM FULL 前後 |
|---|---|---|
| OID | 論理 ID | 変化なし (例: ) |
| Relfilenode | 物理ファイル名 | 変化する (例: → ) |
| パス | ディスク上の場所 | → |
3. テーブルスペース(Tablespaces)の仕組み
ユーザーはデータ格納場所を別のディレクトリに設定できます。
- pg_default: 通常のオブジェクト用(デフォルト)。
- pg_global: クラスタ全体で共有されるオブジェクト用。
- 命名テーブルスペース: ユーザーが定義した別空間。
シンボリックリンクによる管理
PostgreSQL は、実パスを直接使わず、
pg_tblspc/ ディレクトリ内にシンボリックリンクを作成して管理します。
# 外部ディレクトリの作成 mkdir -p /Users/burak/pg-tblspc-extra # テーブルスペースの定義 CREATE TABLESPACE extra_space LOCATION '/Users/burak/pg-tblspc-extra'; # 内部マッピング確認 ls -la $PGDATA/pg_tblspc/16401 -> /Users/burak/pg-tblspc-extra
- グローバルテーブルスペース:
に格納。global/{relfilenode} - デフォルトテーブルスペース:
に格納。base/{DB_OID}/{relfilenode} - 命名テーブルスペース:
のようなマッピングが内部で行われる。pg_tblspc/{SpcOid}/PG_xx_yyy/{DB_OID}/{relfilenode}
注意: インデックスは通常、親テーブルと同じテーブルスペースを使う(
USING INDEX TABLESPACE を指定しない限り)。
4. ヒープテーブルの構造とページ管理
PostgreSQL はデータを ページ化 して格納しています。
- ページサイズ: デフォルト 8KB(コンパイル時に変更可)。
- ページの構成:
- ヘッダー: チェックサム、フラグ、空き領域情報など。
- ラインポインタ: タプルの位置を示すインデックス(Offset)。
- タプル: 実際のデータ行(ページの最後から配置される)。
PageHeader とヘッダーの値
pageinspect 拡張機能を使って内部を確認すると、以下のように管理されている。
: ラインポインタ開始位置のオフセット(例:28 オフセット)。lower
: タプルデータ開始位置のオフセット(例:8072 オフセット)。upper- 有効領域:
で計算される空きサイズ。upper - lower
ctid: 物理的な行の住所
任意の行を物理的に特定するためのシステムカラム
ctid が存在します。
ctid = (page_number, offset_in_page) という構造を持ちます。
| カラム | 意味 |
|---|---|
| Block Number | ページ番号(0 から始まる) |
| Offset Number | 当該ページ内のタプルオフセット |
5. TOAST(大属性ストレージ技術)
PostgreSQL は、1 ページを超えるような大きな値(
text, bytea, JSON など)を直接格納できません。TOAST テーブルを使用して外部に退避させます。
- 閾値: データサイズが 2KB を超えると TOAST 化されます。
- 仕組み: メインテーブルには「TOAST テーブル内の OID ポインタ」のみを格納し、本体データは別の
テーブルに置きます。pg_toast
TOAST テーブルの構成
pg_toast スキーマ下(例:pg_toast_16483)で管理されます。
- chunk_id: データブロック ID。
- chunk_seq: チャンク内のシーケンス番号。
- chunk_data: 実際のデータバイト列(圧縮された場合がある)。
メインテーブルと TOAST テーブルの関連性
メインテーブルの行ヘッダーには、外部データを指すポインタが格納されます。
typedef struct varatt_external { int32 va_rawsize; // 元のデータサイズ(含ヘッダー) uint32 va_extinfo; // 保存されたサイズ(圧縮情報含む) Oid va_valueid; // TOAST テーブル内の値の ID Oid va_toastrelid; // TOAST テーブルの OID } varatt_external;
確認クエリ例: TOAST 化された行(例:
id=2)の情報を取得。
SELECT id, length(data) AS logical_bytes, pg_column_toast_chunk_id(data) AS va_valueid -- TOAST テーブル内の OID FROM toast_demo WHERE id = 2; -- 結果: chunk_id=16492 (TOAST テーブル内の特定レコードを指す)
6. タプルの書き込みと読み取り
ページへの書き込みロジック
新しい行が追加される際、以下の処理が行われます。
- ラインポインタ更新:
を更新し、空き領域を示す。pd_lower - タプル配置: データを
領域に書き込む(整列のためパディングがかかる)。upper - WAL レコード: 変更内容は WAL に記録される(
で管理)。pd_lsn
追加の影響
新しい行を追加すると、既存のラインポインタやタプルの物理オフセットがシフトする可能性があります。これは
page_header の lower と upper が更新されることで表れます。
読み取り戦略
- 全ページスキャン (Seq Scan): インデックスがない場合、全てのページのタプルを順次確認します。
- インデックス利用 (Index Scan):
のような条件がある場合、B-tree インデックスを先に検索し、該当するページとオフセットだけを抽出して読み込みます。WHERE data = '...'
-- インデックス利用例 SELECT * FROM sampletbl WHERE data = 'CCCCCCCCC'; -- 実行: -- 1. B-Tree で 'CCCCCCCCC' の位置を探す (Index Scan) -- 2. インデックスのレコードからページ番号とオフセットを取得する -- 3. 該当ページをロードし、その位置のタプルを読み取る
まとめ
このノートでは PostgreSQL 1.1-1.4 章(The Internals of PostgreSQL)の内容を検証しました。
- 論理構造: OID で識別されるオブジェクトとシステムカタログ(
,pg_class
など)の役割を理解した。pg_database - 物理構造:
形式のパス管理と、$PGDATA/base/{DB}/relfilenode
を通じた Relfilenode の再割り当てを確認した。VACUUM FULL - テーブルスペース: シンボリックリンクによるマッピング機構を実践で確認した。
- ページ構造: ヘッダー、ラインポインタ、タプルの物理配置と
を確認した。ctid - TOAST: 2KB 以上のデータを外部テーブルへ退避する仕組みと
構造体を理解した。varatt_external
今後も内部構造の深い掘り下げ記事を予定しています。