- Published on
DB 完全ガイド — 内部構造・インデックス・クエリプランナ・パーティショニング・Vector DB (Season 2 Ep 13, 2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
はじめに — なぜDBを深く知る必要があるのか
「SQLさえ書ければいいのでは?」への反例:
- P99レイテンシのスパイク → インデックス・プランナの理解なしにデバッグ不可
- トランザクション分離の問題 → 分離レベルを知らないと再現も修正も不可
- ストレージコストの急増 → パーティショニング・圧縮戦略が必要
- 分散DBの選定 → 内部構造を知らないと誤った選択に
- Vector検索 → 埋め込みストアの設計が必要
2025年のシニアエンジニアにとってDBを深く知ることは必須教養だ。
第1部 — ストレージエンジンの二つの哲学
1.1 B-Tree (Balanced Tree)
読み取り最適化。従来のRDBMS(PostgreSQL, MySQL InnoDB)のデフォルト。
[50]
/ \
[20] [80]
/ \ / \
[10][30][70][90]
特徴:
- 平衡二分木 (実際のfanoutは数百)
- O(log N) の探索
- In-place update → 書き込みamplificationが低い
- インデックス構造として広く使われる
欠点: ランダム書き込みが多いと分裂 + I/O。
1.2 LSM-Tree (Log-Structured Merge)
書き込み最適化。RocksDB, Cassandra, ScyllaDB, LevelDB, ClickHouse。
Memtable (RAM)
↓ flush
L0 SSTables (ディスク)
↓ compact
L1 SSTables
↓ compact
L2 SSTables ...
特徴:
- すべての書き込みがappend-only → シーケンシャルI/O
- Compactionが後から整理する
- 書き込み速度が非常に速い
- 空間とCPUをcompactionに使う
欠点: 読み取りが複数レイヤの探索になる → Bloom Filterなどで緩和。
1.3 選択の基準
| ワークロード | 好ましい方式 |
|---|---|
| OLTP (読み取り ~ 書き込み) | B-Tree (Postgres, MySQL) |
| Write-heavy (ログ・メッセージ) | LSM (Cassandra, RocksDB) |
| 時系列 | LSM またはカラムナ (InfluxDB, TimescaleDB) |
| 分析 | カラムナ (ClickHouse, DuckDB) |
1.4 2024〜2025のトレンド: B-TreeとLSMの融合
- Aurora, Neon: ストレージ層の分離 (S3のようなオブジェクトストレージ + ローカルキャッシュ)
- TigerBeetle: 金融特化、LSM + 決定性
- FoundationDB: レイヤード、ACID + Key-Value
- Postgres Incremental Materialized View: 読み取り最適 + リアルタイム
第2部 — インデックス深掘り
2.1 インデックスの8種類
| 種類 | 用途 |
|---|---|
| B-Tree | 範囲クエリ (most common) |
| Hash | 等値条件のみ |
| GIN | 全文検索、JSONB、Array |
| GiST | 地理・図形、範囲型 |
| SP-GiST | 空間データ |
| BRIN | 巨大テーブルの近似インデックス |
| Bloom | 複数カラムのOR検索 |
| Covering (INCLUDE) | インデックスに追加カラム (Index-only scan) |
2.2 インデックス設計の5原則
- クエリが先: 実際のクエリを見て設計する
- 選択度: Selectivityの高いカラムを優先
- 複合インデックスの順序: 最も頻繁に使うフィルタから
- INCLUDEでCovering: テーブルアクセスを回避
- 使っていないインデックスは削除: 書き込み性能を損なう
2.3 インデックスが効かない理由 Top 10
- 関数の適用:
WHERE LOWER(email) = ...→ Expression Indexが必要 - 暗黙の型変換:
WHERE id = '123'(idはint) - 先頭ワイルドカード:
LIKE '%foo'→ Full scan - OR条件: オプティマイザが諦めることがある → UNION
- Not Equals (
!=): ほとんどインデックスを使わない - NULL比較: Postgresは
IS NULLのインデックスが可能、設計に注意 - データが小さい: プランナがSequential Scanを選ぶ
- 複合インデックスの順序が違う:
(a, b)インデックスにWHERE b = ? - 統計が古い: ANALYZEが必要
- インデックスのBloat: REINDEXが必要
第3部 — クエリプランナとEXPLAIN
3.1 プランナの役割
SQL (宣言) → 実行計画 (手続き) へ変換する。コストベース最適化 (CBO)。
3.2 PostgreSQL EXPLAINの読み方
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT u.name, COUNT(o.id)
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'KR'
GROUP BY u.name;
出力例:
HashAggregate (cost=1234..5678 rows=100)
Group Key: u.name
-> Hash Join (cost=500..1200 rows=10000)
Hash Cond: o.user_id = u.id
-> Seq Scan on orders o (cost=0..800 rows=100000)
-> Hash (cost=300..300 rows=500)
-> Index Scan on users_country_idx (cost=0..300 rows=500)
読み方:
- 内側から外側へ
cost=startup..totalrows: 見積もり行数 (ANALYZEの実測と比較)actual time: 実際の時間Buffers: メモリ・ディスクアクセス
3.3 重要なノード種別
| ノード | 意味 |
|---|---|
| Seq Scan | 全体スキャン (小さいテーブル・選択度が低いときはOK) |
| Index Scan | インデックスを使用 |
| Index Only Scan | インデックスだけで解決 (速い) |
| Bitmap Heap Scan | インデックスで探してからテーブルを一括で |
| Nested Loop Join | 小さいouter + インデックスのあるinner |
| Hash Join | ハッシュテーブルを作ってマッチング |
| Merge Join | 両側がソート済みの場合 |
| Hash Aggregate | GROUP BY |
| Sort | ORDER BY |
3.4 Plan HintはPostgresにない
MySQL・Oracleにはヒントがある。PostgresはANALYZE + 統計目標 + random_page_costの調整で誘導する。
第4部 — トランザクション分離
4.1 ACIDの再定義
- Atomicity: 全部かゼロか
- Consistency: 制約違反がない
- Isolation: 同時実行が逐次実行のように見える
- Durability: コミットされたら残る
4.2 分離レベルの4段階 (SQL-92)
| レベル | 防ぐもの |
|---|---|
| Read Uncommitted | (ほとんど何も防がない) |
| Read Committed | Dirty Read |
| Repeatable Read | + Non-repeatable Read |
| Serializable | + Phantom |
4.3 DBごとのデフォルト分離レベル
| DB | デフォルト |
|---|---|
| PostgreSQL | Read Committed |
| MySQL InnoDB | Repeatable Read |
| Oracle | Read Committed (Serializableオプションあり) |
| SQL Server | Read Committed |
4.4 Snapshot Isolation vs Serializable
- Snapshot Isolation (SI): 各トランザクションが開始時点のスナップショットを見る
- 欠点: Write Skewが起こりうる
- Serializable (MVCC SSI): SI + 衝突検知 → abort
- Postgresは
SERIALIZABLEでSSIを提供する
- Postgresは
4.5 Write Skewの例
二人の医師のうち少なくとも一人は当直が必要:
-- T1: on_call を読んで2名 → OK
-- T2: 同時に on_call を読んで2名 → OK
-- 両方が自分の状態を off-call に変更
-- コミット → 当直0名。制約違反
Serializableでのみ検知される。
4.6 Deadlock
二つ以上のトランザクションが互いのロックを待つ。DBが検知 → 1つを犠牲にする (abort)。
防止:
- ロック順序を一貫させる (ID昇順)
SELECT FOR UPDATE NOWAIT- リトライロジック (冪等性 + バックオフ)
第5部 — MVCC (Multi-Version Concurrency Control)
5.1 MVCCの基本
「書き込みは読み取りを妨げず、読み取りは書き込みを妨げない。」
各行の複数バージョンを保持し、トランザクションは自分のスナップショットのバージョンを読む。
5.2 PostgresのMVCC
- Tupleヘッダ: xmin (生成トランザクション)、xmax (削除トランザクション)
- Vacuum: 死んだタプルの整理。必須。
- autovacuum: 自動、ただしチューニングは必要
5.3 Vacuum Bloatの問題
- Long-running transactionがautovacuumを妨げる
- Table/Index Bloat → 性能低下
- 解決: pg_repack、VACUUM FULL、定期的なモニタリング
5.4 MySQL InnoDBのMVCC
- Undo Logに以前のバージョン
- Purge Threadが整理する
- Clustered Index (Primary Keyで物理的にソート)
第6部 — パーティショニングとシャーディング
6.1 垂直 vs 水平
- 垂直: カラム分割 (ほとんど使わない)
- 水平 (Horizontal): 行の分割。Partitioning・Sharding。
6.2 Partitioning (単一DB内)
Postgresネイティブ (10+):
CREATE TABLE events (
id bigserial,
event_time timestamptz,
data jsonb
) PARTITION BY RANGE (event_time);
CREATE TABLE events_2025_01 PARTITION OF events
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
メリット: パーティションプルーニング、管理が容易 (DROP old partition)。 限界: 単一DB内にとどまる。単一サーバの容量制約。
6.3 Sharding (複数DB)
データを複数サーバに分散する。
シャードキーの選択:
- カーディナリティが高い
- アクセスパターンを考慮
- リバランスが容易
- Hot Shardの防止
シャーディング方式:
- Hash: 均等、範囲クエリ ❌
- Range: 範囲クエリOK、Hot Shardの危険
- Directory-based: 柔軟、メタデータの管理が必要
6.4 Cross-shardクエリ
基本的に高くつく。 JOIN・ORDER BYが複数シャードにまたがると、アプリケーションレベルのmergeになる。
解決:
- 関連データを同じシャードに (tenant_idで)
- Read Replica + Materialized View
- OLAPは別立て (データウェアハウス)
6.5 分散SQL DB (シャーディング自動)
- CockroachDB
- YugabyteDB
- Spanner
- TiDB
- Vitess (MySQLの上)
- Citus (Postgres拡張)
第7部 — ReplicationとHA
7.1 Replicationの方式
- Physical (WAL/Binlog): バイト単位、正確
- Logical: SQL/Row単位、柔軟 (異機種・部分的)
7.2 Postgres Replication 2025
- Streaming Replication: WALの転送
- Logical Replication (10+): テーブル単位
- Synchronous オプション: 確実だが遅い
- Patroni: HA管理 (標準)
7.3 Failoverのパターン
Primary の障害を検知
↓ (consensus: etcd・Patroni)
Standby の中から最新を選択
↓
新 Primary へ昇格 + アプリが DSN を切り替え
↓
旧 Primary は Rebuild
核心: Split-brainの防止 (Fencing)。
7.4 Read Replicaの活用
- Read-heavyワークロードの分散
- Replication lagを受け入れる
- 「Read-your-writes」はPrimaryで
第8部 — PostgreSQLの2025年の独走
8.1 なぜPostgresが勝ったのか
2024〜2025年のDB選定でPostgresが事実上のデフォルトになった理由:
- 拡張性: 数多くのExtension
- JSONB: NoSQL機能を内蔵
- pgvector: Vector DBの機能
- PostGIS: GIS最強
- Timescale: 時系列
- Citus: 分散
- Logical Replication: CDC・マイグレーション
- ライセンス: PostgreSQL License (寛容)
- コミュニティ: 安定、大企業への依存がない
- クラウド管理型: Aurora, RDS, Cloud SQL, Neon, Supabase
8.2 Postgres Extension Top 15 (2025)
| Extension | 用途 |
|---|---|
| pgvector | Vector Search |
| PostGIS | 地理 |
| TimescaleDB | 時系列 |
| Citus | 分散 |
| pg_partman | パーティション管理 |
| pg_cron | スケジュール |
| pg_stat_statements | クエリ性能 |
| hypopg | 仮想インデックス |
| pg_repack | オンライン再構成 |
| pg_hint_plan | プランヒント |
| pg_trgm | 類似検索 |
| unaccent | アクセント除去 |
| uuid-ossp | UUID生成 |
| pgcrypto | 暗号 |
| hstore | キー・バリュー |
8.3 2024〜2025のPostgresエコシステム
- Neon: Serverless Postgres、ブランチ
- Supabase: Postgres + Auth + Realtime
- Xata: DevEx優先
- Aurora Postgres: AWS管理型
- ParadeDB: 検索拡張
第9部 — NoSQL: いつ使うか
9.1 NoSQLの5カテゴリ
| カテゴリ | 例 | 用途 |
|---|---|---|
| Document | MongoDB, Firestore | スキーマが柔軟 |
| Key-Value | Redis, DynamoDB | 高速な参照 |
| Wide-column | Cassandra, ScyllaDB, HBase | 大規模書き込み |
| Graph | Neo4j, Neptune | 関係中心 |
| Time-Series | InfluxDB, Timescale | 時系列 |
9.2 2025の現実: 「Postgresで十分」
ほとんどのNoSQLユースケースをPostgresがカバーする:
- Document → JSONB
- Key-Value → UNLOGGEDテーブル、またはRedis併用
- Time-Series → TimescaleDB
- Graph → Apache AGE extension
それでもNoSQLを選ぶとき:
- グローバル低遅延 (DynamoDB Global Table)
- 超大量の書き込み (Cassandra)
- グラフアルゴリズム特化 (Neo4j)
9.3 Redisの立ち位置
インメモリのキャッシュ・キュー・ランキング・Rate Limit。「一つくらいは誰もが持っている」道具。
2024年のライセンス変更 → Valkeyフォーク (AWS・Google主導)。選択肢が増えた。
第10部 — Vector DB
10.1 Vector DBが必要な理由
LLMの埋め込み検索・推薦などのための近似最近傍 (ANN) 検索。
10.2 ANNアルゴリズム
- HNSW (Hierarchical Navigable Small World): 速い・正確、メモリ ↑
- IVF (Inverted File): クラスタリングベース
- PQ (Product Quantization): メモリ節約
- HNSW + PQ: ハイブリッド
10.3 2025のVector DB比較
| DB | 特徴 |
|---|---|
| pgvector | Postgres拡張、統合が最高 |
| Qdrant | Rust、速い、フィルタリングが強い |
| Weaviate | ハイブリッド検索、GraphQL API |
| Milvus | 大規模、Zilliz |
| Pinecone | SaaS、運用が楽 |
| LanceDB | ファイルベース、組み込み |
| Turbopuffer | Serverless |
10.4 2025のおすすめ
- シンプルなRAG: pgvector (既存のPostgresに追加)
- 大規模・高度なフィルタ: QdrantまたはWeaviate
- フルマネージド: Pinecone
- ローカル/エッジ: LanceDB
10.5 pgvectorの例
CREATE EXTENSION vector;
CREATE TABLE documents (
id bigserial PRIMARY KEY,
content text,
embedding vector(1536)
);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
-- 検索
SELECT content
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;
第11部 — DBロードマップ6か月
Month 1: SQL深掘り
- Window Function、CTE、Recursive
- トランザクション分離・Deadlockの実習
- インデックス設計
Month 2: Postgres内部
- EXPLAINの完全理解
- MVCC・Vacuum
- WAL・Replication
Month 3: Storage Engine
- DDIA 3章 (Storage)
- B-Tree / LSM の実装体験
- RocksDB・Cassandraの学習
Month 4: 分散DB
- CockroachDB・Spannerの構造
- Sharding設計
- Replicationの運用
Month 5: Vector DB
- pgvectorの実戦
- HNSWの理解
- RAGデータレイヤの構築
Month 6: 最適化・運用
- Slow Queryの分析
- インデックスのチューニング
- Connection Pooling (PgBouncer)
- Schema Migrationの無停止化 (Postgres深掘り)
第12部 — DBチェックリスト12
- B-Tree vs LSM-Tree の選択基準を知っている
- Postgresのインデックス型5種 を用途別に知っている
- インデックスが効かない理由5つ を言える
- EXPLAIN ANALYZE の出力を読める
- トランザクション分離の4段階 を防ぐ現象とセットで知っている
- Write Skew を説明できる
- MVCCとVacuum の関係を知っている
- Partitioning vs Sharding の違いを知っている
- Hash vs Rangeシャーディング のトレードオフを知っている
- Postgres Extension Top 5 を知っている
- pgvectorとQdrant の選択基準を知っている
- Connection Pool (PgBouncer) の必要性を知っている
第13部 — DBアンチパターン10
- ORMだけを信じてクエリを点検しない: N+1、過剰なカラムのロードがよくある
- インデックスを無制限に足していく: 書き込み性能の低下。使われていないものは削除
- Long transaction: MVCC bloat。トランザクションは短く
- デフォルト分離レベルの盲信: Read Committedの限界を知らずWrite Skewのバグ
- Connectionを一つずつ管理: Poolなしで数千connection → Postgresが落ちる
- JSONBに何もかも: スキーマの強みを放棄。定型はカラムで
- シャードキーを間違える: Hot Shard → 再シャーディング地獄
- Migrationの無計画なdowntime: 無停止マイグレーション戦略が必須
- Vacuumチューニングの無視: 気づかないうちにテーブルが10倍bloat
- DBをメッセージキューに: 可能だが非推奨。Redis/Kafkaを使う
おわりに — DBは「本番システムの重力」だ
アプリケーションは替えられても、DBスキーマは簡単には変えられない。DBは本番システムで最も慣性の大きい部分だ。
だから最初にうまく設計しなければならない:
- インデックス設計で5年もつ構造に
- パーティション・シャーディングで10倍成長に備える
- 分離レベルでデータ整合性を保証する
- 可観測性・バックアップで災害に備える
2025年のDBエンジニアリングはこうだ:
- Postgres First: ほとんどはこれで解決する
- Vector DB: LLM時代の必須
- 分散DB: 超大規模サービスにだけ
- NoSQL: 明確な理由があるときだけ
次回予告 — 「ネットワーク完全ガイド: HTTP/3・QUIC・TLS 1.3・gRPC・WebSocket・CDN」
Season 2 Ep 14はインターネットの配管、ネットワーク。次回は:
- TCP vs UDP vs QUIC
- HTTP/1.1 → 2 → 3 の進化
- TLS 1.3 Handshake
- gRPC vs REST vs GraphQL vs tRPC
- WebSocket vs SSE vs Long Polling
- CDNアーキテクチャ (Cloudflare, Fastly)
見えない電線を、次回に。