بعضی وقتا زمانی که در یک جای تاریک هستید فکر میکنید دفن شدهاید اما در واقع شما کاشته شدهاید
طراحی لیدربورد و سیستمهای رتبهبندی با Redis Sorted Set
برای اینکه Sorted Set را درست بفهمیم، لازم است خیلی خلاصه بدانیم Set و Hash در Redis چه کاری میکنند؛ چون ZSET عملاً از دل همین دو مفهوم بیرون آمده.

Redis Set چیست؟
Set در Redis یک مجموعه از رشتههای یکتاست.
هر عضو فقط یکبار میتواند وجود داشته باشد
ترتیب ندارد
فقط میتوانی بپرسی «هست یا نیست؟»
Set برای سناریوهایی خوب است که:
تکراری بودن مهم نیست
فقط عضویت مهم است، نه مقدار یا رتبه
مثلاً:
لیست کاربران آنلاین
لیست آیدیهایی که قبلاً کاری را انجام دادهاند
اما همینجا محدودیتش معلوم میشود:
نه ترتیبی دارد، نه عددی، نه مفهومی از رتبه.
مثال Set: فقط «هست یا نیست»
فرض کن میخوای بفهمی کدوم کاربرها الان آنلایناند.
ذهنی که پشت داده است
ما فقط اینو میخوای:
user_42 آنلاین هست یا نه؟
user_99 آنلاین هست یا نه؟
هیچ امتیاز، ترتیب یا اطلاعات اضافهای مهم نیست.
داده در Redis چطور ذخیره میشود
SADD online_users user_42
SADD online_users user_99
SADD online_users user_7تو چطور میبینیش
SMEMBERS online_usersخروجی:
user_7
user_42
user_99نکته مهم:
ترتیب هیچ معنایی ندارد. Redis عمداً ترتیب را به تو تضمین نمیدهد.
اگر بپرسی:
SISMEMBER online_users user_42خیلی سریع جواب میگیری: بله یا خیر.
Redis Hash چیست؟
Hash شبیه یک دیکشنری یا آبجکت است:
یک کلید اصلی داری
داخلش مجموعهای از field → value نگه میداری
Hash برای این عالی است که:
اطلاعات مرتبط یک موجودیت را کنار هم نگه داری
به هر فیلد جداگانه دسترسی داشته باشی
مثلاً:
پروفایل کاربر (name، level، xp، status)
تنظیمات یک بازی یا سرویس
اما Hash هم یک ضعف جدی دارد:
هیچ مفهومی از مرتبسازی بین چند عضو مختلف ندارد.
مثال Hash: یک موجودیت با چند ویژگی
حالا فرض کن میخوای پروفایل یک کاربر را ذخیره کنی.
ذهنی که پشت داده است
ما یک کاربر داریم با چند ویژگی:
نام
لول
امتیاز کلی
همه اینها به یک موجودیت تعلق دارند.
داده در Redis چطور ذخیره میشود
HSET user:42 name "Ali" level 7 totalScore 1850تو چطور میبینیش
HGETALL user:42خروجی:
name
Ali
level
7
totalScore
1850یا اگر فقط یکی از فیلدها مهم باشد:
HGET user:42 levelاینجا Hash عالی است،
اما هیچ راهی نداری بگویی:
«کدام کاربر امتیازش بالاتر است؟»
حالا چرا Sorted Set بهوجود آمده؟
اینجا Redis میگوید:
از Set، یکتایی اعضا را بگیر
از Hash، نگاشت عضو به مقدار را بگیر
و یک چیز اضافه کن که هیچکدام ندارند: ترتیب دائمی
نتیجه میشود Sorted Set:
هر عضو یکتا است (مثل Set)
هر عضو یک عدد دارد (مثل Hash)
و کل مجموعه همیشه مرتب است
نکتهای که معمولاً نادیده گرفته میشود
اگر جایی داری:
Set + سورت در کد
یا Hash + مرتبسازی دستی
در واقع داری کاری میکنی که Redis از قبل برایش ساخته شده، ولی استفادهاش نمیکنی.
این نه بهینه است، نه مقیاسپذیر، و نه تمیز.
مثال Sorted Set: رتبهبندی واقعی
حالا میرسیم به لیدربورد.
ذهنی که پشت داده است
ما میخواهیم:
هر کاربر یک امتیاز داشته باشد
بتوانیم بفهمیم چه کسی بالاتر است
سریع ۱۰ نفر اول را ببینیم
داده در Redis چطور ذخیره میشود
ZADD leaderboard 1850 user_42
ZADD leaderboard 2100 user_7
ZADD leaderboard 1600 user_99اینجا:
score = امتیاز
member = شناسهی کاربر
تو چطور میبینیش
۱۰ نفر اول:
ZREVRANGE leaderboard 0 9 WITHSCORESخروجی:
user_7
2100
user_42
1850
user_99
1600Redis بدون اینکه ازش بخواهی سورت کند،
از اول داده را مرتب نگه داشته.
مثال امتیاز مساوی (جایی که خیلیها اشتباه میکنند)
فرض کن دو نفر امتیاز برابر دارند:
ZADD leaderboard 2000 user_20
ZADD leaderboard 2000 user_3Redis چه کار میکند؟
چون score برابر است، میرود سراغ اسمها.
از نظر لغوی:
user_20 > user_3پس ترتیب همیشه ثابت است و لیدربورد «به هم نمیریزد».
تعریف ساده
یک Redis Sorted Set مجموعهای از رشتههای یکتا (members) است که هر کدام یک امتیاز عددی (score) دارند و همیشه بهصورت مرتبشده نگهداری میشوند.
نکتهی کلیدی اینجاست:
مرتببودن، ویژگی ذاتی داده است، نه نتیجهی کوئری.
یعنی Redis از لحظهی ذخیرهسازی، ترتیب را حفظ میکند؛ نه اینکه هر بار موقع خواندن، سورت کند.
Sorted Set دقیقاً چه مشکلی را حل میکند؟
1. لیدربورد (Leaderboard)
واضحترین و مهمترین کاربرد.
امتیاز هر کاربر = score
شناسهی کاربر = member
گرفتن ۱۰ نفر اول؟ → O(log N)
آپدیت امتیاز؟ → O(log N)
برای یک بازی آنلاین با میلیونها کاربر، این یعنی نجات پروژه.
2. Rate Limiting (محدودسازی درخواست)
با ZSET میتوانی Sliding Window Rate Limiter بسازی:
هر درخواست = یک timestamp بهعنوان score
حذف درخواستهای قدیمی
شمردن درخواستهای بازهی زمانی
بدون نیاز به دیتابیس سنگین یا لاک پیچیده.
Sorted Set ترکیب کدام ساختارهاست؟
میتونی Sorted Set رو اینطوری تصور کنی:
از Set:
اعضا یکتا هستند
هیچ عضوی تکراری نیست
از Hash:
هر عضو به یک مقدار (score) مپ شده
اما چیزی که هیچکدام ندارند:
مرتبسازی دائمی و ذاتی
قانون مرتبسازی در Sorted Set
Redis برای مرتبسازی دو قانون خیلی شفاف دارد:
قانون اول: امتیاز (Score)
اگر دو عضو score متفاوت داشته باشند:
A > B اگر A.score > B.scoreیعنی امتیاز بالاتر = رتبه بالاتر.
قانون دوم: ترتیب لغوی (Lexicographical)
اگر دو عضو امتیاز یکسان داشته باشند:
A > B اگر A.member از نظر لغوی بزرگتر از B.member باشدمثلاً:
score = 100
"user_20" > "user_3"نکته مهم:
چون اعضا یکتا هستند، دو رشتهی کاملاً یکسان وجود ندارد
این قانون باعث میشود ترتیب همیشه deterministic باشد
چرا این جزئیات مهماند؟
چون در سیستم واقعی:
امتیازها مساوی میشوند
هزاران کاربر score برابر دارند
اگر ترتیب مشخص نباشد، UI شما «به هم میریزه»
کاربر حس بیعدالتی میگیرد
Redis این مشکل را در سطح ساختار دیتا حل کرده، نه در کد شما.
یک مثال ساده: امتیازدهی به رانندهها با Redis Sorted Set
بیایم همهچیز رو با یک مثال خیلی ساده شروع کنیم:
فرض کن چند راننده داریم و هرکدوم توی اولین مسابقه یک امتیاز گرفتهاند.
هدف ما اینه که این امتیازها رو ذخیره کنیم و هر لحظه بتونیم رتبهبندیشون رو ببینیم.
اضافهکردن دادهها به Sorted Set
برای این کار از دستور ZADD استفاده میکنیم.
ZADD racer_scores 10 "Norem"
ZADD racer_scores 12 "Castilla"
ZADD racer_scores 8 "Sam-Bodden" 10 "Royce" 6 "Ford" 14 "Prickett"چند نکتهی خیلی مهم همینجا وجود دارد که معمولاً نادیده گرفته میشود:
ZADDشبیهSADDاست، اما قبل از هر عضو یک score میگیرداین دستور variadic است؛ یعنی میتوانی چند score–member را یکجا اضافه کنی
مقدار برگشتی نشان میدهد چند عضو جدید اضافه شدهاند (نه اینکه چند تا دستور اجرا شده)
از همین لحظه، Redis دادهها را مرتبشده نگه میدارد.
دیدن لیست مرتبشدهی رانندهها
حالا بدون هیچ سورت اضافهای، میتوانیم لیست را بگیریم.
ZRANGE racer_scores 0 -1خروجی:
Ford
Sam-Bodden
Norem
Royce
Castilla
Prickettاین خروجی از کمترین امتیاز به بیشترین است.
چرا؟ چون ZRANGE همیشه از پایین به بالا میآید.
اگر برعکسش را بخواهی:
ZREVRANGE racer_scores 0 -1خروجی:
Prickett
Castilla
Royce
Norem
Sam-Bodden
Fordاینجا بالاترین امتیاز اول میآید، دقیقاً چیزی که برای لیدربورد لازم داریم.
نکتهی ظریف:0 یعنی اولین عنصر-1 یعنی آخرین عنصر
دقیقاً مثل LRANGE در لیستها.
دیدن امتیازها همراه با اسمها
تا اینجا فقط اسمها را دیدیم. اگر امتیاز هم بخواهیم:
ZRANGE racer_scores 0 -1 WITHSCORESخروجی بهصورت جفتهای پشتسرهم برمیگردد:
Ford 6
Sam-Bodden 8
Norem 10
Royce 10
Castilla 12
Prickett 14این شکل خروجی شاید اولش عجیب باشد،
ولی برای پردازش ماشینمحور عالی است و سریع.
کار روی بازهها (اینجا قدرت ZSET معلوم میشود)
فرض کن فقط رانندههایی را میخواهیم که ۱۰ امتیاز یا کمتر گرفتهاند.
ZRANGEBYSCORE racer_scores -inf 10خروجی:
Ford
Sam-Bodden
Norem
Royceاینجا داریم به Redis میگوییم:
از منفی بینهایت
تا ۱۰
هر چی توی این بازه است را بده
این یعنی فیلتر + مرتبسازی با هم، آن هم با پیچیدگی مناسب.
حذف دادهها (تکی و گروهی)
حذف یک رانندهی خاص خیلی ساده است:
ZREM racer_scores "Castilla"اما قدرت واقعی اینجاست که بتوانی یک بازه را پاک کنی.
مثلاً:
همهی کسانی که امتیازشان کمتر از ۱۰ است را حذف کن
ZREMRANGEBYSCORE racer_scores -inf 9Redis تعداد آیتمهای حذفشده را برمیگرداند،
که برای مانیتورینگ خیلی مهم است.
بعد از حذف، اگر دوباره لیست را ببینیم:
ZRANGE racer_scores 0 -1خروجی:
Norem
Royce
Prickettگرفتن رتبهی یک عضو
یکی از مهمترین قابلیتهای Sorted Set این است که بپرسی:
«این شخص چندمه؟»
ZRANK racer_scores "Norem"خروجی:
0یعنی از پایین، اولین نفر.
اگر رتبه از بالا را بخواهی (حالت لیدربورد واقعی):
ZREVRANK racer_scores "Norem"خروجی:
2یعنی نفر سوم از بالا.
این دستور برای نمایش رتبهی کاربر بدون گرفتن کل لیدربورد حیاتی است.
مرتبسازی لغوی (Lexicographical) — وقتی همه امتیاز برابرند
از Redis 2.8 به بعد، اگر همهی اعضا score یکسان داشته باشند،
میتوانی از Sorted Set بهعنوان ایندکس لغوی استفاده کنی.
بیایم همه رانندهها را با score صفر اضافه کنیم:
ZADD racer_scores 0 "Norem" 0 "Sam-Bodden" 0 "Royce" 0 "Castilla" 0 "Prickett" 0 "Ford"حالا اگر لیست را بگیریم:
ZRANGE racer_scores 0 -1خروجی:
Castilla
Ford
Norem
Prickett
Royce
Sam-Boddenکاملاً لغوی مرتب شدهاند.
گرفتن بازهی لغوی
مثلاً همهی اسمهایی که بین A و L هستند:
ZRANGEBYLEX racer_scores [A [Lخروجی:
Castilla
Ford
براکت [ یعنی شامل (inclusive)
و میتوانی بازههای باز یا بینهایت هم تعریف کنی.
چرا این قابلیت خیلی مهم است؟
چون Sorted Set فقط برای لیدربورد نیست.
با این تکنیک میتوانی:
ایندکس بسازی
روی بازههای عددی بزرگ (مثلاً 128 بیت) جستجو کنی
بدون دیتابیس سنگین، range query واقعی داشته باشی
Redis این کار را با یک ساختار دوگانه انجام میدهد:
Skip List برای مرتبسازی
Hash Table برای دسترسی سریع
به همین دلیل:
اضافهکردن عضو:
O(log N)خواندن دادهی مرتب: تقریباً بدون هزینه
آپدیت امتیازها: چرا Sorted Set برای لیدربورد ایدهآل است؟
قبل از رفتن به مبحث بعدی، یک نکتهی بسیار مهم دربارهی Sorted Set وجود دارد که اگر درک نشود، کل ارزش آن از دست میرود:
امتیاز (score) اعضای Sorted Set هر لحظه قابل تغییر است.
و مهمتر از آن:
این تغییر با پیچیدگی
O(log N)انجام میشود.
یعنی حتی اگر میلیونها عضو داشته باشی،
آپدیت امتیاز هنوز هم قابلاعتماد و سریع است.
به همین دلیل است که Sorted Set انتخاب پیشفرض برای leaderboard محسوب میشود.
لیدربورد در دنیای واقعی یعنی چه؟
تصور کن یک بازی آنلاین داری (مثلاً بازیهای اجتماعی شبیه بازیهای فیسبوکی):
میخواهی همیشه بتوانی Top-N را نشان بدهی
میخواهی به هر کاربر بگویی:
«رتبهی تو الان 4932 است»امتیازها مدام تغییر میکنند
تعداد آپدیتها خیلی زیاد است
اگر این را با دیتابیس رابطهای یا سورت در کد انجام بدهی،
سیستم خیلی زود تحت فشار قرار میگیره.
Sorted Set دقیقاً برای همین سناریو طراحی شده.
دو مدل آپدیت امتیاز در لیدربورد
در عمل، دو حالت داریم:
حالت اول: امتیاز جدید را دقیق میدانیم
مثلاً بازی تمام شده و امتیاز نهایی مشخص است.
در این حالت، ZADD کافی است.
ZADD racer_scores 100 "Wood"
ZADD racer_scores 100 "Henshaw"اگر همان عضو دوباره اضافه شود:
ZADD racer_scores 150 "Henshaw"اینجا Redis:
عضو جدید اضافه نمیکند
فقط امتیاز را جایگزین میکند
و جایگاه عضو در لیدربورد را بهروزرسانی میکند
نکتهی ظریف:
مقدار برگشتی
0استیعنی عضو از قبل وجود داشته
حالت دوم: میخواهیم امتیاز را افزایش بدهیم
در خیلی از بازیها، امتیاز تجمعی است:
هر برد → +50
هر مأموریت → +20
اینجا استفاده از ZINCRBY منطقیتر است.
ZINCRBY racer_scores 50 "Wood"خروجی:
"150"Redis امتیاز جدید را برمیگرداند.
حالا اگر همین کار را برای Henshaw انجام بدهیم:
ZINCRBY racer_scores 50 "Henshaw"خروجی:
"200"اینجا اتفاقات مهمی افتاده:
امتیاز قبلی مهم نیست
Redis خودش جمع میزند
رتبه بلافاصله اصلاح میشود
بدون race condition، بدون lock، بدون دردسر.
تفاوت رفتاری ZADD و ZINCRBY (خیلی مهم)
ZADDامتیاز را جایگزین میکند
وقتی امتیاز نهایی را میدانی
ZINCRBYامتیاز را افزایش یا کاهش میدهد
وقتی امتیاز مرحلهای یا تجمعی است
اگر این دو را اشتباه استفاده کنی،
لیدربوردت بهمرور غلط و غیرقابل اعتماد میشود.
دستورات پایهای که لیدربورد روی آنها میچرخد
بدون وارد شدن به لیست بلندبالا، لیدربورد واقعی معمولاً فقط به اینها نیاز دارد:
ZADD
اضافهکردن یا آپدیت امتیازZRANGE
گرفتن لیست مرتبشده (از پایین به بالا)ZRANK
گرفتن رتبهی کاربر از پایینZREVRANK
گرفتن رتبهی کاربر از بالا (حالت لیدربورد)
با همین چهار دستور میتوان یک لیدربورد کامل ساخت.
نکتهی عملکردی که نباید نادیده بگیری
بیشتر عملیاتهای Sorted Set:
O(log N)هستندو برای آپدیتهای پرتعداد عالیاند
اما یک هشدار جدی وجود دارد:
اگر ZRANGE را با خروجی خیلی بزرگ صدا بزنی
(مثلاً دهها هزار یا صدها هزار عضو)،
هزینهاش دیگر فقط لگاریتمی نیست.
در این حالت:
هزینه میشود
O(log N + M)که
Mتعداد آیتمهای برگشتی است
نتیجهی عملی:
لیدربورد را صفحهبندیشده بگیر، نه یکجا.
اگر Sorted Set تنها کافی نبود چه؟
گاهی Sorted Set فقط نقش ایندکس را دارد،
و دادهی اصلی جای دیگری است.
در این سناریوها:
میتوانی ZSET را برای رتبهبندی نگه داری
و دیتای واقعی را در ساختارهای دیگر ذخیره کنی
یا سراغ امکانات Query و JSON در Redis بروی
اما این تصمیم، بعد از درک درست Sorted Set گرفته میشود، نه قبلش.
سخن پایانی
با رشد سیستمهای بلادرنگ، حجم بالای داده و نیاز به پاسخدهی سریع در محصولات دیجیتال، انتخاب درست ساختار داده و الگوی معماری نقش مستقیمی در مقیاسپذیری و پایداری سامانهها دارد. Redis و بهویژه Sorted Set نمونهای از ابزارهایی هستند که اگر درست درک و استفاده شوند، میتوانند بخش مهمی از پیچیدگی سیستمهایی مثل لیدربورد، رتبهبندی و محدودسازی درخواستها را بهصورت ذاتی حل کنند.
اگر در حال طراحی یا بازطراحی سامانههایی با بار بالا، نیاز به رتبهبندی، یا منطقهای real-time هستید، تیم معماری و توسعهٔ نرمافزار شرکت راهکار نگار هوشمند (آرکان) میتواند از تحلیل مسئله تا طراحی معماری و پیادهسازی عملیاتی، همراه شما باشد.
این مقاله با هدف انتقال تجربه و آگاهیبخشی فنی، توسط تیم توسعهٔ نرمافزار شرکت راهکار نگار هوشمند (آرکان) تهیه شده است.
#Redis
#SortedSet
#Leaderboard
#Scalable_Systems
#Backend_Architecture
#System_Design
#High_Performance
#RealTime_Systems
#Data_Structures
#Software_Engineering
#DevTools
#شرکت_راهکار_نگار_هوشمند
#arcanco
مطلبی دیگر از این انتشارات
تحلیل و مستندسازی معماری ابزار HSC بر اساس arc42
مطلبی دیگر از این انتشارات
چگونه با OpenTelemetry ، سیستمهای میکروسرویسی را بینقص ردیابی کنیم؟
مطلبی دیگر از این انتشارات
مدیریت APIها