Postgres の内部構造を読む:データベースクラスタ、データベース、およびテーブル

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 範囲の例
組み込み(初期化)ハードコードされた低い値
template1
: 1
template0
: 4
postgres
: 5
ユーザー作成テーブル、インデックスなど開始は通常 16384 以上
  • OID 1〜16383: 初期化オブジェクト用または予約済みです。
  • 共有カタログ: クラスタ全体で共有されるため、
    pg_database
    はクラスタに 1 つだけ存在します。

システムカタログ(内部構造)の役割

すべてのオブジェクトは独自の OID で識別され、システムテーブル(カタログ)に格納されています。代表的なテーブルと目的は以下の通りです。

  • pg_class
    : テーブル、ビュー、インデックスなどの「関係(Relation)」を格納します。
  • pg_database
    : クラスタ内のすべてのデータベース情報を格納します(共有)。
  • pg_index
    : インデックスに関する情報を格納します(汎用的な情報ではなく、詳細データ用)。

事実:

pg_class
は汎用情報用であり、インデックスの詳細などを格納する別テーブル(
pg_index
)を作ることで、将来的な拡張のリスクを分散しています。

拡張機能と OID

  • :
    pgvector
    のような拡張機能をインストールすると、自動的に
    pg_extension
    に追加され、新しい OID が生成されます。
  • 履歴:
    • 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}
  • 同一性:
    CREATE TABLE
    直後は、テーブルの OID とファイル名(Relfilenode)は一致します

確認手順例

  1. テーブル作成後の状態:

    SELECT oid, relname, relfilenode, relkind 
    FROM pg_class WHERE relname IN ('orders', 'orders_pkey');
    -- 結果: orders (OID: 16386) のファイル名も base/.../16386 となる
    
  2. VACUUM FULL
    による変化:

    • VACUUM
      は空きスペース整理を行い、物理ファイルを再配置します。
    • 結果: OID は変更されないが(永続 ID)、Relfilenode(ファイル名)は変化する
プロパティ説明VACUUM FULL 前後
OID論理 ID変化なし (例:
16386
)
Relfilenode物理ファイル名変化する (例:
16386
16397
)
パスディスク上の場所
base/{DB}/16386
base/{DB}/16397

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(コンパイル時に変更可)。
  • ページの構成:
    1. ヘッダー: チェックサム、フラグ、空き領域情報など。
    2. ラインポインタ: タプルの位置を示すインデックス(Offset)。
    3. タプル: 実際のデータ行(ページの最後から配置される)。

PageHeader とヘッダーの値

pageinspect
拡張機能を使って内部を確認すると、以下のように管理されている。

  • lower
    : ラインポインタ開始位置のオフセット(例:28 オフセット)。
  • upper
    : タプルデータ開始位置のオフセット(例:8072 オフセット)。
  • 有効領域:
    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. タプルの書き込みと読み取り

ページへの書き込みロジック

新しい行が追加される際、以下の処理が行われます。

  1. ラインポインタ更新:
    pd_lower
    を更新し、空き領域を示す。
  2. タプル配置: データを
    upper
    領域に書き込む(整列のためパディングがかかる)。
  3. WAL レコード: 変更内容は WAL に記録される(
    pd_lsn
    で管理)。

追加の影響

新しい行を追加すると、既存のラインポインタやタプルの物理オフセットがシフトする可能性があります。これは

page_header
lower
upper
が更新されることで表れます。

読み取り戦略

  • 全ページスキャン (Seq Scan): インデックスがない場合、全てのページのタプルを順次確認します。
  • インデックス利用 (Index Scan):
    WHERE data = '...'
    のような条件がある場合、B-tree インデックスを先に検索し、該当するページとオフセットだけを抽出して読み込みます。
-- インデックス利用例
SELECT * FROM sampletbl WHERE data = 'CCCCCCCCC';
-- 実行: 
-- 1. B-Tree で 'CCCCCCCCC' の位置を探す (Index Scan)
-- 2. インデックスのレコードからページ番号とオフセットを取得する
-- 3. 該当ページをロードし、その位置のタプルを読み取る

まとめ

このノートでは PostgreSQL 1.1-1.4 章(The Internals of PostgreSQL)の内容を検証しました。

  1. 論理構造: OID で識別されるオブジェクトとシステムカタログ(
    pg_class
    ,
    pg_database
    など)の役割を理解した。
  2. 物理構造:
    $PGDATA/base/{DB}/relfilenode
    形式のパス管理と、
    VACUUM FULL
    を通じた Relfilenode の再割り当てを確認した。
  3. テーブルスペース: シンボリックリンクによるマッピング機構を実践で確認した。
  4. ページ構造: ヘッダー、ラインポインタ、タプルの物理配置と
    ctid
    を確認した。
  5. TOAST: 2KB 以上のデータを外部テーブルへ退避する仕組みと
    varatt_external
    構造体を理解した。

今後も内部構造の深い掘り下げ記事を予定しています。

同じ日のほかのニュース

一覧に戻る →

2026/07/01 2:59

Claude Sonnet 5

