✂️
概念 #データ設計 #垂直分割 #パーティショニング #スキーマ設計 #パフォーマンス #DDIA 📚 データ志向アプリケーション設計(DDIA)

垂直分割(Vertical Partitioning)

テーブルのカラムを分割して格納するVertical Partitioningの設計。頻繁にアクセスされるカラムと大きなデータを分離し、キャッシュ効率とクエリパフォーマンスを改善する方法を理解する

定義

垂直分割(Vertical Partitioning):1つのテーブルのカラムを複数のテーブルに分割する手法。水平分割(シャーディング)が行を分割するのに対し、垂直分割はカラムを分割する。

水平分割(Horizontal):
  orders → orders_2024, orders_2023, orders_2022(行で分割)

垂直分割(Vertical):
  users → users_core + users_profile + users_settings(列で分割)

なぜ垂直分割が必要か

問題:幅広いテーブルのパフォーマンス

-- 全カラムを持つusersテーブル(50カラム以上)
CREATE TABLE users (
  id UUID PRIMARY KEY,
  email VARCHAR(255),         -- 頻繁にアクセス
  name VARCHAR(100),          -- 頻繁にアクセス
  created_at TIMESTAMPTZ,     -- 頻繁にアクセス
  
  bio TEXT,                   -- たまにアクセス(数KB)
  avatar_url TEXT,            -- たまにアクセス
  
  notification_email BOOLEAN, -- 設定系(滅多にアクセスしない)
  notification_push BOOLEAN,
  theme VARCHAR(20),
  language VARCHAR(10),
  timezone VARCHAR(50),
  -- ...40カラム続く
);

-- 認証チェックで毎回全カラムが読まれる
SELECT * FROM users WHERE email = 'alice@example.com';
-- → 使わないbioやsettingsカラムもI/Oに含まれる
-- → DBのバッファキャッシュに不要なデータが入る

バッファキャッシュの効率

PostgreSQLはページ(8KB)単位でデータをキャッシュする

幅広いテーブル:
  1行 = 2KB → 1ページに4行
  頻繁アクセスの4行を読みたいが、滅多に使わないカラムも同じページに

垂直分割後:
  users_core(5カラム):  1行 = 200B → 1ページに40行
  → 同じキャッシュサイズで10倍の行をキャッシュできる
  → キャッシュヒット率が大幅に向上

垂直分割のパターン

パターン1:コア情報と拡張情報の分離

-- コアテーブル(頻繁にアクセス・必須情報)
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(100) NOT NULL,
  status VARCHAR(20) NOT NULL DEFAULT 'active',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- プロファイルテーブル(たまにアクセス・任意情報)
