
20 คำถาม Backend ที่ผมอยากย้อนเวลาไปบอกตัวเอง (บันทึกเตรียมสัมภาษณ์)
Table of Contents
ผมเจอบทความออนไลน์ที่รวม 20 คำถาม backend interview ไว้ ตอนอ่านจบผมพยักหน้าไปตลอดเพราะเป็นเรื่องเดียวกันกับที่ผมเจอมาตอนเรียนรู้และคุยกับ senior engineer หลาย ๆ คน
ผมไม่ได้เก่งหรือ master ทุกข้อนะครับ บางข้อเรียนรู้จาก production incident โดยตรง บางข้อก็ยังศึกษาเพิ่มเติมอยู่ แต่อยากเขียนสรุปไว้เป็นบันทึกของตัวเอง พร้อมคำตอบในแบบของผม ถ้าใครกำลังเตรียมสัมภาษณ์ backend ก็หวังว่าจะมีประโยชน์บ้าง
คำถามทั้งหมดแบ่งเป็น 5 กลุ่ม: databases, concurrency, caching, APIs, และ message brokers คำตอบแต่ละข้อเป็นสิ่งที่ผมเข้าใจจากการลงมือทำและอ่านเพิ่มเติม ไม่ใช่ textbook definition
Databases and Query Performance
Bottleneck ส่วนใหญ่ที่ผมเจอมาจะวนกลับไปที่ database layer เสมอ การรู้ว่าข้อมูลเข้าออกอย่างมีประสิทธิภาพตอน table ใหญ่ขึ้นเป็นเรื่องที่หลีกเลี่ยงไม่ได้
1. เกิดอะไรขึ้นถ้าเอา index ไปใส่ random UUID column?
Random UUIDv4 ทำให้ insert กระจายไปทั่ว B-tree แทนที่จะเรียงต่อกัน
- เกิด page split บ่อย fragmentation และ disk I/O เพิ่มขึ้นเมื่อ table โตขึ้น
- ทางแก้: ใช้ sequential key หรือ time-sorted UUID อย่าง ULID หรือ UUIDv7 ที่ทำให้ insert ไปต่อท้าย index
2. จะ paginate 50 ล้านแถวโดยไม่ใช้ OFFSET ทำยังไง?
OFFSET 100000 ยังต้องอ่านแล้วทิ้ง 100k แถวแรกก่อนเสมอ ยิ่งหน้าลึกยิ่งช้า
- ใช้ keyset (cursor) pagination:
WHERE id > :last_seen ORDER BY id LIMIT nบน indexed column - query time คงที่ทุกความลึก แลกกับการกระโดดข้ามไปหน้า N แบบสุ่มไม่ได้

3. เมื่อไหร่ควรใช้ composite index แทนที่จะสร้าง index แยกสองอัน?
เวลา query ใช้สอง column ร่วมกันบ่อย ๆ
- index แยกสองอัน planner มักเลือกใช้แค่อันเดียวต่อ scan
- composite index บน (A, B) filter ทั้งสอง column ใน seek เดียว
- ลำดับ column สำคัญ ใช้ได้เฉพาะ leftmost prefix เช่น (A, B) ช่วย
WHERE AและWHERE A AND Bแต่ไม่ช่วยWHERE Bเดี่ยว ๆ
4. N+1 query problem คืออะไรและแก้ยังไง?
ดึง list หนึ่ง query แล้ววน query ทีละแถวเพื่อเอา related data กลายเป็น 101 query สำหรับ 100 แถว
- ทางแก้: eager load หรือ batch เช่น
JOIN FETCH(JPA),.includes()(Rails), DataLoader (GraphQL) - ระวัง ORM ที่ lazy-load relation เป็น default เพราะ trap นี้มองไม่เห็นจนกว่าจะเจอ load สูง

