Architecture Mastery • Phase 2

Redis Fundamentals, History & RAM-First Internals

The Paradigm Shift: From Disk Buffers to In-Memory Event Loop, Memory Encodings, and High-Performance Persistence

1. The "Why" & Origins: Breaking the Disk Bottleneck

ในปี 2009 วิศวกรชาวซิซิลีชื่อ Salvatore Sanfilippo (antirez) กำลังพัฒนาระบบ Real-time Web Log Analytics ชื่อว่า LLOOGG ปัญหาใหญ่ที่เขาเผชิญคือ: Relational Database (MySQL) รับโหลดการเขียน Log วิเคราะห์แบบ Real-time ไม่ไหว

แม้จะพยายามปรับแต่ง Index และรันบนเซิร์ฟเวอร์ราคาแพง Disk I/O Bottleneck ก็ยังคงเกิดขึ้น เพราะทุกๆ การอัปเดตตัวเลขสถิติ (Pageview Counter, Unique Visitors) RDBMS ต้องทำ Disk-bound operations, B+ Tree updates, และ Lock contention ข้าม Transactions

ทำไม Memcached ถึงยังตอบโจทย์ไม่สุด?

ในยุคนั้น Memcached เป็น In-Memory Key-Value Cache ยอดนิยม แต่มีข้อจำกัดเชิงสถาปัตยกรรมที่สำคัญ:

การถือกำเนิดของ RE.D.I.S. (REmote DIctionary Server):
Antirez จึงสร้าง Data Structure Server ที่เปลี่ยนมุมมองใหม่: ไม่ใช่แค่ "Cache เก็บสตริง" แต่เป็น "Remote Data Structure Engine" ที่ให้ Client ส่งคำสั่งเชิงตรรกะมาประมวลผลตรงๆ ใน RAM ของเซิร์ฟเวอร์แบบ Atomic

2. RAM-First Architecture: สถาปัตยกรรมที่ทิ้ง Disk ออกจาก Critical Path

เมื่อเปรียบเทียบกับ PostgreSQL (Disk-First) ที่เราศึกษาใน Phase 1 สถาปัตยกรรมของ Redis แตกต่างกันอย่างสิ้นเชิงในระดับรากฐาน:

DISK-FIRST ARCHITECTURE (e.g. PostgreSQL) ========================================= Client Query ---> [ Optimizer ] ---> [ Buffer Pool Lookup ] | (Buffer Miss) | (Slow Page Fault) v [ OS Page Cache / NVMe Disk ] (Latency: 10,000 - 50,000 ns) | v [ shared_buffers ] | [ WAL Flush (fsync) ]-+-> (Ack Client) RAM-FIRST ARCHITECTURE (Redis) ============================== Client Command -> [ Network Socket ] -> [ Multiplexing (epoll) ] | v [ SINGLE-THREAD EVENT LOOP ] | (Direct RAM Memory Access) (Latency: 50 - 100 ns) | +-> [ Reply Buffer ] -> Client Ack : : (Asynchronous / Non-blocking) v [ RDB Fork / AOF Write ] (Background/Separate)
มิติทางสถาปัตยกรรม Disk-First (RDBMS / PostgreSQL) RAM-First (Redis)
Primary Data Home Physical Disk Blocks (Tablespace 8KB Pages) DRAM Main Memory (Direct Pointers / Allocators)
บทบาทของ RAM Cache ชั่วคราว (Buffer Pool / shared_buffers) Single Source of Truth ในการประมวลผล
Critical Path Latency ~500 µs – 10 ms (ผูกกับ Disk I/O & WAL fsync) ~10 – 100 µs (RAM Speed ไร้ Disk Wait)
Data Layout ใน RAM Fixed-size 8KB Pages จำลองโครงสร้างดิสก์ Native C Data Structures (SkipList, Dict, SDS)
Concurrency Model Multi-process / Multi-thread + Heavy Row/Table Locks Single-Threaded Event Loop ไร้ Lock Contention

3. The Single-Threaded Event Loop & I/O Multiplexing

คำถามยอดฮิตระดับ Lead Architect: "ในยุค Multi-Core CPU ทำไม Redis ถึงใช้ Single Thread ในการประมวลผลคำสั่ง แล้วทำไมมันถึงเร็วกว่าระบบ Multi-threaded RDBMS ทั่วไป?"

