مسئله: جدول امتیاز بازی موبایل با ۱۰ میلیون بازیکن — هر باخت، امتیاز عوض میشود و هر کاربر میخواهد رتبه خودش و ۱۰ نفر برتر را همین الان ببیند. سؤال ظاهراً ساده که یک ساختمانداده جادویی را وادارت میکند.
۱-۲) نیازمندی و تخمین
- FR: ثبت امتیاز، رتبه من، top-K ،رتبههای اطراف من.
- NFR: بهروزرسانی و خواندن هر دو در میلیثانیه؛ دهها هزار آپدیت در ثانیه در پیک مسابقات.
- چرا نه SQL ساده؟ SELECT COUNT(*) WHERE score > X برای هر نمایش یعنی اسکن میلیونها سطر — ORDER BY + LIMIT هم با ایندکس برای «رتبه من» کافی نیست. نیازمند ساختمانداده مرتبِ حافظهای.
۳) طراحی — Skip List در Redis
Redis Sorted Set (ZSET) دقیقاً برای همین است: پیادهسازی Skip List — لیست پیوندی چندسطحه که مثل ایندکس بالا میپرد. ZADD (بهروزرسانی امتیاز)، ZREVRANK (رتبه من)، ZRANGE (top-K) همگی O(log N). ده میلیون عضو؟ چند صد مگابایت حافظه و پاسخ میکروثانیهای. یک دستور، یک مسئله کامل.
۴) عمق و گلوگاه
- تاریخچه و پایداری: Redis را write-through به DB کن یا دورهای snapshot بگیر؛ رتبهبندی فصلی ریست میشود ولی تاریخچه ماندگار است.
- Leaderboard های متعدد (روزانه/هفتگی/دوستان): کلید جدا per دوره؛ «دوستان» = ZSET کوچک per کاربر یا راهحل با INTER — حجمش را تخمین بزن قبل از انتخاب.
- تقلب: امتیاز باید از نتیجه بازیِ اعتبارسنجیشده سرور بیاید، نه عددی که کلاینت میفرستد — اعتماد به کلاینت یعنی پایان رقابت.
- شارد: اگر یک ZSET هم دیر شد، تقسیم بر بازه امتیاز یا بازی — ولی با ۱۰M عضو معمولاً همان یک نمونه (با replica برای خواندن) کافی است.
به زبان ساده
رتبهبندی زنده یعنی ساختمانداده مرتبِ حافظهای (Sorted Set) که آپدیت، «رتبه من» و «۱۰ نفر برتر» را در کسری از میلیثانیه جواب میدهد.
مثال واقعی
جدول امتیاز مسابقه: با هر باخت، Redis فقط جای بازیکن را در لیست مرتب جابهجا میکند؛ نیازی نیست برای هر نمایش، ده میلیون رکورد دوباره مرتب شوند.