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

Lists

Redis List คือคอลเลกชันสตริงที่เรียงลำดับ โดยนำไปใช้งานในรูปแบบ doubly linked list ทำให้สามารถ push และ pop ที่ปลายทั้งสองด้านได้ด้วยความซับซ้อน O(1) เหมาะสำหรับ queue, stack และ activity feed ข้อที่ต้องระวังคือการเข้าถึงองค์ประกอบตรงกลางมีความซับซ้อน O(N) ดังนั้น List จึงเหมาะที่สุดเมื่อทำงานกับปลายด้านหัวหรือท้ายเป็นหลัก

LPUSH key val [val ...] เพิ่มค่าหนึ่งค่าหรือหลายค่าไปที่ด้านหัวของ list เมื่อ push หลายค่าพร้อมกัน อาร์กิวเมนต์ตัวสุดท้ายจะอยู่ที่หัว เพราะแต่ละค่าถูก push ทีละตัวจากซ้ายไปขวา

RPUSH key val [val ...] เพิ่มค่าหนึ่งค่าหรือหลายค่าไปที่ด้านท้ายของ list

127.0.0.1:6379> RPUSH tasks "buy milk"
(integer) 1
127.0.0.1:6379> RPUSH tasks "write code" "review PR"
(integer) 3
127.0.0.1:6379> LPUSH tasks "urgent: fix bug"
(integer) 4
RPUSH tasks "buy milk"
RPUSH tasks "write code" "review PR"
LPUSH tasks "urgent: fix bug"
LRANGE tasks 0 -1
LLEN tasks
LINDEX tasks 0
LINDEX tasks -1

เมื่อมี list แล้ว คำสั่งสามตัวนี้ครอบคลุมรูปแบบการอ่านข้อมูลส่วนใหญ่:

  • LRANGE key start stop — คืนค่าองค์ประกอบตั้งแต่ index start ถึง stop (รวมปลายทั้งสอง นับจาก 0) ใช้ -1 สำหรับองค์ประกอบสุดท้าย -2 สำหรับตัวที่สองจากท้าย
  • LLEN key — คืนจำนวนองค์ประกอบใน list
  • LINDEX key index — คืนค่าองค์ประกอบเดียวที่ index ที่กำหนด index ลบนับจากท้าย
127.0.0.1:6379> LRANGE tasks 0 -1
1) "urgent: fix bug"
2) "buy milk"
3) "write code"
4) "review PR"
127.0.0.1:6379> LLEN tasks
(integer) 4
127.0.0.1:6379> LINDEX tasks 0
"urgent: fix bug"
127.0.0.1:6379> LINDEX tasks -1
"review PR"

LRANGE tasks 0 -1 คือวิธี idiomatic สำหรับดึงข้อมูลทุกองค์ประกอบใน list ส่วน LINDEX มีความซับซ้อน O(N) เพราะต้องเดินผ่าน list จากปลายที่ใกล้ที่สุด ควรใช้เฉพาะกับตำแหน่งแรกหรือท้ายไม่กี่ตำแหน่งเท่านั้น

LSET key index value เขียนทับองค์ประกอบที่ index ที่กำหนด list ต้องมีองค์ประกอบอยู่ที่ตำแหน่งนั้นแล้ว มิฉะนั้น Redis จะคืน error

LREM key count value ลบ value ที่พบใน list:

  • count > 0 — ลบได้ไม่เกิน count ครั้ง โดยสแกนจากหัวไปท้าย
  • count < 0 — ลบได้ไม่เกิน |count| ครั้ง โดยสแกนจากท้ายไปหัว
  • count = 0 — ลบ ทุก การปรากฏ