Concurrency and Transactions
Distributed systems ทำหลายอย่างพร้อมกัน ถ้าไม่จัดการ timing ให้ดี ทุกอย่างพังโดยไม่รู้ตัว
5. ป้องกันการจองที่นั่งซ้ำใน distributed system ทำยังไง?
Check-then-book เป็น race condition สอง request อ่านว่า “ว่าง” พร้อมกัน แล้วเขียนทั้งคู่
- lock ระดับ application ไม่ครอบคลุมข้ามหลาย server
- แก้ที่ database:
SELECT ... FOR UPDATE(pessimistic) หรือ version column (optimistic) ที่ reject การเขียนทับของเก่า - เสริม unique constraint บนที่นั่งเป็นด่านสุดท้าย ไม่ว่าจะใช้กลยุทธ์ lock แบบไหน

6. Read committed, repeatable read, และ serializable ต่างกันยังไง?
- Read committed (default ของ Postgres): แต่ละ statement เห็นข้อมูลล่าสุดที่ commit แล้ว ดังนั้น read อาจเปลี่ยนได้ภายใน transaction เดียวกัน
- Repeatable read: ใช้ snapshot เดียวตลอด transaction มาตรฐาน SQL ยังเปิดให้เกิด phantom read ตรงนี้ แต่ implementation แบบ MVCC ของ Postgres กันให้ด้วย
- Serializable: เสมือน transaction รันทีละอัน อาจ abort ด้วย serialization error ที่ต้อง retry
ใช้ serializable เมื่อ correctness สำคัญกว่า throughput เช่น financial ledger, inventory system
7. สร้าง distributed lock โดยไม่สร้าง single point of failure ทำยังไง?
Redis instance เดียวล่มเมื่อไหร่ lock หายไปพร้อมกัน
- Redlock: acquire lock ด้วย majority quorum ข้าม Redis หลาย node ที่อิสระต่อกัน
- ZooKeeper / etcd: ใช้ consensus protocol (ZAB, Raft) จัดการ leader election และ lock handoff ได้ทนกว่า
- default แบบ pragmatic คือ Redis ถ้าต้องการ guarantee แรงค่อยไป etcd/ZooKeeper และตั้ง TTL ให้ lock เสมอ กัน deadlock ตอน holder ตาย

8. สร้าง idempotency สำหรับ payment retry endpoint ทำยังไง?
Mobile network หลุดบ่อย client retry แล้วถ้าไม่มี idempotency การ retry จะ charge บัตรสองรอบ
- client ส่ง idempotency key ที่ไม่ซ้ำกันต่อหนึ่ง logical request
- server: ถ้า key นั้น process ไปแล้ว ให้ return response เดิม ถ้ายัง ค่อย process แล้วเก็บผลผูกกับ key
- เก็บ key ใน durable store (database ไม่ใช่แค่ Redis) พร้อม TTL
- ใส่ unique constraint บน key กัน retry พร้อมกัน insert ซ้ำ

Caching Strategy
Caching ทำให้ระบบเร็วขึ้น แต่ก็เพิ่ม failure mode ใหม่ที่มองข้ามง่ายจนกว่าจะไปโผล่ใน production
9. Cache stampede เกิดจากอะไรและป้องกันยังไง?
Hot key หมดอายุ แล้ว request หลายพันตัว miss พร้อมกันถล่ม database ในเวลาเดียว ทางเลือก:
- per-key lock: ให้ตัวเดียว rebuild value ที่เหลือรอหรือ serve ของเก่า
- probabilistic early refresh: regenerate ก่อน TTL จะหมดเล็กน้อย
- pre-warm cache ตอน deploy

