Transactions
Transaction ใน Redis คืออะไร?
หัวข้อที่มีชื่อว่า “Transaction ใน Redis คืออะไร?”Transaction ใน Redis คือลำดับคำสั่งที่ถูก จัดคิว แล้วรันทั้งหมดเป็นบล็อกเดียวโดยไม่ถูกขัดจังหวะ ไม่มีคำสั่งของ client อื่นสามารถแทรกเข้าระหว่างคำสั่งที่จัดคิวไว้เมื่อ EXEC ถูกเรียก
คุณเปิด transaction ด้วย MULTI จากนั้นออกคำสั่ง (คำสั่งถูกจัดคิว ยังไม่รันทันที) แล้วยิงทั้งหมดด้วย EXEC หากต้องการยกเลิก queue ให้ใช้ DISCARD
127.0.0.1:6379> MULTIOK127.0.0.1:6379> SET balance 100QUEUED127.0.0.1:6379> DECRBY balance 30QUEUED127.0.0.1:6379> GET balanceQUEUED127.0.0.1:6379> EXEC1) OK2) (integer) 703) "70"สังเกต: ขณะอยู่ใน MULTI ทุกคำสั่งจะตอบกลับว่า QUEUED แทนผลลัพธ์จริง ผลลัพธ์จริงจะมาเป็นรายการเมื่อ EXEC ทำงาน
MULTI
SET balance 100
DECRBY balance 30
GET balance
EXECDISCARD — ยกเลิก transaction
หัวข้อที่มีชื่อว่า “DISCARD — ยกเลิก transaction”หากเปลี่ยนใจก่อนเรียก EXEC ให้ใช้ DISCARD เพื่อล้าง queue และออกจากบล็อก transaction
127.0.0.1:6379> MULTIOK127.0.0.1:6379> SET temp "draft"QUEUED127.0.0.1:6379> DISCARDOK127.0.0.1:6379> EXISTS temp(integer) 0MULTI
SET temp "draft"
DISCARD
EXISTS tempไม่มี rollback — error ใน Redis transaction
หัวข้อที่มีชื่อว่า “ไม่มี rollback — error ใน Redis transaction”Redis transaction ไม่ rollback เมื่อเกิด command error มีสองประเภทของ error:
- Syntax/type error ตอน queue (เช่น จำนวน argument ผิด) — จะยกเลิก transaction ทั้งหมด
- Runtime error ระหว่าง EXEC (เช่น เรียก
INCRกับค่าที่เป็น string) — Redis รันคำสั่งที่เหลือต่อไป เฉพาะคำสั่งนั้นที่ผิดพลาด
127.0.0.1:6379> SET mystr "hello"OK127.0.0.1:6379> MULTIOK127.0.0.1:6379> SET k1 "a"QUEUED127.0.0.1:6379> INCR mystrQUEUED127.0.0.1:6379> SET k2 "b"QUEUED127.0.0.1:6379> EXEC1) OK2) (error) ERR value is not an integer or out of range3) OKk1 และ k2 ถูก set สำเร็จแม้ว่า INCR mystr จะผิดพลาด Redis ไม่ rollback คำสั่งที่สำเร็จแล้ว
Optimistic locking ด้วย WATCH
หัวข้อที่มีชื่อว่า “Optimistic locking ด้วย WATCH”WATCH ให้คุณทำ check-and-set (CAS): ดู key หนึ่งหรือหลาย key ก่อนเริ่ม transaction หาก key ที่ดูอยู่ถูก client อื่นแก้ไขก่อนที่ EXEC จะถูกเรียก transaction ทั้งหมดจะถูกยกเลิก และ EXEC จะคืน (nil) แทนรายการผลลัพธ์ โค้ดของคุณสามารถลองใหม่ได้
127.0.0.1:6379> SET stock 5OK127.0.0.1:6379> WATCH stockOK127.0.0.1:6379> MULTIOK127.0.0.1:6379> DECRBY stock 1QUEUED127.0.0.1:6379> EXEC1) (integer) 4หาก client อื่นเปลี่ยน stock ระหว่าง WATCH กับ EXEC การเรียก EXEC จะคืน (nil) และคุณต้องลองทั้งลำดับจาก WATCH ใหม่
UNWATCH ยกเลิก watch ทั้งหมดโดยไม่ยกเลิก connection
SET stock 5
WATCH stock
MULTI
DECRBY stock 1
EXEC
GET stockข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
MULTI/EXEC transaction | รับประกันว่าคำสั่งที่ queue ไว้จะรันต่อเนื่องโดยไม่ถูกขัดจังหวะ | ไม่มี rollback — runtime error ในคำสั่งหนึ่งไม่ยกเลิกคำสั่งอื่นที่สำเร็จไปแล้ว |
WATCH สำหรับ optimistic locking | ตรวจจับ race condition ได้โดยไม่ต้อง lock resource จริง เหมาะกับ contention ต่ำ | ต้องเขียน retry loop เอง และภายใต้ contention สูงอาจ retry ซ้ำหลายรอบ |
| Lua script แทน transaction เมื่อต้อง branching | ได้ conditional logic แบบ atomic ที่ transaction ทำไม่ได้ | ซับซ้อนกว่า MULTI/EXEC และต้องเขียน Lua แยกต่างหาก |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- คาดหวังว่า
MULTI/EXECจะ rollback เหมือน SQL transaction — Redis transaction ไม่มี rollback runtime error ในคำสั่งหนึ่งจะไม่ยกเลิกคำสั่งอื่นที่สำเร็จไปแล้วในEXECเดียวกัน ต้องตรวจสอบผลลัพธ์แต่ละคำสั่งเอง - ลืมใช้
WATCHเมื่อทำ check-then-set ที่มี race condition — ถ้าอ่านค่าก่อนแล้วMULTI/EXECเขียนทีหลังโดยไม่WATCHkey ที่อ่าน client อื่นอาจแก้ไขค่าระหว่างนั้นได้โดยไม่มีการตรวจจับ - ไม่มี retry logic เมื่อ
EXECคืน(nil)— เมื่อ key ที่WATCHไว้ถูกแก้ไขก่อนEXECtransaction จะถูกยกเลิกและคืน(nil)โค้ดฝั่ง client ต้อง retry ทั้งลำดับตั้งแต่WATCHใหม่ ไม่ใช่ปล่อยผ่านเงียบ ๆ
💡 ตัวอย่างจากของจริง
ระบบ inventory/stock แบบง่าย — ใช้
WATCHบน key จำนวนสินค้าคงเหลือ ก่อนลดจำนวนด้วยMULTI/EXECเพื่อป้องกันการขายเกินสต็อกเมื่อมีคำสั่งซื้อพร้อมกันหลายคำสั่งSidekiq และ background job framework อื่น ๆ — ใช้ pattern คล้าย
WATCH+MULTI/EXECเพื่อทำ optimistic concurrency control เมื่อ worker หลายตัวแย่งกันหยิบงานจาก queue เดียวกัน