Developer Tools

Redis เกินกว่าแค่ Cache: Data Structures, Pub/Sub และแอปพลิเคชันแบบ Real-Time

เรียนรู้วิธีใช้ Redis มากกว่าแค่ cache — สำรวจ data structures, pub/sub messaging, streams และรูปแบบการสร้างแอปพลิเคชันที่รวดเร็วและรองรับการขยายตัวได้

10 นาทีในการอ่าน

Server room with glowing connections

นักพัฒนาส่วนใหญ่รู้จัก Redis ในฐานะ "cache ที่รวดเร็ว" แต่ Redis คือเซิร์ฟเวอร์ข้อมูลแบบ in-memory ที่มีฟีเจอร์ครบครัน รองรับทั้ง session management, leaderboard แบบ real-time, rate limiting, message queues และอีกมากมาย — ทั้งหมดนี้ด้วย latency ระดับต่ำกว่ามิลลิวินาที

ทำไม Redis ถึงเร็วมาก

Redis เก็บข้อมูลทุกอย่างไว้ใน memory และใช้ single-threaded event loop ในการประมวลผลคำสั่ง ไม่มี disk I/O ในการอ่านข้อมูล ไม่มีการรอ lock และไม่มี context switching ผลลัพธ์คือ: 100,000+ operations ต่อวินาที บนฮาร์ดแวร์ทั่วไป

ตัวเปรียบเทียบ latency ทั่วไป:
┌─────────────────┬──────────────┐
│ Operation       │ Latency      │
├─────────────────┼──────────────┤
│ Redis GET       │ 0.1 ms       │
│ PostgreSQL SEL  │ 1-5 ms       │
│ REST API call   │ 50-200 ms    │
│ Disk read       │ 5-10 ms      │
└─────────────────┴──────────────┘

Data structures หลัก

Redis ไม่ได้เป็นแค่ key-value แต่รองรับ data structures ที่หลากหลายซึ่งตอบโจทย์ความต้องการของแอปพลิเคชันได้โดยตรง

Strings — พื้นฐานที่สำคัญ

Strings เก็บข้อความ ตัวเลข หรือข้อมูลไบนารีได้สูงสุด 512 MB:

SET user:1001:name "Alice"
GET user:1001:name          # "Alice"

# Atomic counter
INCR page:views             # 1, 2, 3, ...
INCRBY cart:total 2500      # Add 25.00 (store cents)

# Expiring keys (TTL)
SET session:abc123 "{...}" EX 3600   # Expires in 1 hour
TTL session:abc123                    # Seconds remaining

Hashes — objects ขนาดเล็ก

Hashes เหมาะมากสำหรับการเก็บ objects โดยไม่ต้องมีค่าใช้จ่ายในการ serialize:

HSET user:1001 name "Alice" email "alice@example.com" plan "pro"
HGET user:1001 name         # "Alice"
HGETALL user:1001           # All fields and values
HINCRBY user:1001 logins 1  # Atomic field increment

Hashes ใช้ memory น้อยกว่าถึง 10 เท่า เมื่อเทียบกับการเก็บแต่ละ field เป็น key แยกกัน

Lists — queues และ feeds

Collection แบบเรียงลำดับที่รองรับการ push/pop จากทั้งสองด้าน:

LPUSH notifications:alice "New comment on your post"
LPUSH notifications:alice "You have a new follower"
LRANGE notifications:alice 0 9    # Latest 10 notifications

# Use as a queue (producer/consumer)
RPUSH queue:emails "send-welcome"
LPOP queue:emails                  # Process next job

Sets — collections ที่ไม่ซ้ำกัน

Collection ที่ไม่เรียงลำดับของ strings ที่ไม่ซ้ำกัน:

SADD tags:post:42 "redis" "database" "nosql"
SMEMBERS tags:post:42             # All tags
SISMEMBER tags:post:42 "redis"    # true

# Set operations
SINTER tags:post:42 tags:post:99  # Common tags
SUNION tags:post:42 tags:post:99  # All tags combined

Sorted Sets — leaderboards และการจัดอันดับ

Sets ที่มี score สำหรับแต่ละ member และเรียงลำดับโดยอัตโนมัติ:

ZADD leaderboard 1500 "alice" 2300 "bob" 1800 "charlie"
ZREVRANGE leaderboard 0 2 WITHSCORES   # Top 3 players
ZRANK leaderboard "alice"               # Rank (0-based)
ZINCRBY leaderboard 100 "alice"         # Alice scores 100 points

นี่คือวิธีที่ leaderboard ในเกม, โพสต์ยอดนิยม และ priority queues ถูกสร้างในระดับ scale ใหญ่

รูปแบบการใช้งานในโลกจริง

Pattern 1: Session storage

แทนที่จะเก็บ session ไว้ในฐานข้อมูล ใช้ Redis เพื่อการค้นหาที่รวดเร็วทันที:

import redis
r = redis.Redis()

# Store session
r.setex(f"session:{token}", 3600, json.dumps(user_data))

# Retrieve session
data = r.get(f"session:{token}")

ทำไม? Session ในฐานข้อมูลเพิ่ม latency 1-5ms ต่อ request แต่ Redis session เพิ่มเพียง 0.1ms ที่ 1,000 req/s ประหยัดเวลาได้ถึง 5 วินาทีต่อวินาที

Pattern 2: Rate limiting

Sliding window rate limiter ด้วยเพียง 3 คำสั่ง:

