なぜPostgreSQLの「列の順序」が、ディスクサイズを左右するのか?
現場でコードレビューをしていると、テーブル設計に関して「とりあえず使いそうなカラムを、思いついた順に並べておこう」という設計に出くわすことがよくあります。気持ちはわかります。でも、それが数千万件、数億件という規模になったとき、ストレージコストやI/O負荷で後悔することになるんです。
今日は、PostgreSQLの「データアライメントとパディング」について話をしましょう。これを知っているだけで、DB設計の解像度が一段と上がりますよ。
—
「詰め込み」の効率を意識してる?
まずは、CPUがデータを読み込むときのルールを思い出してください。CPUはメモリ上のデータを効率よく読み込むために、データ型ごとに決められた「境界(境界値)」でアクセスしたがります。
PostgreSQLのテーブルもこれと同じで、特定のデータ型は「アライメント(整列)」という制約を受けています。例えば、8バイトのデータ(`bigint`など)は8バイト境界に配置される必要がある、といったルールです。
このとき、空いた隙間を埋めるために挿入されるのが「パディング(詰め物)」です。
想像してみてください。大きな荷物(8バイト)の隣に小さな荷物(1バイト)を置くとき、CPUの読み込みの都合で、その間に「無駄な空白」ができてしまう状態。これ、テーブル全体で積み重なると、無視できない無駄になるんです。
具体的に見てみよう
例えば、こんなテーブル定義があったとします。
CREATE TABLE bad_table (
is_active boolean, — 1バイト
id bigint, — 8バイト
is_admin boolean — 1バイト
);
一見何の問題もなさそうですよね? でも、物理層ではこうなっています。
1. `is_active` (1バイト)
2. [7バイトのパディング] ← ここが無駄!
3. `id` (8バイト)
4. `is_admin` (1バイト)
5. [7バイトのパディング] ← ここも無駄!
合計で24バイト消費されています。本来なら「1 + 8 + 1 = 10バイト」で収まるはずなのに、パディングのせいで物理サイズが膨らんでいるわけです。これが数億行あれば……想像するだけでゾッとしますよね。
最適化の鉄則:大きいものから順に並べる
この無駄を避けるための黄金律はシンプルです。「データ型のサイズが大きい順に並べる」こと。これだけで、パディングを最小限に抑えられます。
さっきのテーブルを書き直すとこうなります。
CREATE TABLE good_table (
id bigint, — 8バイト
is_active boolean, — 1バイト
is_admin boolean — 1バイト
— ここで合計10バイト + 6バイトのパディング = 16バイト
);
これだけで、24バイトだった行サイズが16バイトまで削れました。8バイトの削減ですが、テーブル全体で見れば30%以上の削減です。キャッシュ効率も上がり、結果としてクエリの速度向上にも直結します。
実務で役立つヒント
1. `pg_column_size` を活用せよ
実務で「本当にこれで合ってるのか?」と不安になったら、実際に確かめるのが一番です。`pg_column_size()` を使えば、その行がどれだけの容量を食っているか確認できます。
2. 可変長データ(text, varchar)は最後へ
固定長データ(`bigint`, `integer`, `timestamp`など)を前に持ってきたら、`text`や`jsonb`などの可変長データは後ろに回すのがセオリーです。
3. やりすぎには注意
もちろん、可読性やメンテナンス性も大事です。すべてのテーブルでミリ単位の調整をする必要はありません。「アクセス頻度が極端に高いテーブル」や「データ量が爆発的に増えることが分かっているテーブル」に絞って最適化を行うのが、僕らエンジニアの賢い立ち回り方です。
—
まとめ
データベース設計は、単に「データを保存する箱を作る」ことではありません。ハードウェアの特性を理解して、いかに効率よく出し入れするかを考えるパズルです。
「とりあえず定義」を卒業して、物理構造を意識した設計ができるようになると、後輩から「あの人の設計はパフォーマンスが安定している」と一目置かれるようになりますよ。
ぜひ、次のテーブル設計のタイミングで、一度カラムの順番を見直してみてください。たかがパディング、されどパディング。この「こだわり」が、一流のエンジニアへの第一歩です。
コメント