+-------------------------------------------------------------------------+ | REDIS PROCESS ARCHITECTURE | | | | Clients (10,000+ Sockets) | | | | | | | v v v | | +------------------------+ | | | OS Kernel epoll/kqueue | (I/O Multiplexing: Monitor socket readiness)| | +-----------+------------+ | | | Ready File Events (Read/Write) | | v | | +-------------------------------------------------------------------+ | | | aeEventLoop (Single Main Thread) | | | | | | | | +------------------+ +-------------------+ +--------------+ | | | | | Read QueryBuf |-->| Execute Command |-->| Write Output | | | | | | (Parse RESP) | | (RAM Dictionary) | | (Send reply) | | | | | +------------------+ +-------------------+ +--------------+ | | | | ^ | | | | | Time Events (ServerCron: Eviction, Expiration, Stats) | | | +-----------+-------------------------------------------------------+ | | | | (Background BIO Threads: Async AOF fsync, Lazy Freeing / UNLINK) | +-------------------------------------------------------------------------+

3 เหตุผลทางฟิสิกส์ฮาร์ดแวร์ที่ Single Thread ชนะ

  1. No Context Switching Overhead: การสลับ Thread ของ OS บังคับให้ CPU ต้อง Save/Restore Registers, สลับ Kernel/User Mode, และที่ร้ายแรงที่สุดคือทำลาย CPU L1/L2 Cache (Cache Pollution / Thrashing) การรัน Single Thread ต่อเนื่องทำให้ CPU Instruction Cache ทำงานได้อย่างเต็มประสิทธิภาพสูงสุด
  2. No Concurrency Control Overhead: ไม่ต้องมี Mutex, Spinlock, Semaphore หรือ Read-Write Locks ในการปกป้อง Data Structures ระดับ RAM ไม่มีความเสี่ยง Deadlock และไม่มี CPU Cycles สูญเปล่าไปกับการแย่ง Lock
  3. CPU Is Not the Bottleneck: Bottleneck ของ In-Memory Store คืองาน Memory Bandwidth และ Network Bandwidth (Packet processing) ไม่ใช่ CPU Speed โดย 1 Core ของ CPU ยุคใหม่สามารถ Execute Redis Memory Operations ได้ทะลุ 100,000–200,000 Ops/Sec สบายๆ
กฎเหล็กของ Single-Threaded Event Loop (Zero Blocking Rule):
เนื่องจากคำสั่งถูกรันต่อคิวกันบน Thread เดียว หากมีคำสั่งใดคำสั่งหนึ่งกินเวลา CPU นาน (Time Complexity $O(N)$ เช่น KEYS *, HGETALL บนแฮชยักษ์, หรือ FLUSHALL) ทุก Client ในระบบจะถูก Block ทั้งหมดทันที! นี่คือจุดกำเนิดของข้อห้ามใช้งานคำสั่งอันตรายบน Production

4. Data Structures & Memory Encodings (Under The Hood)

หัวใจที่ทำให้ Redis ประหยัด RAM มหาศาลและเข้าถึงข้อมูลระดับ Sub-microsecond คือการเลือกใช้ Internal Memory Encodings ที่ปรับเปลี่ยนตามขนาดข้อมูลโดยอัตโนมัติ

Logical Type Internal Compact Encoding Threshold Triggered -------------------------------------------------------------------------------- String -------> int / embstr / raw (44 bytes boundary) List -------> quicklist (Listpack/Ziplist) (list-max-listpack-size) Hash -------> listpack (ziplist) -> hashtable (hash-max-listpack-entries) Set -------> intset -> hashtable (set-max-intset-entries) Sorted Set -------> listpack -> skiplist + dict (zset-max-listpack-entries)

1. Simple Dynamic String (SDS) vs C-String

Redis ไม่ใช้ C-String แบบดั้งเดิม (char* สิ้นสุดด้วย ) แต่สร้างโครงสร้างข้อมูล SDS ขึ้นมาแทน:

2. Skip List (zskiplist) ใน Sorted Set

Sorted Set (ZSet) ใช้การผสมผสานระหว่าง Hash Table (สำหรับค้นหา Score ของ Member ด้วยเวลา $O(1)$) และ Skip List (สำหรับการทำ Range Query $O(\log N)$):

Level 4: [Head] -------------------------------------------------> [85] -> NULL Level 3: [Head] ------------------------> [35] -------------------> [85] -> NULL Level 2: [Head] -----------> [15] ------> [35] ------> [60] ------> [85] -> NULL Level 1: [Head] -> [5] ----> [15] -> [25]-> [35] ->[50]-> [60] -> [70]-> [85] -> NULL (Doubly linked at Level 1 for bidirectional reverse scans)
ทำไม Antirez เลือก Skip List แทน Red-Black Tree หรือ AVL Tree?
  1. Range Scan ที่เรียบง่าย: Leaf Level ของ Skip List คือ Linked List ปกติ ทำให้คำสั่ง ZRANGEBYSCORE เดินหน้าและถอยหลังได้เรียบง่ายกว่า Tree In-order Traversal มาก
  2. Lock-free / Low-cost Rebalancing: Skip List ใช้ Random Height (Probabilistic Balance) ไม่ต้องหมุน Tree (Rotation) ที่ซับซ้อนเมื่อมีการแทรกหรือลบข้อมูล
  3. Memory Locality: ปรับจูน Pointer Overhead ได้ยืดหยุ่นกว่า