# Allow 100 requests per minute per user
MULTI
INCR rate:user:1001
EXPIRE rate:user:1001 60
EXEC

# Check: if INCR result > 100, reject request

Pattern 3: Caching แบบ cache-aside

รูปแบบ caching ที่พบบ่อยที่สุด:

def get_user(user_id):
    # 1. Check cache
    cached = redis.get(f"user:{user_id}")
    if cached:
        return json.loads(cached)
    
    # 2. Cache miss → query database
    user = db.query("SELECT * FROM users WHERE id = %s", user_id)
    
    # 3. Populate cache (expire in 5 minutes)
    redis.setex(f"user:{user_id}", 300, json.dumps(user))
    return user

Pattern 4: Pub/Sub สำหรับ real-time events

Redis Pub/Sub เปิดใช้งาน real-time messaging ระหว่าง services:

# Subscriber (listens for messages)
SUBSCRIBE chat:room:42

# Publisher (sends messages)
PUBLISH chat:room:42 "Hello everyone!"

ในโค้ดแอปพลิเคชัน:

# Publisher
redis.publish("notifications", json.dumps({
    "type": "new_order",
    "order_id": 12345
}))

# Subscriber
pubsub = redis.pubsub()
pubsub.subscribe("notifications")
for message in pubsub.listen():
    handle_notification(message)

Pattern 5: Distributed locks

ป้องกัน race conditions ระหว่างเซิร์ฟเวอร์หลายตัว:

# Acquire lock (NX = only if not exists, EX = expire)
SET lock:invoice:gen "worker-1" NX EX 30

# Release lock (only if you own it — use Lua script)

Redis Streams — event sourcing และ message queues

Streams คือคำตอบของ Redis สำหรับ event logs แบบ Kafka:

# Add events
XADD orders * user_id 1001 product "Widget" qty 3
XADD orders * user_id 1002 product "Gadget" qty 1

# Read latest events
XRANGE orders - + COUNT 10

# Consumer groups (multiple workers)
XGROUP CREATE orders processors $ MKSTREAM
XREADGROUP GROUP processors worker-1 COUNT 5 BLOCK 2000 STREAMS orders >

Streams มอบ event logs ที่คงทนและสามารถเล่นซ้ำได้พร้อม consumer groups — เหมาะมากสำหรับการสื่อสารระหว่าง microservices

เคล็ดลับด้านประสิทธิภาพ

1. ใช้ pipelining สำหรับการทำงานจำนวนมาก

แทนที่จะส่ง 100 round trips แยกกัน ส่ง 100 คำสั่งพร้อมกันครั้งเดียว:

pipe = redis.pipeline()
for i in range(100):
    pipe.set(f"key:{i}", f"value:{i}")
pipe.execute()  # Single round trip

2. เลือก data structure ให้เหมาะสม

  • ต้องการ counter? → INCR (ไม่ใช่ GET + SET)
  • ต้องการ object? → Hash (ไม่ใช่ JSON string ที่ serialize)
  • ต้องการ items ที่ไม่ซ้ำ? → Set (ไม่ใช่ List + logic ตรวจซ้ำ)
  • ต้องการจัดอันดับ? → Sorted Set (ไม่ใช่ Sort หลัง query)

3. ตั้ง TTL ทุกครั้ง

Memory มีจำกัด ตั้ง expiration สำหรับ cache keys เสมอ:

SET cache:api:result "{...}" EX 300   # 5-minute TTL

4. ใช้รูปแบบการตั้งชื่อ key ที่สม่ำเสมอ

resource:id:field
user:1001:profile
cache:api:v2:products
session:abc123
queue:emails:pending

Persistence: RDB vs AOF

Redis สามารถบันทึกข้อมูลลง disk เพื่อความทนทาน:

Mode วิธีทำงาน ข้อดีข้อเสีย
RDB Point-in-time snapshots Recovery เร็ว แต่อาจสูญเสียข้อมูลได้
AOF บันทึกทุก write operation ช้ากว่า แต่สูญเสียข้อมูลน้อยมาก
Both RDB + AOF รวมกัน ความทนทานสูงสุด
# In redis.conf
save 900 1          # Snapshot if 1 key changed in 900s
appendonly yes      # Enable AOF
appendfsync everysec  # Fsync every second

อ้างอิงคำสั่งด่วน

ต้องการค้นหาคำสั่ง Redis เฉพาะเจาะจง? ใช้ Redis Cheat Sheet ของเรา — ครอบคลุมคำสั่งกว่า 200+ คำสั่ง จัดหมวดหมู่ตาม data structure พร้อม syntax, ตัวอย่าง และคัดลอกด้วยคลิกเดียว

สรุป

Redis มีความสามารถมากกว่าแค่ cache data structures ของมันสอดคล้องกับความต้องการของแอปพลิเคชันจริงได้อย่างลงตัว:

  • Strings → sessions, counters, cache ง่ายๆ
  • Hashes → user profiles, configuration objects
  • Lists → queues, activity feeds, รายการล่าสุด
  • Sets → tags, unique visitors, ความสัมพันธ์
  • Sorted Sets → leaderboards, rankings, priority queues
  • Streams → event sourcing, message queues
  • Pub/Sub → real-time notifications, chat

เริ่มต้นด้วย caching แต่อย่าหยุดแค่นั้น Redis สามารถแทนที่ส่วนประกอบของ infrastructure หลายอย่างใน stack ของคุณ — และทำทั้งหมดนั้นด้วยความเร็วระดับต่ำกว่ามิลลิวินาที