10. ถ้า cache user profile ไว้ จะ invalidate ยังไงตอน user เปลี่ยน email?
- Write-through: update cache และ database พร้อมกัน
- Cache-aside: read เช็ค cache ก่อน miss ไป database แล้วค่อย populate
- สำหรับ invalidation การลบ key ตอน update ได้ consistency ทันที ส่วน short TTL ง่ายกว่าแต่มีช่วง stale
- rule of thumb: ลบตรง ๆ สำหรับ user-facing data ใช้ TTL กับ content ที่ไม่ critical
11. ทำไมการเอา Redis ไว้หน้า database อาจทำให้ระบบช้าลงได้?
ทุกการเช็ค cache เพิ่ม network hop 1-3ms
- ถ้า hit rate ต่ำ (สมมุติ 20%) คุณจ่าย hop ทุก request แต่ก็ยังไป hit database เกือบทุกครั้ง สุทธิแล้วช้ากว่า query ตรง
- monitor hit ratio ถ้าต่ำกว่า ~80% cache layer อาจแพงกว่าที่ช่วย
- cache คุ้มกับ key ที่ร้อนและอ่านเยอะ ไม่ใช่ access แบบสุ่มกระจาย
12. Eviction policy ที่เหมาะกับ session store กับ content feed ต่างกันยังไง?
- content feed: LRU เหมาะ เพราะ item เก่าไม่ค่อยถูกเปิดซ้ำ
- session store: LRU เสี่ยง เพราะ active user ที่มี session ยาวอาจถูก evict ตอน memory ตึง แล้วหลุด login โดยไม่คาดคิด
- session ควรใช้ TTL-based expiry หรือ no-eviction พร้อม size cap (และเผื่อ memory ให้พอ)
APIs and Network Architecture
API ของคุณเป็น contract กับ clients ที่คุณควบคุมไม่ได้ การเปลี่ยนแปลงอย่างปลอดภัยและป้องกันการถูกใช้งานในทางที่ผิดเป็น basic skills ที่ต้องมี
13. เปลี่ยน payload ของ API ที่ใช้งานอยู่โดยไม่ทำให้ mobile clients เก่าพังทำยังไง?
Mobile app อยู่บนเครื่อง user เป็นปี ๆ บังคับ upgrade ไม่ได้
- additive change (เพิ่ม optional field) เป็น backward-compatible
- breaking change (ลบหรือเปลี่ยนชื่อ field) ต้องออก version ใหม่ผ่าน URL path (
/v2/) หรือ header (Accept: application/vnd.api.v2+json) พร้อม deprecation window - อย่าเปลี่ยนความหมายของ field เดิมที่มีอยู่แบบเงียบ ๆ
14. Sliding window log กับ fixed window counter สำหรับ rate limiting ต่างกันยังไง?
- fixed window: นับ request ต่อ bucket (เช่น ต่อนาที) ถูกและง่าย แต่ client burst ได้ 2 เท่าตรงขอบระหว่างสองช่วง
- sliding window log: track timestamp ของแต่ละ request แล้วนับเฉพาะที่อยู่ใน rolling window smooth กว่าแต่กิน memory มากกว่า
- ทางสายกลาง: sliding window counter (ถ่วงน้ำหนักสอง bucket) ถูกและแม่นพอใช้สำหรับเคสส่วนใหญ่
15. ออกแบบ endpoint สำหรับ upload video 5GB ทำยังไง?
การอ่าน 5GB เข้า memory ของ application server จะ kill process ทิ้ง สามวิธีที่ใช้ได้:
- presigned URL: client upload ตรงไป S3/GCS โดย server ไม่ต้องแตะ byte เลย (default ที่ดีที่สุด)
- streaming: pipe chunk ไป storage โดยไม่ buffer ทั้ง file
- multipart chunking: client แบ่ง file เป็น part เล็ก ๆ upload แบบ parallel และ resume ได้
16. จัดการ long running tasks ใน synchronous API request ทำยังไง?
ถ้า PDF generation ใช้เวลา 40 วินาที client connection จะ timeout ใช้ async worker pattern:
- API return
202 Acceptedทันทีพร้อม job ID - client poll status endpoint แยก หรือรับ webhook เพื่อเช็คว่าเสร็จหรือยัง
- งานที่เหลือเกิดขึ้นใน background worker หรือ queue