127.0.0.1:6379> LSET tasks 1 "buy oat milk"
OK
127.0.0.1:6379> LRANGE tasks 0 -1
1) "urgent: fix bug"
2) "buy oat milk"
3) "write code"
4) "review PR"
127.0.0.1:6379> RPUSH tasks "write code"
(integer) 5
127.0.0.1:6379> LREM tasks 0 "write code"
(integer) 2
RPUSH tasks "buy milk"
RPUSH tasks "write code" "review PR"
LPUSH tasks "urgent: fix bug"
LSET tasks 1 "buy oat milk"
LRANGE tasks 0 -1
RPUSH tasks "write code"
LREM tasks 0 "write code"
LRANGE tasks 0 -1

LTRIM key start stop แก้ไข list ในที่ โดยเก็บเฉพาะองค์ประกอบใน [start, stop] และทิ้งทุกอย่างที่อยู่นอกช่วงนั้น คืนค่า OK ทันที ทำให้เรียกได้อย่างปลอดภัยหลังการเขียนทุกครั้ง

127.0.0.1:6379> DEL recent
(integer) 1
127.0.0.1:6379> RPUSH recent "page-A" "page-B" "page-C" "page-D" "page-E"
(integer) 5
127.0.0.1:6379> LTRIM recent 0 2
OK
127.0.0.1:6379> LRANGE recent 0 -1
1) "page-A"
2) "page-B"
3) "page-C"
DEL recent
RPUSH recent "page-A" "page-B" "page-C" "page-D" "page-E"
LRANGE recent 0 -1
LTRIM recent 0 2
LRANGE recent 0 -1
ตัวเลือกBenefitCost
Redis List (doubly linked list)push/pop ที่ปลายทั้งสองด้าน O(1) เหมาะกับ queue/stack/feedเข้าถึงองค์ประกอบกลาง list ด้วย LINDEX เป็น O(N)
LPUSH + LTRIM สำหรับ recent-items feedเก็บ list ขนาดคงที่อัตโนมัติ ไม่ต้องเขียน cleanup job แยกต้องเรียก LTRIM ทุกครั้งหลัง push ถ้าลืมจะโตไม่มีที่สิ้นสุด
LREM ลบตาม valueลบข้อมูลที่ไม่รู้ index ล่วงหน้าได้สแกน list ทั้งเส้น O(N) ไม่เหมาะกับ list ขนาดใหญ่ที่ลบบ่อย
  • ใช้ LINDEX หรือวน LRANGE ทีละ index เพื่ออ่านข้อมูลกลาง list บ่อย ๆ — แต่ละครั้งเป็น O(N) ถ้าต้องอ่านสุ่มตำแหน่งบ่อย ควรพิจารณาโครงสร้างข้อมูลอื่นแทน
  • ใช้ LPUSH เก็บ activity feed โดยไม่เรียก LTRIM ตามหลัง — list โตไม่มีขีดจำกัดจนกิน memory ทั้งที่ต้องการแค่ N รายการล่าสุด
  • คาดหวังว่า LSET ทำงานเหมือน “insert ที่ index” — แต่จริง ๆ แล้ว LSET เขียนทับ (overwrite) องค์ประกอบที่มีอยู่แล้วเท่านั้น ถ้า index ไม่มีอยู่จริงจะได้ error

💡 ตัวอย่างจากของจริง

Twitter (X) — ใช้ Redis List คู่กับ LTRIM เก็บ timeline cache ของแต่ละ user ให้มีขนาดคงที่ ไม่ให้โตไม่มีที่สิ้นสุดเมื่อมี tweet ใหม่เข้ามาเรื่อย ๆ

Instagram — ใช้ List pattern คล้ายกันเก็บ recent activity feed ต่อ user ผสาน LPUSH สำหรับ event ใหม่และ LTRIM ตัดให้เหลือ N รายการล่าสุดเสมอ

`LRANGE tasks 0 -1` คืนค่าอะไร?
คำสั่ง push ใดที่เพิ่มองค์ประกอบไปที่ HEAD ของ list?
`LREM key 0 val` ทำอะไร?
หลังจาก `LTRIM mylist 0 2` จะเหลือองค์ประกอบกี่ตัว?