ในการออกแบบระบบ High-Frequency และ Financial Ledger การเข้าใจระดับความเร็วของหน่วยความจำทางกายภาพ (Bare-Metal Latency) คือกุญแจสำคัญที่ทำให้เราเห็นว่าทำไม Disk-First Architecture จึงเป็น Bottleneck และทำไม In-Memory Computing ถึงชนะอย่างเด็ดขาด
| Hardware Layer | Physical Latency | Human Scaled (1 Cycle = 1 Sec) | Throughput / Characteristics |
|---|---|---|---|
| L1 CPU Cache | ~0.5 - 1 ns | 1 วินาที | Cache Line 64 Bytes ต่อ Core |
| L2 CPU Cache | ~3 - 4 ns | 4 วินาที | Private per core / Small shared cluster |
| L3 CPU Cache | ~10 - 20 ns | 15 วินาที | Shared across cores (Unified) |
| Main RAM (DDR4/DDR5) | ~60 - 100 ns | 1.5 - 2 นาที | Bandwidth ~50-100 GB/s ต่อ Channel |
| NVMe SSD (PCIe Gen4) | ~10 - 50 µs (10,000 ns) | 4 - 5 เดือน | Random 4K Read/Write Bottleneck |
| Traditional SATA SSD | ~100 - 200 µs | 1.5 ปี | AHCI Controller Bottleneck |
| HDD Mechanical Platter | ~5 - 10 ms (10,000,000 ns) | 300+ ปี | Rotational delay + Actuator Seek arm |
shared_buffers และ Write-Ahead Logging (WAL)
PostgreSQL และ RDBMS มาตรฐานถูกออกแบบในยุคที่ข้อมูลมีขนาดใหญ่เกินกว่าจะเก็บไว้ใน RAM ทั้งหมด จึงใช้สถาปัตยกรรม Disk-First: ดิสก์คือ Single Source of Truth ส่วน RAM คือ Cache ชั่วคราว
shared_buffers ก่อน หากพบ (Hit) จะนำ Pointer ไปประมวลผลทันทีใน RAMread()) ดึง 8KB Block จาก OS Page Cache หรือ NVMe เข้ามาไว้ใน Buffer Pool Frame ที่ว่างUPDATE หรือ INSERT ข้อมูลจะถูกเขียนลง WAL (Write-Ahead Log) บนดิสก์แบบ Sequential Write ก่อนเสมอ (fsync) จากนั้นข้อมูลใน shared_buffers จะถูกแก้ใน RAM และถูกสลากสถานะเป็น Dirty Pageการเข้าใจเบื้องหลังการรันคำสั่ง SQL ช่วยให้วิศวกรหลุดพ้นจากการเดา และวิเคราะห์คอขวดได้อย่างแท้จริง
EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE)
SELECT account_id, balance
FROM accounts
WHERE status = 'ACTIVE' AND balance > 1000000;
หัวใจสำคัญของ Buffer Metrics:
Buffers: shared hit=420: ข้อมูล 420 blocks (420 × 8KB = ~3.36 MB) พบอยู่ใน shared_buffers เรียบร้อยแล้ว (RAM Speed)Buffers: shared read=150: ข้อมูล 150 blocks ต้องถูกอ่านจาก Disk/OS Page Cache เข้ามา (I/O Latency)Buffers: shared dirtied=12: จำนวน page ที่ถูกแก้ไขจนกลายเป็น Dirty pageBuffers: shared written=4: จำนวน page ที่ถูกกระบวนการนี้ Flush ลง Disk โดยตรงcost=0.42..8.45) ใน EXPLAIN เป็น ค่าสมมติ (Arbitrary Units) ที่คำนวณจากค่าคอนฟิก seq_page_cost=1.0, random_page_cost=4.0, cpu_tuple_cost=0.01 ไม่ใช่หน่วยมิลลิวินาที แต่ Execution Time ในบล็อก ANALYZE คือเวลาจริงที่ CPU รัน
B+ Tree คือกระดูกสันหลังของระบบ RDBMS สมัยใหม่ ความแตกต่างหลักกับ B-Tree แบบดั้งเดิมคือ B+ Tree จะเก็บ Data Record/TID (Tuple Identifier) ไว้เฉพาะที่ Leaf Nodes เท่านั้น ส่วน Internal Nodes จะเก็บเฉพาะ Search Key สำหรับการ Routing
สมมติเราสร้าง Composite Index: CREATE INDEX idx_trade ON trades (currency_pair, status, trade_time);
currency_pair เป็นอันดับแรก ถ้าเท่ากันจะเรียงตาม status และถ้าเท่ากันจะเรียงตาม trade_time=) ต้องมาก่อนเสมอ เมื่อใดก็ตามที่ Query เจอเงื่อนไข Range (>, <, BETWEEN, LIKE 'abc%') คอลัมน์ลำดับถัดไปใน Index จะ ไม่สามารถใช้ B+ Tree Binary Navigation ได้อีกต่อไป จะทำได้เพียง Index Filtering (กรองทิ้ง) เท่านั้นหากสร้าง Index แบบ CREATE INDEX idx_acc ON accounts (status) INCLUDE (balance, account_number);
PostgreSQL จะสามารถทำ Index-Only Scan ได้โดยไม่ต้องวิ่งกลับไปเปิด Heap Table Block (ลด Heap Lookups มหาศาล) ตราบใดที่ Page นั้นถูกมาร์กไว้ใน Visibility Map
Sargable ย่อมาจาก Search Argument Able หากเขียน Query ที่ครอบ Function ลงบนคอลัมน์ Index จะไม่ทำงาน:
-- ANTI-PATTERN: Full Table Scan แน่นอน เพราะ B+ Tree เก็บค่า raw 'created_at'
SELECT * FROM transactions WHERE DATE(created_at) = '2026-05-01';
-- REFACTORED (Sargable): ใช้ B+ Tree Range Scan ได้อย่างเต็มประสิทธิภาพ
SELECT * FROM transactions
WHERE created_at >= '2026-05-01 00:00:00'
AND created_at < '2026-05-02 00:00:00';
เกิดขึ้นเมื่อ ORM ทำการ Loop รัน Child Query 1 รอบต่อ Parent Row 1 แถว ส่งผลให้เกิด Network Round-Trip และ Context Switching มหาศาล
-- Query 1: ดึงบัญชี 100 แถว
SELECT * FROM accounts LIMIT 100;
-- Query 2..101: ยิงทีละแถวเพื่อดึงรายการย่อย (100 network round-trips)
SELECT * FROM ledger_entries WHERE account_id = ?;
-- REFACTORED: รวมเป็น Single Set-Based Query ผ่าน JOIN หรือ IN-clause batching
SELECT a.*, l.*
FROM accounts a
LEFT JOIN ledger_entries l ON l.account_id = a.id
WHERE a.id IN (...);
การสร้าง Index เผื่อไว้ทุกคอลัมน์ ไม่เพียงแต่เปลือง Disk/RAM แต่ทำให้ทุกๆ คำสั่ง INSERT, UPDATE, DELETE ต้องไล่ล็อกและอัปเดต B+ Tree ทุกต้น เกิดปัญหา HOT (Heap-Only Tuples) Update Failure และ Page Splits อย่างรุนแรง
ชุดข้อสอบสถาปัตยกรรมฐานข้อมูลระดับลึก 50 ข้อ ออกแบบมาเพื่อทดสอบและรีเช็กภาพจำเชิงฮาร์ดแวร์ การทำงานของ Optimizer, Buffer Pool, MVCC, Locking และ Indexing พร้อมระบบวิเคราะห์ผลลัพธ์ทันที