Message Brokers and Asynchronous Processing
ระบบ production decouple work ออกจากกัน และคุณต้องรู้ว่าเกิดอะไรขึ้นเวลาชิ้นส่วนที่ถูก decouple พวกนั้นล้มเหลว เพราะมันจะล้ม
17. เลือก RabbitMQ หรือ Kafka เมื่อไหร่?
มันแก้ปัญหาคนละอย่าง
- Kafka: append-only distributed log throughput สูง เรียงลำดับ และ replay ได้ เหมาะกับ event streaming, audit trail, และ fan-out ไปหลาย consumer
- RabbitMQ: smart broker ที่มี routing ยืดหยุ่น (exchange, binding) และ ack ราย message เหมาะกับ task queue และ routing ที่ซับซ้อน
- ต้องการ replay หรือ stream เลือก Kafka ต้องการ routing หรือ per-message ack เลือก RabbitMQ

18. เกิดอะไรขึ้นถ้า Kafka consumer อ่าน message แล้ว commit offset ไม่สำเร็จ?
Consumer จะอ่าน message เดิมซ้ำตอน restart นี่คือ at-least-once delivery
- ดังนั้น processing logic ต้องเป็น idempotent
- pattern ที่ใช้บ่อย: dedupe ด้วย unique event ID ก่อนลง business logic
- ถ้าต้องการ guarantee แรงกว่านั้น commit offset พร้อม side effect แบบ transactional (outbox / exactly-once)
19. จัดการ poison messages ที่ทำให้ workers พังซ้ำ ๆ ทำยังไง?
Message ที่ format ผิดทำให้ worker crash แล้วถูก requeue worker ตัวใหม่รับไปก็ crash อีก วนไปเรื่อย ๆ
- ตั้ง retry limit แล้ว route message ไป Dead Letter Queue (DLQ) เพื่อตรวจสอบ
- ใส่ exponential backoff ระหว่าง retry กันไม่ให้ queue ถูก flood ด้วย immediate retry
- monitor ความลึกของ DLQ ถ้าพุ่งผิดปกติเมื่อไหร่ แปลว่ามีอะไรพังที่ต้นทาง
20. ทำให้ messages ถูก process ตามลำดับที่ส่งมาได้ยังไง?
การรับประกัน global order ข้ามหลาย consumer ไม่มีทางทำได้ในทางปฏิบัติ เพราะ consumer A อาจใช้เวลานานกว่ากับ message 1 กว่า consumer B จะเสร็จ message 2
- ใช้ partition หรือ routing key: message ที่มี key เดียวกันไป partition เดียวกันเสมอ แล้วถูก consume โดย single thread เรียงลำดับ
- ได้ per-key ordering โดยยังขนานข้าม key ได้
- tradeoff: hot key กลายเป็นคอขวด throughput
สิ่งที่ผมได้จากการเขียนบทความนี้
ผ่าน 20 คำถามนี้มาเป็น exercise ที่ดีสำหรับตัวเอง สิ่งที่ผมสังเกตคือทุกข้อ map ไปยังวิธีที่ production systems พังได้ — slow queries ตอน load สูง, race conditions ตอน concurrent writes, cache failures ตอน traffic spikes, broken clients หลัง API changes, และ messages ที่หายหรือซ้ำใน async pipelines
ผมว่าจำคำตอบไม่ใช่ประเด็นนะครับ การเข้าใจว่าทำไมปัญหาพวกนี้เกิดขึ้นและ tradeoffs มีอะไรบ้าง นั่นคือสิ่งที่ทำให้คำถามพวกนี้มีประโยชน์ และพูดตรง ๆ ว่าครึ่งหนึ่งของพวกนี้ผมเข้าใจอย่างถ่องแท้ก็ต่อเมื่อเจออะไรคล้าย ๆ กันในระบบ production แล้ว
ถ้าใครกำลังเตรียมสัมภาษณ์ backend หรืออยากทดสอบความเข้าใจตัวเอง ลองอธิบายแต่ละข้อออกมาเป็นคำพูดดู ถ้าอธิบายได้แบบสนทนาโดยไม่ต้องเอา textbook phrasing มาใช้ แสดงว่าน่าจะเข้าใจดีพอแล้วครับ