Redis คืออะไร?
ทดสอบบน Redis 8.10.2 (
redis:8.10.2-alpine), ioredis 6.0.0, Node 24 บน Mac (Apple silicon) — output ทุกบล็อกด้านล่างคัดลอกมาจากการรันจริง
In-memory key-value data store
หัวข้อที่มีชื่อว่า “In-memory key-value data store”Redis เก็บทุก key และค่าที่เชื่อมโยงโดยตรงใน RAM เมื่อแอปพลิเคชันของคุณ set key ข้อมูลจะอยู่ในหน่วยความจำ — ไม่มีการค้นหาดิสก์ ไม่มี page fault ไม่มี overhead ของ file system เมื่ออ่าน key กลับมา คำตอบก็มาจากหน่วยความจำเดิมนั้น นี่คือเหตุผลที่ความหน่วงของ Redis อยู่ต่ำกว่ามิลลิวินาทีอย่างสม่ำเสมอแม้ภายใต้โหลดหนัก
แต่ละค่ามี type Redis ไม่ใช่แค่ที่เก็บ string ธรรมดา — แต่เข้าใจโครงสร้างข้อมูลได้โดยตรง: Strings, Lists, Hashes, Sets, Sorted Sets, Streams และอีกมาก เซิร์ฟเวอร์สามารถทำงานกับโครงสร้างเหล่านั้นแบบ atomic ได้โดยตรง นั่นหมายความว่าคุณสามารถ append ลงใน list, เพิ่มค่า counter, หรือ pop จาก queue ได้โดยไม่ต้องล็อคระดับ application
การรันคำสั่งเป็น single-threaded — แต่ process ไม่ใช่
หัวข้อที่มีชื่อว่า “การรันคำสั่งเป็น single-threaded — แต่ process ไม่ใช่”Redis รันคำสั่งบน thread เดียว thread นั้นอ่านคำสั่งจาก socket, รันจนเสร็จ, เขียน reply แล้วจึงไปคำสั่งถัดไป ระหว่างที่คำสั่งหนึ่งทำงาน ไม่มีอะไรอื่นแตะ keyspace ได้
“Redis เป็น single-threaded” คือความจริงครึ่งเดียวที่คนพูดต่อกันมา เลยลองดู process จริงกัน นี่คือ ps -T (หนึ่งบรรทัดต่อหนึ่ง thread) ใน container หลังเพิ่งสตาร์ท:
PID USER TIME COMMAND 1 redis 0:00 redis-server *:6379 26 redis 0:00 {bio_close_file} redis-server *:6379 27 redis 0:00 {bio_aof} redis-server *:6379 28 redis 0:00 {bio_lazy_free} redis-server *:6379 29 redis 0:00 {jemalloc_bg_thd} redis-server *:6379 30 redis 0:00 {jemalloc_bg_thd} redis-server *:6379Thread 1 คือตัวที่รันคำสั่งของคุณ ส่วน thread bio_* ทำงานเบื้องหลังที่ต้องไม่บล็อก thread หลัก (ปิดไฟล์, fsync ของ AOF, คืนหน่วยความจำของ value ใหญ่แบบ lazy — ดู UNLINK ในบทหลัง) และ jemalloc_bg_thd คืองานดูแลของ allocator ส่วนฝั่ง I/O ดูได้ด้วย INFO threads และปรับด้วย io-threads:
127.0.0.1:6379> PINGPONG127.0.0.1:6379> CONFIG GET io-threads1) "io-threads"2) "1"127.0.0.1:6379> SET language "Redis"OK127.0.0.1:6379> TYPE languagestringเมื่อสตาร์ทเซิร์ฟเวอร์ทิ้งอีกตัวด้วย redis-server --io-threads 4 จะมี I/O thread เพิ่ม (INFO threads แสดง io_thread_0 ถึง io_thread_3) และ Redis 8 ปรับปรุงวิธีที่ thread เหล่านี้อ่าน/เขียน socket ของ client แต่ถึงอย่างนั้น การรันคำสั่งยังอยู่บน main thread — I/O thread แค่ parse และเขียน byte แหล่งอ้างอิง: Redis 8.0 what’s new และส่วน io-threads ใน redis.conf ที่มากับตัวติดตั้ง
การออกแบบนี้มีผลสองประการ:
- ความเร็ว — ไม่มี lock contention บนข้อมูล ไม่มี context switch ระหว่างคำสั่ง core เดียวก็พอสำหรับคำสั่งง่าย ๆ หลายแสนคำสั่งต่อวินาที
- แต่ละคำสั่งเป็น atomic — ระหว่างที่คำสั่งหนึ่งรัน ไม่มีคำสั่งอื่นเห็นสถานะครึ่ง ๆ กลาง ๆ แต่นั่นคือ ต่อหนึ่งคำสั่ง สองคำสั่งติดกันคือสองรอบของ event loop และ client อื่นแทรกได้ระหว่างนั้น (จะพิสูจน์ด้านล่าง)
Data-structure server ไม่ใช่แค่ cache
หัวข้อที่มีชื่อว่า “Data-structure server ไม่ใช่แค่ cache”ประโยคที่ว่า “Redis คือ cache” เป็นเรื่องทั่วไปแต่ไม่ครบถ้วน cache ธรรมดา map key ไปยัง blob ที่ไม่รู้โครงสร้าง Redis map key ไปยัง โครงสร้างข้อมูลที่หลากหลาย ที่คุณสามารถจัดการได้ฝั่งเซิร์ฟเวอร์:
| ประเภท | คำอธิบาย |
|---|---|
| String | ข้อความ, ตัวเลข, หรือ raw bytes ได้ถึง 512 MB |
| List | ลำดับที่เรียงไว้; push/pop จากทั้งสองด้าน |
| Hash | field-value map ภายใน key เดียว |
| Set | คอลเลกชันของ member ที่ไม่ซ้ำกันแบบไม่มีลำดับ |
| Sorted Set | Set ที่แต่ละ member มี numeric score |
| Stream | บันทึกแบบ append-only ของ entry ที่มี timestamp |
เพราะการทำงานเกิดขึ้นฝั่งเซิร์ฟเวอร์ คุณจึงลด round trip ได้ แทนที่จะ GET → แก้ในแอป → SET คุณใช้ LPUSH หรือ ZINCRBY ในการเรียกเครือข่ายครั้งเดียว
Redis 8 — distribution เดียว ไม่ต้องติดตั้ง module
หัวข้อที่มีชื่อว่า “Redis 8 — distribution เดียว ไม่ต้องติดตั้ง module”หลายปีที่ความสามารถเสริมอยู่ใน module ที่ต้องติดตั้งแยก และ “Redis Stack” คือชุดที่รวมทั้งหมดมาให้ Redis 8 รวมสองอย่างเข้าด้วยกัน: สิ่งที่เคยเรียก Redis Community Edition ตอนนี้ชื่อ Redis Open Source และฟีเจอร์ที่เคยเป็น module ถูกฝังในเซิร์ฟเวอร์เลย ลองถามเซิร์ฟเวอร์จริงว่าโหลดอะไรไว้ (ตัดให้เหลือชื่อกับ version):
127.0.0.1:6379> MODULE LIST # names + ver only; each entry also has path/args vectorset ver 1 bf ver 81001 (RedisBloom, path .../redisbloom.so) timeseries ver 81001 search ver 81001 ReJSON ver 81000และ type JSON ที่ฝังมาใช้ได้ทันที ไม่ต้องติดตั้ง:
127.0.0.1:6379> JSON.SET user:1 $ '{"name":"Ada"}'OK127.0.0.1:6379> JSON.GET user:1"{\"name\":\"Ada\"}"127.0.0.1:6379> TYPE user:1ReJSON-RL127.0.0.1:6379> DEL user:1(integer) 1| ฝังมาให้ตั้งแต่ Redis 8 | ได้อะไร |
|---|---|
| JSON | เก็บและแก้ JSON document ในที่เดิมด้วย JSONPath |
| Search and query | Secondary index, full-text search และ vector search บน hash หรือ JSON |
| Time series | Downsampling, aggregation และ retention สำหรับ sample ที่มี timestamp |
| Probabilistic | Bloom filter, Cuckoo filter, Count-Min sketch, Top-K, t-digest |
| Vector set (beta) | type แบบ native สำหรับ vector similarity search |
Redis 8 ยังมาพร้อมการเพิ่มประสิทธิภาพครั้งใหญ่ที่สุดของโปรเจกต์ — release notes ระบุว่ามีการปรับปรุงกว่า 30 จุด, หน่วงคำสั่งลดลงสูงสุด 87% และ replication เร็วขึ้นสูงสุด 18% (Redis 8.0 what’s new; เป็นตัวเลขจากผู้พัฒนา ไม่ได้วัดในบทนี้) และมี hash command ใหม่สามตัวที่จะพบในบท Hashes
Licence และ fork — “Redis” ปี 2026 หมายถึงอะไร
หัวข้อที่มีชื่อว่า “Licence และ fork — “Redis” ปี 2026 หมายถึงอะไร”เซิร์ฟเวอร์ที่คุณรันมีผลทั้งด้านกฎหมายและความเข้ากันได้ จึงควรรู้ไทม์ไลน์:
| วันที่ | เหตุการณ์ | แหล่งที่มา |
|---|---|---|
| จนถึง Redis 7.2 | BSD-3-Clause open source | — |
| 2024-03-28 | Redis เปลี่ยนเป็น dual source-available RSALv2 / SSPLv1 | redis.io: dual source-available licensing |
| 8 วันต่อมา | Linux Foundation ประกาศ Valkey fork แบบ BSD จาก Redis 7.2.4 (release แรก 7.2.5) | valkey.io: history |
| 2025-05-01 | เพิ่ม AGPLv3 เป็น licence ทางเลือกที่สามตั้งแต่ Redis 8 | redis.io: AGPLv3 |
คอร์สนี้สอน Redis Open Source 8.x Valkey ใช้ wire protocol เดียวกัน และคำสั่งหลัก (strings, lists, hashes, sets, sorted sets, streams, MULTI, Lua) ตรงกับสาย 7.2 แต่ฟีเจอร์ใหม่ ๆ เริ่มแตกต่างกัน คำสั่งที่เกิดใน Redis 8 — HGETEX, HSETEX, HGETDEL (8.0), XACKDEL (8.2), SET ... IFEQ และ DELEX (8.4) — จะระบุ version ไว้ตรงที่ปรากฏ เพื่อให้รู้ว่าบทไหนไม่รันบนเซิร์ฟเวอร์รุ่นเก่าหรือ Valkey ผมไม่ได้รัน Valkey container ในบทนี้: ข้อความเรื่องความเข้ากันได้มาจากเอกสารข้างต้น ไม่ใช่จากการทดสอบ
Use case ที่พบบ่อย
หัวข้อที่มีชื่อว่า “Use case ที่พบบ่อย”| Use case | ฟีเจอร์ของ Redis |
|---|---|
| Cache | TTL-based expiry บน key ใดก็ได้ |
| Session store | Hash ต่อ session ID อ่านเร็ว |
| Task queue | LPUSH / BRPOP บน List |
| Leaderboard | Sorted Set กับ ZADD / ZRANK |
| Rate limiter | INCR + EXPIRE บน counter key |
| Pub/Sub messaging | ช่อง PUBLISH / SUBSCRIBE |
ตรวจ type แบบเร็ว
หัวข้อที่มีชื่อว่า “ตรวจ type แบบเร็ว”รัน PING เพื่อยืนยันว่าเซิร์ฟเวอร์ยังอยู่ แล้วใช้ TYPE ตรวจ key ใดก็ได้ (session แรกด้านบนทำแบบนี้ไปแล้ว):
PINGReading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”AI เขียนตัวนับ page-view แล้วอ้างเหตุผลด้วยประโยคที่ทุกคนรู้จัก:
🐛 หา bug ในโค้ดนี้
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
// Redis is single-threaded, so this read-modify-write is safe.async function recordVisit() {const n = Number((await redis.get('visits')) ?? 0);await redis.set('visits', n + 1);}
await Promise.all(Array.from({ length: 50 }, recordVisit));console.log(await redis.get('visits'));เฉลย
รันกับ container 8.10.2 บรรทัดสุดท้ายพิมพ์ 1 ไม่ใช่ 50 “Single-threaded” รับประกันแค่ว่าแต่ละคำสั่งจากทั้งสองรันโดยลำพัง; GET กับ SET คือสองรอบของ event loop และ GET อีก 49 ตัวรันหมดก่อนที่ SET ตัวแรกจะลงสำเร็จ นี่คือ lost update แบบคลาสสิก วิธีแก้คือทำให้เป็น คำสั่งเดียว:
// @runimport { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);await redis.del('b1:visits');await Promise.all(Array.from({ length: 50 }, () => redis.incr('b1:visits')));console.log(await redis.get('b1:visits')); // "50"await redis.del('b1:visits');redis.disconnect();การทดสอบเดียวกันพิมพ์ 50 คุณจะได้เจอ INCR จริงจังในบท Counters
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ลองมอง Redis เป็น พนักงานเคาน์เตอร์ที่เร็วมากคนเดียว ลูกค้า (client) เข้าคิว พนักงานทำคำขอหนึ่งให้เสร็จสมบูรณ์ก่อนหันไปคำขอถัดไป ไม่มีใครเห็นคำขอที่ทำค้างครึ่งทาง ผู้ช่วยหลังร้าน (background thread) ทำงานเบื้องหลัง เช่น ซิงก์สมุดบัญชีลงดิสก์และเก็บของออกจากชั้น แต่ไม่เคยยืนเคาน์เตอร์ พนักงานเร็วเพราะชั้นวางคือ RAM และกฎเคาน์เตอร์นี้คือเหตุผลที่แต่ละคำสั่งเป็น atomic ลูกค้าที่ต้องการให้สองคำขอนับเป็นหนึ่งเดียวต้องขอให้ชัดเจน: ใช้คำสั่งเดียวที่ทำหลายขั้น, MULTI หรือ Lua script
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ตัวเลือก | ข้อดี | ข้อเสีย |
|---|---|---|
| เก็บข้อมูลใน RAM (in-memory) | latency ต่ำกว่ามิลลิวินาที; อ่านเขียนเร็วกว่าดิสก์มาก | RAM แพงกว่าดิสก์มาก และ dataset ทั้งหมดต้องพอดีกับหน่วยความจำที่มี |
| รันคำสั่งแบบ single-threaded | ไม่มี lock contention; ทุกคำสั่ง atomic โดยโครงสร้าง; เข้าใจง่าย | คำสั่งช้าตัวเดียวทำ client ทุกคนค้าง; หนึ่ง instance รันคำสั่งบน core เดียว (ขยายด้วย sharding ไม่ใช่ thread) |
| Redis Open Source 8 (ทางเลือก RSALv2 / SSPLv1 / AGPLv3) | มี JSON, search, time series, probabilistic ในตัว | ไม่ใช่ BSD อีกต่อไป; ตรวจ licence กับฝ่ายกฎหมาย หรือพิจารณา Valkey (BSD) หากสำคัญ |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “Redis เป็น single-threaded” การรันคำสั่งใช่ แต่ process มี background thread (
ps -T) และ I/O thread ที่เลือกเปิดได้ (io-threads) ถ้าเข้าใจผิดจะเชื่อว่าคำสั่งหลายตัวต่อกันเป็น atomic หรือ Redis ใช้ได้แค่ core เดียวเท่านั้น - “Redis ก็แค่ cache” มันคือ data-structure server จะเป็น แค่ cache หรือไม่ขึ้นกับการตั้งค่า persistence และ
maxmemoryไม่ใช่ตัวซอฟต์แวร์ - “Redis เป็น open source ก็ต้องเป็น BSD” เคยเป็นจนถึง 7.2 ตอนนี้มีสามทางเลือก (RSALv2, SSPLv1, AGPLv3); Valkey คือ fork แบบ BSD
- “Redis Stack เป็นผลิตภัณฑ์แยกที่ต้องติดตั้ง” ไม่ใช่แล้วตั้งแต่ Redis 8 —
MODULE LISTบน image ธรรมดาแสดง JSON, search, time series และ Bloom โหลดมาแล้ว
Common mistakes
หัวข้อที่มีชื่อว่า “Common mistakes”- คิดว่า Redis durable โดยค่าเริ่มต้น — ค่าเริ่มต้นเอนไปทางความเร็ว ไม่ใช่ durability ถ้าต้องการให้ข้อมูลรอดหลัง restart ต้องตั้งค่า persistence (RDB/AOF) เอง
- ปล่อยให้ dataset โตโดยไม่ตั้ง
maxmemory— เพราะทุกอย่างอยู่ใน RAM dataset ที่ไม่มีขอบเขตและไม่มี eviction policy อาจกินหน่วยความจำหมดจน OOM ทั้งเครื่อง - คิดว่า single-threaded แปลว่า “ช้า” — command thread เดียวคือเหตุผลที่ Redis เร็ว เพราะไม่มี context switching แต่ก็แปลว่าคำสั่งแพงตัวเดียว (เช่น
KEYS *บน dataset ใหญ่) บล็อก client ทุกคนจนกว่าจะจบ บท First Commands วัดผลนี้ให้ดู
Real-world example
Stack Overflow ใช้ cache แบบ L1 (หน่วยความจำภายในแอป)/L2 (Redis) หน้า SQL Server: request เช็ค cache ในเครื่อง แล้วเช็ค Redis และค่อยถามฐานข้อมูลเมื่อ miss — ที่ขนาดรายงานไว้ราว 160 พันล้านการดำเนินการ Redis ต่อเดือนทั้ง fleet (Stack Overflow: The Architecture – 2016 Edition, Nick Craver, 2016 — ตัวเลขอายุสิบปี ถือเป็นประวัติศาสตร์)