CREATE TABLE user_profiles (
  user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
  bio TEXT,
  avatar_url TEXT,
  website_url TEXT,
  location VARCHAR(100),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 設定テーブル(滅多にアクセスしない)
CREATE TABLE user_settings (
  user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
  notification_email BOOLEAN DEFAULT true,
  notification_push BOOLEAN DEFAULT true,
  theme VARCHAR(20) DEFAULT 'light',
  language VARCHAR(10) DEFAULT 'ja',
  timezone VARCHAR(50) DEFAULT 'Asia/Tokyo'
);
-- 認証: コアテーブルだけ
SELECT id, email, status FROM users WHERE email = $1;

-- プロフィールページ: JOINで結合
SELECT u.*, p.bio, p.avatar_url
FROM users u
LEFT JOIN user_profiles p ON p.user_id = u.id
WHERE u.id = $1;

-- 設定ページ: 設定テーブルだけ
SELECT * FROM user_settings WHERE user_id = $1;

パターン2:大きなバイナリデータの分離

-- 本体テーブル(メタデータのみ)
CREATE TABLE documents (
  id UUID PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  owner_id UUID REFERENCES users(id),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  file_size_bytes INT,
  content_type VARCHAR(100)
);

-- コンテンツテーブル(大きなデータ)
CREATE TABLE document_contents (
  document_id UUID PRIMARY KEY REFERENCES documents(id),
  content TEXT,           -- 数MB〜数十MBになる可能性
  raw_bytes BYTEA
);

-- 一覧表示: コンテンツを読まない
SELECT id, title, file_size_bytes FROM documents WHERE owner_id = $1;

-- 詳細表示: コンテンツも読む
SELECT d.*, c.content
FROM documents d
JOIN document_contents c ON c.document_id = d.id
WHERE d.id = $1;

パターン3:カラムストアへの部分オフロード

-- PostgreSQL: 頻繁なOLTPクエリ用(コアカラムのみ)
CREATE TABLE orders (
  id UUID PRIMARY KEY,
  customer_id UUID REFERENCES users(id),
  status VARCHAR(20),
  total_amount DECIMAL(10,2),
  created_at TIMESTAMPTZ
);

-- BigQuery: OLAP分析用(全カラム、変換済み)
-- CDCで非同期に同期される
-- orders_analytics テーブル(列指向で格納、より多くのカラムを持つ)

PostgreSQLのパーティション(垂直方向)

PostgreSQLの PARTITION BY は水平分割専用だが、垂直分割は上記の別テーブルアプローチで実現する。

一方、TOAST(The Oversized-Attribute Storage Technique)はPostgreSQLが内部的に行う垂直分割の一形態。

-- TOASTの動作
-- 2KBを超える値(TEXT, BYTEA等)は自動的に別のTOASTテーブルに格納される

-- TOAST設定の確認
SELECT
  attname,
  attstorage  -- 'p'=plain, 'e'=external, 'x'=extended(圧縮+外部), 'm'=main
FROM pg_attribute
WHERE attrelid = 'documents'::regclass;

-- TOASTを使わないよう設定(頻繁に短い値しか入らない場合)
ALTER TABLE documents ALTER COLUMN title SET STORAGE PLAIN;

垂直分割のトレードオフ

観点分割前分割後
キャッシュ効率低い(余分なカラムも読む)高い(必要なカラムだけ)
JOIN数01〜3(用途によって)
スキーマ管理シンプルテーブル数が増える
整合性強い(1テーブル)要注意(外部キー制約で担保)
Null値の削減多い(オプション項目)少ない(分割先テーブル自体が任意)

適切な分割の判断基準

垂直分割を検討すべきサイン:

1. テーブルのカラム数が20を超えている
2. 一部のカラムだけが高頻度でアクセスされる
3. TEXT/BYTEA/JSONBなど大きなカラムがある
4. キャッシュヒット率が低い
5. 同じテーブルでOLTPとOLAP両方に使われている

分割しない方がよいサイン:
1. ほぼすべてのクエリが全カラムを使う
2. トランザクションで複数カラムを一緒に更新することが多い
3. テーブルが既に小さい(100万行以下)

関連概念

出典・参考文献

  • Martin Kleppmann, Designing Data-Intensive Applications (2017) Chapter 6
  • PostgreSQL Documentation, “TOAST” — postgresql.org/docs/current/storage-toast.html
  • Markus Winand, SQL Performance Explained (2012)
  1. 1. 🗄️データ志向アプリケーション設計:概要
  2. 2. 🧩データモデルとクエリ言語
  3. 3. 💾ストレージエンジンとインデックス
  4. 4. 🔁レプリケーション
  5. 5. 🍕パーティショニング(シャーディング)
  6. 6. 🔒トランザクションとACID
  7. 7. 分散システムの本質的な問題
  8. 8. 🤝一貫性と分散合意
  9. 9. 📦バッチ処理
  10. 10. 🌊ストリーム処理
  11. 11. 📋エンコーディングとスキーマ進化
  12. 12. 🔗Sagaパターンと分散トランザクション
  13. 13. 🏗️データシステムの統合設計
  14. 14. 📸MVCC(多版型同時実行制御)
  15. 15. 📊列指向ストレージとOLAP設計
  16. 16. 🕰️ベクタークロックと因果順序
  17. 17. 🔀CRDT(競合なし複製データ型)
  18. 18. 🔍クエリオプティマイザーと実行計画
  19. 19. キャッシュ戦略とRedis設計
  20. 20. 🔎全文検索と転置インデックス
  21. 21. 🌐NewSQL(分散ACIDデータベース)
  22. 22. 📝WALと論理レプリケーション
  23. 23. 🔌コネクションプーリング
  24. 24. 🚧ゼロダウンタイムマイグレーション
  25. 25. 🆔分散ID生成
  26. 26. 🔄N+1問題とDataLoaderパターン
  27. 27. 📈タイムシリーズDB
  28. 28. 🛡️Row Level Security(行レベルセキュリティ)
  29. 29. 📤Outboxパターン(トランザクショナルアウトボックス)
  30. 30. 💾DBバックアップとPITR
  31. 31. ⚠️データベース設計アンチパターン
  32. 32. 🕸️グラフDB深掘り
  33. 33. 🔋バックプレッシャーとサーキットブレーカー
  34. 34. 🔵コンシステントハッシング
  35. 35. 📋マテリアライズドビュー
  36. 36. 📡DBモニタリングとオブザーバビリティ
  37. 37. 🔐データプライバシーとCrypto-Shredding
  38. 38. ✂️垂直分割(Vertical Partitioning)
  39. 39. 🪟ウィンドウ関数
  40. 40. 🧲ベクトルDBとpgvector
  41. 41. 🔧dbtとデータ変換パイプライン
  42. 42. 📬ジョブキューの設計
  43. 43. 📐正規化理論(1NF〜BCNF)
  44. 44. ☁️クラウドDBサービスの設計思想と選択基準
  45. 45. 🗺️地理空間データとPostGIS
  46. 46. 🔑DBセキュリティと権限管理
  47. 47. 🏔️Lakehouse(Apache Iceberg / Delta Lake)
  48. 48. 📜データコントラクト
  49. 49. 🔭OpenTelemetryとDBトレーシング

出典: Martin Kleppmann, 'Designing Data-Intensive Applications' (2017) Chapter 6