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

Sorted Sets

Sorted Set เหมือน Set แต่สมาชิกแต่ละตัวมี floating-point score Redis รักษาการเรียงลำดับตามคะแนนตลอดเวลาด้วย O(log N) สำหรับการ insert และ range query ถ้าสมาชิกสองตัวมีคะแนนเท่ากัน จะเรียงตาม lexicographic order

ZADD key score member [score member ...] เพิ่มสมาชิก ถ้าสมาชิกมีอยู่แล้วคะแนนจะ ถูกอัปเดต คืนจำนวน สมาชิกใหม่ ที่เพิ่ม

127.0.0.1:6379> ZADD game:scores 4200 "alice" 3800 "bob" 5100 "carol"
(integer) 3
127.0.0.1:6379> ZADD game:scores 4700 "alice"
(integer) 0
127.0.0.1:6379> ZSCORE game:scores "alice"
"4700"
ZADD game:scores 4200 "alice" 3800 "bob" 5100 "carol"
ZADD game:scores 4700 "alice"
ZSCORE game:scores "alice"

ZRANGE key start stop [WITHSCORES] คืนสมาชิกตามลำดับคะแนนจากน้อยไปมาก ZREVRANGE คืนตามลำดับจากมากไปน้อย Index เป็น 0-based; -1 หมายถึงสมาชิกสุดท้าย

127.0.0.1:6379> ZADD leaderboard 900 "carol" 1500 "alice" 2100 "bob" 750 "dave"
(integer) 4
127.0.0.1:6379> ZRANGE leaderboard 0 -1 WITHSCORES
1) "dave"
2) "750"
3) "carol"
4) "900"
5) "alice"
6) "1500"
7) "bob"
8) "2100"
127.0.0.1:6379> ZREVRANGE leaderboard 0 2 WITHSCORES
1) "bob"
2) "2100"
3) "alice"
4) "1500"
5) "carol"
6) "900"
ZADD leaderboard 900 "carol" 1500 "alice" 2100 "bob" 750 "dave"
ZRANGE leaderboard 0 -1 WITHSCORES
ZREVRANGE leaderboard 0 2 WITHSCORES

ZRANK key member คืน rank แบบ 0-based ตามลำดับจากน้อยไปมาก ZREVRANK ให้ rank ตามลำดับจากมากไปน้อย ZSCORE คืนคะแนนเป็น bulk string

127.0.0.1:6379> ZADD players 300 "x" 600 "y" 900 "z"
(integer) 3
127.0.0.1:6379> ZRANK players "y"
(integer) 1
127.0.0.1:6379> ZREVRANK players "y"
(integer) 1
127.0.0.1:6379> ZSCORE players "z"
"900"
ZADD players 300 "x" 600 "y" 900 "z"
ZRANK players "y"
ZREVRANK players "y"
ZSCORE players "z"

ZRANGEBYSCORE key min max [WITHSCORES] ดึงสมาชิกทั้งหมดที่มีคะแนนระหว่าง min และ max (inclusive) ใช้ -inf และ +inf สำหรับขอบเขตเปิด

127.0.0.1:6379> ZADD temps 18.5 "london" 32.1 "bangkok" 24.0 "berlin" 28.7 "tokyo"
(integer) 4
127.0.0.1:6379> ZRANGEBYSCORE temps 20 30 WITHSCORES
1) "berlin"
2) "24"
3) "tokyo"
4) "28.7"
ZADD temps 18.5 "london" 32.1 "bangkok" 24.0 "berlin" 28.7 "tokyo"
ZRANGEBYSCORE temps 20 30 WITHSCORES

ZINCRBY key increment member บวก increment เข้ากับคะแนนของสมาชิกแบบ atomic ZCARD key คืนจำนวนสมาชิกทั้งหมด ZREM key member [member ...] ลบสมาชิกหนึ่งตัวหรือมากกว่า

127.0.0.1:6379> ZADD votes 10 "post:1" 5 "post:2" 8 "post:3"
(integer) 3
127.0.0.1:6379> ZINCRBY votes 3 "post:2"
"8"
127.0.0.1:6379> ZCARD votes
(integer) 3
127.0.0.1:6379> ZREM votes "post:3"
(integer) 1
127.0.0.1:6379> ZCARD votes
(integer) 2
ZADD votes 10 "post:1" 5 "post:2" 8 "post:3"
ZINCRBY votes 3 "post:2"
ZCARD votes
ZREM votes "post:3"
ZCARD votes
ตัวเลือกBenefitCost
Sorted Set แทน SQL table + ORDER BYinsert และ range read O(log N) เสมอ ไม่ต้องพึ่ง index ที่ต้อง maintain แยกคะแนนเป็น float64 เดี่ยวเท่านั้น sort ตามหลายคอลัมน์แบบ relational ทำไม่ได้ตรงๆ
ZRANGEBYSCORE สำหรับ range queryดึงข้อมูลตามช่วงคะแนนได้ตรงๆ โดยไม่ต้อง scan ทั้งหมดต้องออกแบบ score ล่วงหน้าให้ครอบคลุม use case การ query ทั้งหมด เปลี่ยนทีหลังมีค่าใช้จ่ายในการ re-score
tie-break ด้วย lexicographic order เมื่อคะแนนเท่ากันพฤติกรรม deterministic ไม่ต้องเขียน logic เพิ่มเมื่อคะแนนชนกันถ้าไม่ได้ตั้งใจออกแบบ member name ให้เรียงมีความหมาย ผลลัพธ์ tie อาจดูสุ่มสำหรับผู้ใช้
  • คิดว่า ZADD กับสมาชิกที่มีอยู่แล้วจะสร้าง duplicate — Sorted Set ไม่มีสมาชิกซ้ำ การ ZADD สมาชิกเดิมจะแค่อัปเดตคะแนนและคืนค่า 0 ไม่ใช่เพิ่มรายการใหม่
  • ใช้ ZRANGE เมื่อต้องการลำดับจากมากไปน้อยZRANGE เรียงจากน้อยไปมากเสมอ ถ้าต้องการ “คะแนนสูงสุดก่อน” ต้องใช้ ZREVRANGE ไม่เช่นนั้นผลลัพธ์จะกลับด้าน
  • ลืมตั้ง -inf/+inf เมื่อต้องการ range แบบเปิดขอบ — การเขียนตัวเลขจำกัดตายตัวใน ZRANGEBYSCORE แทนที่จะใช้ -inf/+inf ทำให้ query พลาดสมาชิกที่มีคะแนนต่ำ/สูงกว่าที่คาดไว้

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

Leaderboard และระบบจัดอันดับเกม — ผูก score เข้ากับผู้เล่นโดยตรงเพื่อให้ query “top N” และ “อันดับของฉัน” ทำงานแบบ real-time โดยไม่ต้อง batch คำนวณใหม่

Priority queue และ scheduled job — หลายระบบใช้ Sorted Set ผูก timestamp เป็นคะแนน เพื่อ query “งานที่ถึงกำหนดแล้ว” ด้วย ZRANGEBYSCORE แทนการ poll ฐานข้อมูลเชิงสัมพันธ์ซ้ำๆ

ZREVRANGE คืนอะไรเมื่อเทียบกับ ZRANGE?
ZRANK คืนอะไรสำหรับสมาชิก?
จะเกิดอะไรขึ้นเมื่อ ZADD คะแนนให้สมาชิกที่มีอยู่แล้ว?
คำสั่งใดบวกค่าเข้ากับคะแนนของสมาชิกแบบ atomic?