## Japanese Translation: Claude Sonnet 5 は、プレミアム向けの Opus モデルに限定されていた高度な自律型機能を大幅に安価な価格で提供することで、AI のアクセシビリティにおいて大きな転換をもたらしました。これにより、性能格差は縮小しつつも、厳格な安全基準を維持しています。低廉なコストにもかかわらず、評価結果ではリスクのある行動が少ないことが示されており、Agent 型コンテキストにおいては Sonnet 4.6 よりも安全性が高く、開発などの危険なサイバーセキュリティタスクを実行する能力が限定的であるためデフォルトでサイバー防御機能が有効化されています。技術的な向上点には、初期コストを上げることなく処理効率を改善する新しいトークナイザーが含まれており、同じ入力が 1.0–1.35 倍多くのマッピングされたトークンに対応しますが、導入価格(入力/出力トークンあたり 2 ドル/10 ドル)を設定することでこの移行をほぼ費用対中立とします。2026 年 8 月 31 日以降には、標準的な価格(入力/出力トークンあたり 3 ドル/15 ドル)が適用されます。本モデルは、ブラウンフィールドコードの保守、多段階のソフトウェアエンジニアリング、法務調査など複雑なワークフローにおいて卓越したパフォーマンスを発揮します。チャット、Cowork、Claude Code、プラットフォーム全体におけるレート制限を引き上げることで、高度なエフォートレベルに伴う高いトークン利用量を対応可能です。最近のベンチマークスコアの見直しは、実際の品質低下を意味するものではなく、評価方法の更新によるものです。例えば、「コスト対性能チャート」の更新(変更ログ:2026 年 6 月 30 日)や、「Humanity's Last Exam」と OSWorld-Verified 評価におけるスコアの再計算は、標準的な手法を用いた実世界でのパフォーマンスをより正確に反映しています。Sonnet 5 は、無料プランからエンタープライズまでのすべてのサブスクリプションレベルで最適なデフォルト選択となり、広くユーザー層の即時かつ安全な導入を可能にします。

2026/07/01 0:44

Claude Code がリクエストに対してステガノグラフィーを用いて暗記している

## Japanese Translation: 地元の Claude Code バージョン 2.1.196 の最近の検査により、特定の条件下でシステムがデベロッパーのプロンプトに暗黙的に隠しデータを注入するセキュリティ慣行が発見されました。これは透明性の高い対策ではなく裏口を介したシグナルに依存することで信頼性を損ない、`ANTHROPIC_BASE_URL` が設定されており、かつシステムの時-zone が中国と一致するか、または特定の API ホスト名が検出された場合にのみコードがトリガーされます。これらの条件下で、プロンプトの句読点を改変—具体的には "Today's" のアポストロフィを、および日付の区切り文字を一括線からスラッシュに変更する—with 見えないユニコードマーカーを埋め込む。さらに、バインaries 内に base64 文字列として保存されたドメインおよびキーワードリスト(「DeepSeek」や「Zhipu」などの用語を含む)は XOR でデコードされ、潜在的な API リセラーまたはモデル蒸留攻撃を検出します。後端の脅威(無権限のプロキシなど)をブロックすることを意図されていますが、この論理はカスタムセットアップを使用する正当な開発者を懲罰し、AI に送信されるコンテキストに検出データを直接エンコードします。幸運にも、これらの特定の設定がないユーザーは変更を見ませんが、トリガー条件を満たす者はパッチが適用されるまで改変されたプロンプトを受けます。著者は、プロンプトの句読点にシグナルを隠すことは開発者の信頼を侵害し、セキュリティには明示的なポリシーに依存すべきだと主張しています。この機能をバイパスするのは、ホスト名の修正、時-zone の調整、またはバインりパッチ適用によって容易であると考えられています。

2026/07/01 6:29

脳波から単語へ:手術を必要としない新たなコミュニケーションの道筋

## Japanese Translation: 研究者らが、非侵襲的脳記録からリアルタイムでテキストへの変換を行えるエンドツーエンドの AI パイプライン「Brain2Qwerty v2」を公開しました。本システムは、磁気共鳴法(MEG)を用いて 10 時間にわたって 9 名の被験者から記録された約 2 万 2,000 の文を学習データとし、生信号に対してエンドツーエンドの深層学習を適用するとともに、ノイズの多い神経入力を活用するため到大規模言語モデルを微調整しています。一般化単語精度は 61% に達しており、これにより他の非侵襲的手法で一般的であった約 8% より著しく改善されました。最適な条件下では個人ごとの性能は最大 78% に向上し、すべての文の半分以上が 1 つ以下の子音エラーで復号化されました。パフォーマンスはデータ量に対して対数線形に拡張するため、規模拡大だけでもさらなる進歩が可能であることが示唆されます。v1 および v2 の完全な学習コードは、パートナー組織である BCBL よりも提供された v1 データセットとともに公開され、AI エージェントがパイプラインの最適化を支援し、最終的な構成はエンジニアによって手動で選択されました。この研究成果は、500 万ドル規模のデジタル・ブレイン・プロジェクトの一環として、Tribev2(知覚)、NeuralSet(拡張処理)、NeuralBench(評価)などとの並行して、オープンな基礎脳のモデルを推進しています。目的は、侵襲性脳プロスタネースと非侵襲的なアクセシビリティのギャップを埋め、脳病変の影響を受けた数百万人の患者に対してより迅速な診断・治療を可能にしつつ、孤立した研究活動を超えてオープン神経科学を進めることです。