ข้ามไปยังเนื้อหา

Redis คืออะไร?

ทดสอบบน Redis 8.10.2 (redis:8.10.2-alpine), ioredis 6.0.0, Node 24 บน Mac (Apple silicon) — output ทุกบล็อกด้านล่างคัดลอกมาจากการรันจริง

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

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 *:6379

Thread 1 คือตัวที่รันคำสั่งของคุณ ส่วน thread bio_* ทำงานเบื้องหลังที่ต้องไม่บล็อก thread หลัก (ปิดไฟล์, fsync ของ AOF, คืนหน่วยความจำของ value ใหญ่แบบ lazy — ดู UNLINK ในบทหลัง) และ jemalloc_bg_thd คืองานดูแลของ allocator ส่วนฝั่ง I/O ดูได้ด้วย INFO threads และปรับด้วย io-threads:

cli/intro-what-is-redis-1.redis
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> CONFIG GET io-threads
1) "io-threads"
2) "1"
127.0.0.1:6379> SET language "Redis"
OK
127.0.0.1:6379> TYPE language
string

เมื่อสตาร์ทเซิร์ฟเวอร์ทิ้งอีกตัวด้วย 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 อื่นแทรกได้ระหว่างนั้น (จะพิสูจน์ด้านล่าง)
 
ใครรันอะไร: command thread เดียว แยกจาก I/O และ background thread

ประโยคที่ว่า “Redis คือ cache” เป็นเรื่องทั่วไปแต่ไม่ครบถ้วน cache ธรรมดา map key ไปยัง blob ที่ไม่รู้โครงสร้าง Redis map key ไปยัง โครงสร้างข้อมูลที่หลากหลาย ที่คุณสามารถจัดการได้ฝั่งเซิร์ฟเวอร์:

ประเภทคำอธิบาย
Stringข้อความ, ตัวเลข, หรือ raw bytes ได้ถึง 512 MB
Listลำดับที่เรียงไว้; push/pop จากทั้งสองด้าน
Hashfield-value map ภายใน key เดียว
Setคอลเลกชันของ member ที่ไม่ซ้ำกันแบบไม่มีลำดับ
Sorted SetSet ที่แต่ละ member มี numeric score
Streamบันทึกแบบ append-only ของ entry ที่มี timestamp

เพราะการทำงานเกิดขึ้นฝั่งเซิร์ฟเวอร์ คุณจึงลด round trip ได้ แทนที่จะ GET → แก้ในแอป → SET คุณใช้ LPUSH หรือ ZINCRBY ในการเรียกเครือข่ายครั้งเดียว

หลายปีที่ความสามารถเสริมอยู่ใน module ที่ต้องติดตั้งแยก และ “Redis Stack” คือชุดที่รวมทั้งหมดมาให้ Redis 8 รวมสองอย่างเข้าด้วยกัน: สิ่งที่เคยเรียก Redis Community Edition ตอนนี้ชื่อ Redis Open Source และฟีเจอร์ที่เคยเป็น module ถูกฝังในเซิร์ฟเวอร์เลย ลองถามเซิร์ฟเวอร์จริงว่าโหลดอะไรไว้ (ตัดให้เหลือชื่อกับ version):

Terminal window
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 ที่ฝังมาใช้ได้ทันที ไม่ต้องติดตั้ง:

cli/intro-what-is-redis-2.redis
127.0.0.1:6379> JSON.SET user:1 $ '{"name":"Ada"}'
OK
127.0.0.1:6379> JSON.GET user:1
"{\"name\":\"Ada\"}"
127.0.0.1:6379> TYPE user:1
ReJSON-RL
127.0.0.1:6379> DEL user:1
(integer) 1
ฝังมาให้ตั้งแต่ Redis 8ได้อะไร
JSONเก็บและแก้ JSON document ในที่เดิมด้วย JSONPath
Search and querySecondary index, full-text search และ vector search บน hash หรือ JSON
Time seriesDownsampling, aggregation และ retention สำหรับ sample ที่มี timestamp
ProbabilisticBloom 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

เซิร์ฟเวอร์ที่คุณรันมีผลทั้งด้านกฎหมายและความเข้ากันได้ จึงควรรู้ไทม์ไลน์:

วันที่เหตุการณ์แหล่งที่มา
จนถึง Redis 7.2BSD-3-Clause open source—
2024-03-28Redis เปลี่ยนเป็น dual source-available RSALv2 / SSPLv1redis.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 8redis.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ฟีเจอร์ของ Redis
CacheTTL-based expiry บน key ใดก็ได้
Session storeHash ต่อ session ID อ่านเร็ว
Task queueLPUSH / BRPOP บน List
LeaderboardSorted Set กับ ZADD / ZRANK
Rate limiterINCR + EXPIRE บน counter key
Pub/Sub messagingช่อง PUBLISH / SUBSCRIBE

รัน PING เพื่อยืนยันว่าเซิร์ฟเวอร์ยังอยู่ แล้วใช้ TYPE ตรวจ key ใดก็ได้ (session แรกด้านบนทำแบบนี้ไปแล้ว):

PING

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 แบบคลาสสิก วิธีแก้คือทำให้เป็น คำสั่งเดียว:

src/intro-what-is-redis-visits.ts
// @run
import { 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

ลองมอง Redis เป็น พนักงานเคาน์เตอร์ที่เร็วมากคนเดียว ลูกค้า (client) เข้าคิว พนักงานทำคำขอหนึ่งให้เสร็จสมบูรณ์ก่อนหันไปคำขอถัดไป ไม่มีใครเห็นคำขอที่ทำค้างครึ่งทาง ผู้ช่วยหลังร้าน (background thread) ทำงานเบื้องหลัง เช่น ซิงก์สมุดบัญชีลงดิสก์และเก็บของออกจากชั้น แต่ไม่เคยยืนเคาน์เตอร์ พนักงานเร็วเพราะชั้นวางคือ RAM และกฎเคาน์เตอร์นี้คือเหตุผลที่แต่ละคำสั่งเป็น atomic ลูกค้าที่ต้องการให้สองคำขอนับเป็นหนึ่งเดียวต้องขอให้ชัดเจน: ใช้คำสั่งเดียวที่ทำหลายขั้น, MULTI หรือ Lua script

ตัวเลือกข้อดีข้อเสีย
เก็บข้อมูลใน 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) หากสำคัญ
  • “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 โหลดมาแล้ว
  • คิดว่า 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 — ตัวเลขอายุสิบปี ถือเป็นประวัติศาสตร์)

ทำไม Redis จึงเร็วกว่าฐานข้อมูลที่ใช้ดิสก์มาก?
'Redis เป็น single-threaded' รับประกันอะไรจริง ๆ?
worker 50 ตัวพร้อมกันรัน GET visits แล้ว SET visits (n+1) ค่าสุดท้ายคือ 1 เพราะอะไร?
จะใช้ data type ไหนทำ leaderboard ที่จัดอันดับตามคะแนน?
บน image redis:8.10.2 ธรรมดา MODULE LIST แสดงอะไร?
Redis 8 (2025-05-01) เพิ่ม licence ทางเลือกใด?