3. Compact Data Structures (Intset & Listpack/Ziplist)

เมื่อ Collection มีขนาดเล็ก Redis จะไม่ใช้โครงสร้าง Pointer-heavy แต่จะบีบข้อมูลอัดเรียงกันเป็นไบต์ใน Array ก้อนเดียว (Contiguous Memory Block):

5. Persistence Engineering: RDB vs AOF Under The Hood

แม้ Redis จะเป็น In-Memory แต่ข้อมูลสามารถคงอยู่ได้อย่างปลอดภัยผ่าน 2 กลไกหลักที่ออกแบบมาเพื่อลดผลกระทบต่อ Main Event Loop ให้เหลือน้อยที่สุด

คุณสมบัติ RDB (Redis Database Snapshot) AOF (Append-Only File)
รูปแบบการทำงาน Point-in-Time Binary Snapshot ณ วินาทีที่กำหนด Write Log บันทึกทุกคำสั่งแก้ไข (Text/RESP format)
ความถี่ในการบันทึก ทุกๆ หลายนาที (เช่น ทุก 5-15 นาที) ทุกคำสั่ง / ทุกวินาที (appendfsync everysec)
Recovery Speed เร็วมาก (อ่าน Binary dump กลับเข้า RAM ตรงๆ) ช้ากว่า (ต้อง Replay คำสั่ง SQL/Command ใหม่ทีละแถว)
ความเสี่ยงข้อมูลสูญหาย สูง (ข้อมูลที่เพิ่งเขียนหลัง Snapshot ล่าสุดจะหายทั้งหมด) ต่ำมาก (สูญหายสูงสุดเพียง 1 วินาทีในโหมด everysec)
ผลกระทบต่อ Latency Fork time latency spike เมื่อ RAM มีขนาดใหญ่ Disk I/O continuous write (ผูกกับ fsync overhead)

กลไก Copy-on-Write (COW) ใน RDB Snapshotting

เมื่อ Redis รันคำสั่ง BGSAVE หรือเงื่อนไข Auto-save ทำงาน:

  1. Redis Main Process จะเรียก System Call fork() เพื่อสร้าง Child Process ขึ้นมา
  2. ด้วยคุณสมบัติ Copy-on-Write (COW) ของ Linux Kernel: Child Process จะแชร์ Virtual Memory Page ร่วมกับ Parent Process ทันทีโดยไม่ต้องคัดลอก RAM ซ้ำซ้อน ณ จุดเริ่มต้น
  3. Child Process ทำหน้าที่วน Loop อ่าน Data Pages ใน RAM แล้ว Serialize เขียนลงไฟล์ dump.rdb บนดิสก์
  4. ความเสี่ยง Production: หากช่วงที่ Child กำลัง Save มีคำสั่ง Write ปริมาณมหาศาลยิงเข้ามาที่ Parent Kernel จะต้อง Duplicate Physical Page ที่ถูกแก้ไขนั้นแยกออกมา ทำให้ Memory Usage ของเครื่องพุ่งทะยานขึ้นได้ (อาจพุ่งสูงถึง 1.5x - 2x ของ RAM ปกติจนเสี่ยงโดน OS OOM Killer ฆ่าทิ้ง)
เปรียบเทียบ AOF vs PostgreSQL WAL:
AOF ทำหน้าที่คล้าย Write-Ahead Log (WAL) ใน PostgreSQL แต่มีข้อแตกต่างทางสถาปัตยกรรมที่สำคัญคือ: PostgreSQL เขียน WAL "ก่อน" แก้ไขข้อมูลใน Buffer Pool (Write-Ahead) เพื่อการันตี Crash Recovery แต่ Redis เขียน AOF "หลัง" จากที่คำสั่ง Execute ใน RAM สำเร็จแล้ว ทำให้ Redis ไม่ต้องเสียเวลา Rollback คำสั่งที่ไม่ผ่าน Syntax check และไม่มี Disk latency ขัดขวาง Read/Write memory path!

6. Phase 2 Architectural Assessment Bank (In-Memory Mastery)

ชุดข้อสอบประเมินสถาปัตยกรรมระดับลึกสำหรับ Phase 2 เพื่อทดสอบความเข้าใจเรื่อง Bare-Metal RAM, Event Loop, Memory Encoding, Skip List และ Persistence พร้อมระบบตรวจคำตอบทันที

Phase 2 Assessment Score: 0 / 25