← Redis
Production Battleground & Post-Mortems
Redis Real-World Case Studies & Architectural Failures
Dissecting Hardware Limits, NIC Saturation, OOM Killers, and Cascading Failures at GitHub, Twitter (X), Slack, and Grab/Uber
การเข้าใจทฤษฎี In-Memory และ Event Loop ทำให้เราสร้างระบบที่เร็วได้ แต่การเข้าใจ "จุดพังพินาศในสมรภูมิจริง" (Real-World Battleground Post-Mortems) คือสิ่งที่แยกความแตกต่างระหว่างวิศวกรทั่วไปกับระดับ Lead Distributed Systems Architect
ในคู่มือฉบับนี้ เราจะผ่าชันสูตร 4 มหากาพย์ความพังพินาศระดับโลก เมื่อ Redis ชนเพดานข้อจำกัดทางกายภาพของฮาร์ดแวร์ ทั้ง Network Interface Card (NIC), Linux Copy-on-Write (COW), Replication Buffer Overflow, และ Volatile Memory Eviction Traps
Case Study 1: GitHub — การพังทลายของ 100 Gbps Network Interface จาก Big Key ใน Queue
1. บริบทและสถาปัตยกรรมเดิม (Background Context)
GitHub ใช้งาน Redis เป็นกระดูกสันหลังในการจัดการ Background Job Processing ผ่านระบบที่ชื่อว่า Resque โดยรองรับงานประมวลผล Webhook, Git Push Ingestion, CI/CD Actions, และ Event Fan-out นับล้านงานต่อวัน
[ Webhook / Push Event ]
|
v
+-----------------------+ LPUSH queue:jobs { "diff": "2MB JSON...", ... }
| GitHub Web App (JVM) | -------------------------------------------------+
+-----------------------+ |
v
+---------------------------------------+
| REDIS RESQUE CLUSTER |
| |
| Key: "queue:default" |
| [ 2MB Payload ] -> [ 1.8MB Payload ] |
+---------------------------------------+
|
| BRPOP (10,000 req/sec)
v
[ 1,000+ Resque Workers ]
(10,000 x 2MB = 20 GB/s Traffic)
(Exceeds 100 Gbps = 12.5 GB/s NIC Limit!)
2. สาเหตุทางสถาปัตยกรรมระดับลึก (Architectural Root Cause)
Payload in Queue Anti-Pattern: มีทีมพัฒนาส่ง Job งานใหม่โดยนำ JSON Payload ก้อนใหญ่ (รวม Git Commit Metadata และ Large Code Diff ขนาด 1–3 MB) บรรจุลงไปใน Redis List ตรงๆ แทนที่จะเก็บเพียง Reference ID
High-Frequency Polling: ฝั่ง Consumer มี Resque Workers นับพันโปรเซสรันคำสั่ง BRPOP แย่งกันดึงงานด้วยความถี่รวมกันกว่า 10,000 ครั้งต่อวินาที
Hardware Bottleneck (NIC Saturation):
10,000 requests/sec × 2 MB = 20,000 MB/s (~160 Gbps)
แม้ CPU Core ของ Redis จะยังทำงานไม่ถึง 100% แต่ทราฟฟิกพุ่งชนเพดาน 100 Gbps Network Interface Card (NIC) ของเครื่อง Host จนแบนด์วิดท์เต็ม 100% ทันที
Cascading Failure: เมื่อ Ring Buffer ของ Network Device Driver บน Linux Kernel เต็ม แพ็กเก็ต TCP ถูกดรอปทิ้ง การเชื่อมต่อระหว่าง Web App กับ Redis เริ่มเกิด Connection Timeout และคำขอก็สะสมจน Resque Workers ล่มทั้งระบบ
3. การแก้ไขเชิงสถาปัตยกรรม (Architectural Solution)
นำหลักการ Pointer-Only Queue Pattern (Claim Check Pattern) มาปรับใช้:
// ARCHITECTURAL REFACTORING: Kotlin Implementation
data class JobClaimCheck(
val jobId: String,
val payloadStorageUri: String // บันทึกเฉพาะ Pointer ชี้ไปยัง Blob Storage
)
@Service
class ResqueProducer(
private val redisTemplate: StringRedisTemplate,
private val s3Client: AmazonS3
) {
fun enqueueHeavyJob(jobId: String, largePayload: ByteArray) {
// Step 1: ผลัก Large Payload ออกจาก Redis ไปเก็บใน S3
val uri = "s3://github-jobs-bucket/payloads/" + jobId + ".json"
s3Client.putObject("github-jobs-bucket", "payloads/" + jobId + ".json", largePayload.inputStream(), null)
// Step 2: Redis List บันทึกเฉพาะ Pointer ขนาดเล็ก (ไม่เกิน 100 Bytes)
val claimCheckJson = "{\"jobId\":\"" + jobId + "\",\"uri\":\"" + uri + "\"}"
redisTemplate.opsForList().leftPush("queue:default", claimCheckJson)
// Network Bandwidth ลดลงทันที 99.99%! (จาก 2MB เหลือ 100 Bytes ต่อ Job)
}
}
Engineering References & Sources:
• GitHub Engineering Blog: "How GitHub Deals with Background Jobs at Scale"
• Resque Architecture Specification by Chris Wanstrath (GitHub Co-founder)
Case Study 2: Twitter (X) — Celebrity Fan-Out & Timeline ZSet Crisis
1. บริบทและสถาปัตยกรรมเดิม (Background Context)
ระบบ Home Timeline ของ Twitter อาศัยการทำ Fan-out on Write โดยทวีตใหม่จะถูกนำ ID ไปแทรกลงใน In-Memory List หรือ Sorted Set ของ Follower ทุกคน เพื่อให้การเปิดแอปมาอ่านฟีด (Read Path) ทำได้รวดเร็วในระดับ Sub-10ms
[ Celebrity Tweet ] (e.g. 50,000,000 Followers)
|
v
+-------------------------------------------------------------+
| Fan-out Ingestion Daemon |
+-------------------------------------------------------------+
|
+-- Write to Follower 1 (acc:101) -> LPUSH / ZADD
+-- Write to Follower 2 (acc:102) -> LPUSH / ZADD
:
+-- Write to Follower 50M (acc:50M) -> LPUSH / ZADD
|
v
[ 50,000,000 Writes to Redis in seconds! ]
-> Huge Memory Latency Spike & Single Thread Lockup
2. สาเหตุทางสถาปัตยกรรมระดับลึก (Architectural Root Cause)
Super-hot Key & Write Spike: เมื่อผู้ใช้ที่มีผู้ติดตาม 50–100 ล้านคนทวีตข้อความ ระบบ Fan-out on Write ต้องไล่เขียน Redis Commands จำนวน 50–100 ล้านคำสั่งลงใน RAM ส่งผลให้เกิด Latency Spike มหาศาลและคิวใน Event Loop ค้าง
Giant ZSet Traversal Bottleneck: ผู้ใช้ที่มีการโต้ตอบสูงมีขนาด Timeline โตจนกลายเป็น Big Key เมื่อเรียก ZREVRANGE ดึงฟีด 200 รายการ CPU Core ของ Redis ต้องทำการ Traversal ผ่าน Skip List และเสียเวลาบีบอัดข้อมูลลง RESP Protocol
Zero Blocking Rule Violation: ใน Single-Threaded Event Loop เมื่อคำสั่งดึง Timeline ขนาดใหญ่กินเวลา CPU ไป 20–50 มิลลิวินาที คำขอจาก Client อื่นๆ นับหมื่นที่รออยู่ใน Socket Queue จะต้องรอทั้งหมด เกิด Read Latency พุ่งสูงทันที
3. การแก้ไขเชิงสถาปัตยกรรม (Architectural Solution)
Hybrid Fan-out Architecture:
• ผู้ใช้ทั่วไป (< 20,000 Followers): ใช้ Fan-out on Write ตามปกติ
• Celebrity Accounts (> 20,000 Followers): ยกเลิก Fan-out on Write เปลี่ยนไปใช้ Fan-out on Read (Fan-in Query) โดยจะดึงทวีตสดของ Celebrity มารวมกับ Timeline ของผู้ใช้ใน JVM Heap Memory ฝั่ง App ตอนเปิดแอปเท่านั้น
Strict Bounded List via LTRIM: ตัดขอบเขต Timeline ใน Redis ให้คงเหลือไม่เกิน 800 ทวีตล่าสุดเสมอ:
-- Atomic Fan-out with Fixed Bound via Lua
redis.call('LPUSH', KEYS[1], ARGV[1])
redis.call('LTRIM', KEYS[1], 0, 799) -- บังคับให้ขนาดไม่เกิน 800 ป้องกัน Big Key ถาวร
Engineering References & Sources:
• InfoQ / Twitter Engineering: "Timelines at Scale" (Raffi Krikorian, VP of Platform Engineering)
• High Scalability: "How Twitter Serves Over 100 Million Timelines"
Case Study 3: Slack — Disaster Recovery Failover ที่ทำให้เกิด Replication Buffer OOM
1. บริบทและสถาปัตยกรรมเดิม (Background Context)
Slack พึ่งพา Redis อย่างหนักในการจัดการ State แบบ Real-time เช่น Presence Tracking (สถานะออนไลน์/ออฟไลน์), Unread Message Badges, และ Channel Member State ของผู้ใช้นับสิบล้านคน โดยวาง Topology แบบ Master-Replica พร้อม Sentinel Quorum
+------------------------------------------------------+
| REDIS MASTER (120GB RAM) |
| |
| 1. Network Glitch -> Sentinel triggers Failover |
| 2. Replica requests PSYNC |
| 3. Backlog Buffer Full -> Falls back to FULL SYNC |
| 4. fork() -> BGSAVE (120GB Dump) |
| |
| [ Incoming Client Writes: 50,000 ops/sec ] |
| | |
| v |
| [ client-output-buffer-limit slave (RAM Overflow) ] |
| | |
| v |
| Linux Kernel OOM Killer executes: SIGKILL redis-server!
+------------------------------------------------------+
2. สาเหตุทางสถาปัตยกรรมระดับลึก (Architectural Root Cause)
Transient Network Blip: เครือข่ายใน Data Center กระตุกชั่วครู่ ทำให้ Replica ขาดการเชื่อมต่อจาก Master
Replication Backlog Buffer Undersized: เมื่อ Replica ต่อกลับมาและขอทำ Partial Resync (PSYNC) พบว่า Replication Offset หลุดออกจาก Backlog Buffer ไปแล้ว Master จึงถูกบีบให้เข้าสู่โหมด Full Resynchronization
Heavy Fork & Copy-on-Write (COW): บนเครื่อง Master ที่ใช้ RAM 120GB การเรียก System Call fork() เพื่อสร้าง Child Process รัน BGSAVE ก่อให้เกิด Fork Latency และ Copy-on-Write Memory Duplication
Replication Output Buffer Exhaustion: ระหว่างที่ Child กำลังเขียน RDB Dump ลงดิสก์ คำสั่ง Write ใหม่จากผู้ใช้ 50,000 Ops/Sec ยังคงไหลเข้ามา Master ต้องกักเก็บคำสั่งเหล่านี้ไว้ใน Replication Output Buffer ใน RAM จนทะลุเพดาน client-output-buffer-limit
Cascading Crash: Master ตัดการเชื่อมต่อของ Replica ทิ้ง แต่ Replica ก็พยายาม Reconnect ใหม่และขอ Full Sync ซ้ำๆ วนลูปจน RAM ของ Master ทะลัก และถูก Linux Kernel OOM Killer สั่งฆ่า Process ดับทันที
3. การแก้ไขเชิงสถาปัตยกรรม (Architectural Solution)
Engineering References & Sources:
• Slack Engineering Blog: "Scaling Slack's Cache: Lessons Learned from Massive Scale"
• AWS / Redis Labs Case Study: "Slack's Real-time Messaging Architecture"
Case Study 4: Grab / Uber — Geo-Distributed Driver Dispatch & Memory Eviction Loss
1. บริบทและสถาปัตยกรรมเดิม (Background Context)
ระบบ Real-time Driver Dispatch ของแอปเรียกรถอย่าง Grab และ Uber อาศัย Redis GEO (เช่น GEOADD, GEORADIUS / GEOSEARCH) ในการระบุพิกัดคนขับนับแสนคนเพื่อจับคู่กับผู้โดยสารที่ใกล้ที่สุดภายในเสี้ยววินาที โดยเบื้องหลัง Redis จะแปลงละติจูด/ลองจิจูดเป็น 52-bit Geohash Integer บรรจุลงใน Sorted Set (ZSet)
Drivers' GPS Updates (Every 3 seconds)
|
v
+-----------------------------------------------------------+
| Redis Cluster: Key = "drivers:geo:bangkok" (ZSet/Geohash) |
| |
| Memory Policy: volatile-lru |
| Key TTL: None (Forgotten!) |
+-----------------------------------------------------------+
|
v
[ RAM hits maxmemory limit (e.g. 32GB) ]
|
v (volatile-lru searches for keys WITH TTL to evict)
[ NO KEYS WITH TTL FOUND! ]
|
v
Redis halts all WRITES and throws:
"OOM command not allowed when used memory > 'maxmemory'"
|
v
[ Dispatch System Down: Drivers' GPS frozen, No cars available! ]
2. สาเหตุทางสถาปัตยกรรมระดับลึก (Architectural Root Cause)
The Volatile Eviction Trap: วิศวกรตั้งค่า maxmemory-policy volatile-lru โดยเข้าใจว่าระบบจะช่วยเตะข้อมูลเก่าทิ้งอัตโนมัติเมื่อ RAM เต็ม แต่ ลืมกำหนด TTL ให้กับ Key หลักของ Geohash (เพราะมองว่าเป็น Master Geo Set)
High-Frequency Ingestion: คนขับกว่า 100,000 คันส่งสัญญาณ GPS ทุก 3 วินาที ในช่วงเทศกาลหรือฝนตก Traffic พุ่งจน RAM ของ Redis ชนเพดาน maxmemory
Out-of-Memory Lockout: เนื่องจากนโยบาย volatile-lru จะเลือก Evict เฉพาะ Key ที่ "มี TTL เท่านั้น" เมื่อไม่มี Key ไหนมี TTL เลย Redis จึงไม่สามารถลบข้อมูลใดๆ ได้ และเปลี่ยนพฤติกรรมเป็นการ ปฏิเสธคำสั่ง Write ทั้งหมดพร้อมพ่น Error: OOM command not allowed
Business Impact: ระบบไม่สามารถบันทึกพิกัด GPS ล่าสุดของคนขับได้ พิกัดค้าง ระบบจับคู่รถล้มเหลวทั้งเมือง
3. การแก้ไขเชิงสถาปัตยกรรม (Architectural Solution)
Shift to `allkeys-lru`: เปลี่ยนนโยบาย Eviction เป็น allkeys-lru เพื่อให้ Redis สามารถเลือกเตะข้อมูลที่ไม่ได้ใช้นานที่สุดออกจาก Keyspace ทั้งหมดได้เสมอเมื่อเกิดวิกฤต RAM เต็ม
Granular Geohash Keys with Dynamic TTL: เลิกใช้ Single Giant Geo Key แต่ให้แบ่ง Key ตาม Geohash Cell Level (เช่น geo:cell:w4rq9:{timestamp_minute}) และตั้ง TTL 15–30 วินาที เพื่อให้ Memory หมุนเวียนกลับมาใช้งานได้เองตามธรรมชาติ
Cluster Isolation: แยกคลัสเตอร์ระหว่าง Ephemeral Ingestion Data (GPS พิกัดรถ) ออกจาก Core Financial/Stateful Data (ยอดเงินในกระเป๋าคนขับ) อย่างเด็ดขาด
Engineering References & Sources:
• Grab Tech Blog: "How Grab Uses Redis for Real-time Geospatial Querying"
• Uber Engineering: "Manifold: Large-Scale Geospatial Indexing and Dispatch"
Production Post-Mortem Assessment Bank (Interactive Knowledge Check)
ทดสอบความเข้าใจเชิงลึกเกี่ยวกับข้อจำกัดทางฮาร์ดแวร์ ปัญหา Network Saturation และการป้องกันวิกฤตระดับ Production จากกรณีศึกษาจริง 10 ข้อ
Post-Mortem Score:
0 / 10
Reset Answers