บทที่ 2 · Part 1 — Foundation
Numbers & Estimation
Latency/capacity/size anchor ที่ต้องจำ และ back-of-envelope math ผ่านเคส e-wallet ไทย
มีคำถามที่ตอบไม่ได้ถ้าไม่มีตัวเลขในหัว: "ระบบนี้ต้องใช้ฐานข้อมูลกี่ตัว" คนที่ตอบได้ใน 3 นาทีไม่ได้เก่งกว่า — เขาแค่จำ order of magnitude ของ latency กับ capacity ไว้ และคูณเลขในหัวได้ บทนี้คือชุดตัวเลขนั้น
จบบทนี้คุณจะ
- จำ latency, capacity และ size anchor ระดับ order of magnitude ได้
- ทำ back-of-envelope estimation ได้ครบ 4 มิติ: QPS, storage, bandwidth, memory
- รู้ว่า peak factor ที่คนมักคำนวณผิดมาจากไหน
- ใช้ตัวเลขเพื่อ push back requirement ที่ไม่จริงได้
[!WARNING] ตัวเลขในบทนี้เป็น illustrative ทั้งหมดมีไว้เพื่อสอน รูปร่างของการคำนวณ ไม่ใช่ผลวัดจากระบบของคุณ ก่อนใช้ในการตัดสินใจจริงต้องวัดเอง หรืออ้างจาก vendor benchmark / load test / dashboard ของคุณ
Latency — จากลิสต์คลาสสิกของ Jeff Dean
เป้าหมายคือจำ order of magnitude ไม่ใช่ทศนิยม
| Operation | เวลาโดยประมาณ | ความหมายกับงานของคุณ |
|---|---|---|
| L1 cache reference | ~1 ns | ฟรี |
| Main memory reference | ~100 ns | in-process cache เร็วกว่า network cache ~1,000 เท่า |
| Compress 1 KB ด้วย codec เร็วๆ | ~2 µs | CPU ถูกกว่า network — compress payload เถอะ |
| Read 1 MB sequentially จาก memory | ~50–100 µs | serialize object ใหญ่ไม่ฟรี |
| Round trip ใน AZ เดียวกัน | ~0.5 ms | service ที่เรียกภายใน 50 ครั้ง เสียเวลา network เปล่าๆ 25 ms |
| SSD random read | ~100 µs (0.1 ms) | index lookup ที่ลง disk รับได้ table scan ไม่รับ |
| Read 1 MB sequentially จาก SSD | ~0.5–1 ms | sequential ชนะ random ขาด — นี่คือเหตุผลที่ LSM-tree มีอยู่ |
| HDD seek | ~5–10 ms | ถ้ายังมีจานหมุนใน latency path นั่นคือปัญหาของคุณ |
| Round trip กรุงเทพ ↔ สิงคโปร์ | ~25–35 ms | cross-region synchronous call ทำลาย p99 |
| Round trip กรุงเทพ ↔ US East | ~230–250 ms | hop เดียวก็เกินงบ UX ของ mobile แล้ว |
| Mobile 4G first-byte บน warm connection | ~50–150 ms | TLS handshake บน cold connection บวกอีก 1–2 round trip |
วิธีใช้ตารางนี้จริง
เวลาออกแบบ endpoint ให้ไล่ว่ามันมีกี่ round trip แล้วคูณ 0.5 ms ถ้า endpoint หนึ่งเรียก 8 service แบบ sequential คุณจ่าย network ไปแล้ว 4 ms ก่อนคิดงาน CPU หรือ query ใดๆ และตัวเลขนี้จะบวมขึ้นมากที่ p99
ทำไม p99 ไม่เหมือน average เลย
ถ้า service หนึ่งมี p99 = 100 ms และคุณเรียกมันแบบ parallel 10 ตัวแล้วรอทุกตัว
โอกาสที่จะ ไม่ เจอตัวช้าเลยคือ 0.99^10 ≈ 90% — แปลว่า 10% ของ request
จะช้าอย่างน้อย 100 ms สิ่งที่เคยเป็น "1 ใน 100" กลายเป็น "1 ใน 10"
นี่คือหัวใจของ paper The Tail at Scale — fan-out ทำให้ tail latency กลายเป็น latency ปกติ วิธีแก้อยู่ในบทที่ 12 (hedged request, tied request)
Capacity anchors
ตัวเลขวางแผนแบบ conservative สำหรับ VM/container บน cloud สมัยนี้
| Component | ตัวเลขวางแผนต่อ node | อะไรทำให้พัง |
|---|---|---|
| Stateless API service (4 vCPU) | 1,000–5,000 req/s สำหรับงาน JSON เบาๆ | JSON ขนาด 1 MB+, synchronous fan-out, N+1 query, blocking I/O ที่ thread ไม่พอ |
| Relational DB (managed, 8–16 vCPU) | point read 5,000–20,000/s; write 1,000–5,000/s | write ถูกจำกัดด้วย WAL/fsync และจำนวน index — ทุก index ที่เพิ่มเก็บภาษีจากทุก write |
| Redis (single node) | 50,000–150,000 ops/s, p99 ต่ำกว่า 1 ms | key ใหญ่, KEYS *, คำสั่ง blocking บน single thread, hot key กระจุกที่ shard เดียว |
| Kafka (cluster เล็ก) | หลายร้อย MB/s รวม; ~10k–100k msg/s ต่อ partition | จำนวน partition คือเพดาน parallelism ของ consumer — วางแผนตั้งแต่แรก เพราะเปลี่ยนแล้ว key จะย้าย partition |
| Object storage (S3-class) | throughput เหลือเฟือ; first byte ~10–100 ms | ไม่ใช่ฐานข้อมูล ไม่มี transaction ข้าม object และ LIST ราคาแพง/ช้าเมื่อ prefix มี object จำนวนมาก — ระดับ consistency ต่างกันตาม provider จึงต้องอ่านเอกสารของตัวที่ใช้จริง (Amazon S3 ให้ strong read-after-write สำหรับ PUT/DELETE/GET/LIST แล้ว) |
| CDN edge | ดูดซับ static traffic ได้เกือบทั้งหมด | เฉพาะเมื่อ cache key ถูก — Vary header หรือ cookie ตัวเดียวทำให้ hit rate เหลือใกล้ศูนย์ |
ตัวเลขพวกนี้คือจุดเริ่มของบทสนทนา ไม่ใช่จุดจบ
เอาไปใช้ตอน sizing รอบแรกเพื่อรู้ว่า "ต้องใช้ 3 node หรือ 300 node" แล้วต้องมี load test ยืนยันก่อนขึ้น production เสมอ
รูปแบบการบันทึกตัวเลขที่วัดเอง
ตัวเลขที่ไม่มีบริบทเทียบกันไม่ได้และเชื่อไม่ได้ ทุกครั้งที่วัด ให้บันทึกครบ 7 ช่องนี้ แล้วเก็บไว้ในที่เดียวของทีม
Component Postgres 16, db.r7g.2xlarge (8 vCPU / 64 GB), gp3 12k IOPS
Workload INSERT ledger_entries 5 แถว/txn, 6 index บนตาราง
Payload ~400 bytes/แถว
Concurrency 64 connection (pgbouncer transaction pooling)
Durability synchronous_commit = on, 1 sync standby ใน AZ อื่น
ผลลัพธ์ p50 4 ms · p95 11 ms · p99 28 ms · 1,850 txn/s ที่จุดอิ่มตัว
วันที่วัด 2026-07-20 ผู้วัด <ชื่อ>
ทำไมช่อง Durability สำคัญที่สุด
ตัวเลข write throughput ที่วัดโดยปิด synchronous_commit
หรือไม่มี sync standby สูงกว่าของจริงหลายเท่า
ตัวเลขที่ไม่บอก durability configuration คือตัวเลขที่ใช้ตัดสินใจไม่ได้
Size anchors
- 1 UUID = 16 bytes แบบ binary, 36 chars แบบ text — การเก็บ UUID เป็น text ใน index ที่ร้อนคือความสิ้นเปลืองที่เจอบ่อยมาก
- Row ของข้อมูล transactional ทั่วไป: 200 bytes – 1 KB; JSON blob ที่ใส่ทุกอย่างไว้ข้างใน: 2–20 KB
- Thumbnail ที่ compress แล้ว: 10–50 KB; รูปจากมือถือ: 2–5 MB; วิดีโอ 1080p 1 นาที: ~50–100 MB
- 1 ล้าน row × 500 bytes = 500 MB; 1 พันล้าน × 500 bytes = 500 GB — บวก index อีก 1.5–3 เท่าของ base
- เลขยกกำลังสิบที่ต้องจำ: 1 วัน ≈ 86,400 s (ปัดเป็น 100k เวลาคิดในหัว), 1 เดือน ≈ 2.6 M s, 1 ปี ≈ 31.5 M s
ทางลัดที่ใช้ได้ทุกครั้ง
1 ล้านครั้งต่อวัน ≈ 12 ครั้งต่อวินาที (เพราะ 1,000,000 / 86,400 ≈ 11.6) จำเลขนี้ไว้ตัวเดียวก็ประมาณ QPS ได้เกือบทุกเคส
Back-of-envelope estimation
เป้าหมายไม่ใช่ความแม่นยำ เป้าหมายคือรู้ว่า design ของคุณห่างจากความจริงเกิน 10 เท่าไหม ภายใน 5 นาที ต่อหน้าคนอื่น
4 คำถาม
- QPS — average และ peak (build ตาม peak)
- Storage — ต่อวัน, ต่อปี, และหลังใช้ retention policy
- Bandwidth — เข้าและออก (egress มีค่าใช้จ่าย ingress มักไม่มี)
- Memory — จะเก็บ working set ไว้ใน cache กี่เปอร์เซ็นต์
ตัวอย่างเดินจริง: e-wallet ไทย
ประกาศสมมติฐานออกมาให้ชัด เพราะนั่นคือวินัยของการประมาณ: ผู้ใช้ลงทะเบียน 5 ล้านคน, active รายวัน 20% (1 ล้าน DAU), ต่อคนต่อวันเช็คยอด 3 ครั้ง และเคลื่อนย้ายเงิน 0.5 ครั้ง
READS (เช็คยอดเงิน)
1,000,000 DAU x 3 = 3,000,000 reads/day
average = 3,000,000 / 86,400 s ≈ 35 reads/s
peak: traffic ไม่แบน แอปคนไทยพีคช่วงพักเที่ยงและ 19:00-22:00
ใช้ peak factor 5x-10x
peak ≈ 35 x 8 ≈ 280 reads/s <-- เล็กมาก DB ตัวเดียวเอาอยู่
WRITES (โอน, เติมเงิน, จ่ายบิล)
1,000,000 x 0.5 = 500,000 txns/day ≈ 6 txns/s average
peak ≈ 6 x 10 ≈ 60 txns/s <-- ยังเล็ก
แต่: วันเงินเดือนออก (ราวๆ วันที่ 25) และวันเทศกาลเป็นสัตว์อีกชนิด
เงินเดือนออก + campaign พร้อมกัน: สมมติ 30x average = 180 tps
Design target: 300 tps sustained, burst 1,000 tps นาน 5 นาที
STORAGE (ledger entry — ส่วนที่คนลืม)
Double-entry: 1 transfer = >=2 ledger rows (debit + credit)
ถ้ามี fee กับ settlement leg มักกลายเป็น 4-6 rows
500,000 txns/day x 5 rows x 400 bytes ≈ 1 GB/day
x 365 ≈ 365 GB/year ของ raw ledger ยังไม่รวม index
Retention ตามกฎ (สมมติ 10 ปี — ต้อง CONFIRM กับ compliance)
≈ 3.6 TB + index ≈ 6-10 TB
--> Postgres ตัวเดียวเก็บได้ แต่ serve ย้อนหลัง 10 ปีไม่สบาย
สรุป: hot ledger (90 วัน) + archive tier
MEMORY (cache ยอดเงิน)
1,000,000 active balances x 100 bytes ≈ 100 MB
--> cache ได้สบาย แต่ยอดเงินคือเงิน ยอดเก่า 1 ครั้ง = ticket 1 ใบ
ดูบทที่ 7 ว่าทำไมต้อง cache "read model" ไม่ใช่ยอดที่เป็น authoritative
5 นาทีนั้นซื้ออะไรให้คุณ
เลขคณิต 5 นาทีพิสูจน์แล้วว่า transaction rate ไม่ใช่ส่วนที่ยาก — relational database ตัวเดียวที่ tune ดีๆ รับ financial write 300 tps ได้ ส่วนที่ยากจริงคือ correctness (ห้าม double-spend, ห้ามเงินหาย), retention (ledger 10 ปี), และ peak concentration (วันเงินเดือนออก) ซึ่งเป็นบทสนทนาที่ต่างจาก "เรามี 1 ล้านผู้ใช้ ต้องใช้ microservices กับ Kafka" สิ้นเชิง — นี่คือทักษะที่ให้ผลตอบแทนสูงสุดในคอร์สนี้
Bandwidth — มิติที่ถูกลืมบ่อยที่สุด
ต่อจากตัวอย่างเดิม: ถ้าหน้า transaction history ตอบ JSON 20 รายการ ≈ 8 KB
EGRESS
3,000,000 balance reads/day x 1 KB ≈ 3 GB/day
500,000 history views/day x 8 KB ≈ 4 GB/day
รวม ≈ 7 GB/day ≈ 210 GB/month
--> ค่า egress ยังถูก แต่ถ้าเปลี่ยนเป็นส่ง statement PDF 200 KB
ให้ 1 ล้านคนต่อเดือน = 200 GB/month เพิ่มมาจากจุดเดียว
Peak factor ที่คนคำนวณผิดบ่อย
- Thundering herd จาก marketing — push notification ถึง 1 ล้านคนตอน 20:00 ทำให้พีคเกิดใน 60 วินาที ไม่ใช่กระจายทั่วชั่วโมง 1 ล้าน × open rate 8% ใน 1 นาที = ~1,300 req/s จากศูนย์ ตาราง campaign ของ marketing จะไม่โผล่ใน capacity plan ถ้าคุณไม่ไปถาม
- Retry storm — client ที่ retry 3 ครั้งโดยไม่มี backoff เปลี่ยน outage 5 นาทีให้เป็นโหลด 4 เท่า ตอนที่คุณอ่อนแอที่สุด คูณ peak ด้วย retry policy ของคุณเสมอ
- Cron alignment — ทุกอย่างที่ตั้งไว้
00:00จะรันตอน00:00พร้อมกัน — ใส่ jitter - Batch window — ไฟล์ settlement/clearing, การออก statement และงาน reconciliation ก็เป็นพีค เพียงแต่มันเกิดตอน 02:00 ที่ไม่มีใครดู dashboard
จาก estimation ไปเป็นข้อสรุปเชิงสถาปัตยกรรม
ตัวเลขมีประโยชน์เมื่อมันตัดทางเลือกออก ตารางนี้คือ mapping ที่ใช้ได้บ่อย
| ถ้าตัวเลขบอกว่า | ข้อสรุปเชิงสถาปัตยกรรม |
|---|---|
| write < ~1,000 tps และ data < ~1 TB | ฐานข้อมูลเดียว + read replica พอ อย่า shard |
| read : write > 20 : 1 | ใส่ cache กับ read replica ก่อนคิดเรื่องอื่น |
| working set ใส่ RAM ได้ทั้งก้อน | cache hit จะสูงมาก — จ่ายค่า RAM คุ้มกว่าเพิ่ม node |
| data โตเกิน 10 TB และ query แตะแต่ข้อมูลใหม่ | ทำ tiering (hot/warm/cold) ไม่ใช่ shard |
| peak / average > 20 เท่า | ต้องมี queue ซับแรง + autoscaling + load shedding |
| fan-out > 10 downstream ต่อ request | tail latency จะเป็นปัญหาหลัก ไม่ใช่ throughput |
| egress > หลาย TB/เดือน | ค่า network จะเริ่มแซงค่า compute — ดูบทที่ 17 |
Lab 2 — Calibrate ตัวเอง
ส่วน A. หยิบระบบที่คุณดูแลอยู่ โดยยังไม่เปิด dashboard เขียนตัวเลขที่คุณเดา: peak RPS ของ endpoint ที่ร้อนที่สุด, p50 และ p99 ของ endpoint นั้น, จำนวน row ในตารางใหญ่สุด, GB/day ของ log, ค่า infra ต่อเดือน
ส่วน B. เปิดดูของจริง แล้วให้คะแนนตัวเอง — ห่างไม่เกิน 2 เท่า = ดี, ไม่เกิน 10 เท่า = รับได้, เกิน 10 เท่า = คุณกำลังออกแบบแบบตาบอดในมิตินั้น
ส่วน C. ทุกมิติที่พลาดเกิน 10 เท่า เขียน 1 ประโยคว่าจะดู dashboard หรือ query ตัวไหนทุกสัปดาห์นับจากนี้
สรุปบทนี้
จำ order of magnitude ไม่ใช่ทศนิยม · คิด 4 มิติเสมอ (QPS, storage, bandwidth, memory) · build ตาม peak ไม่ใช่ average · ประกาศสมมติฐานออกมาให้คนในห้องแย้งได้ · และกำกับตัวเลขที่ยังไม่ได้วัดว่า illustrative ทุกครั้ง บทถัดไป คือลำดับขั้นที่ทำให้ทั้งหมดนี้กลายเป็นกระบวนการที่ทำซ้ำได้