Реалізація користувацького рейтингу для геймифікації мобільного додатку
Рейтинг користувачів здається простим — сортувати користувачів за очками. Але коли у вас є тисячі користувачів, простий ORDER BY score DESC може перевантажити базу даних. А коли потрібний real-time ранг одного користувача серед мільйонів, потрібні Redis та спеціалізовані структури даних.
Архітектура для різних масштабів
До 10 000 користувачів: PostgreSQL з функціями вікна RANK() / DENSE_RANK(). Запити з розбиттям на сторінки працюють добре навіть без спеціальних індексів. Кешуйте топ-100 у Redis з TTL 5 хвилин.
10 000 – 1 000 000 користувачів: Redis Sorted Sets (ZADD, ZRANK, ZREVRANK, ZREVRANGE). Для додавання очок: ZADD leaderboard:global NX <score> <user_id> або ZINCRBY leaderboard:global <delta> <user_id>. Для отримання рангу користувача: ZREVRANK leaderboard:global <user_id> — O(log N). Топ-100: ZREVRANGE leaderboard:global 0 99 WITHSCORES — O(log N + 100). Це масштабується без деградації навіть до мільйонів користувачів.
Мільйони користувачів: Розділіть Redis Sorted Sets по часовим періодам і регіонам. Глобальний рейтинг «за весь час» стає недосяжною метою для більшості користувачів, що демотивує. Краще використовувати сегментацію.
Часові періоди рейтингу
Рейтинг «за весь час» для топ-гравців, тижневий і місячний для всіх інших. Скидайте на початку кожного періоду: не видаляйте дані, архівуйте їх. Користувачі повинні бачити свої історичні результати.
Реалізація: використовуйте окремі ключі для кожного періоду — leaderboard:weekly:2025-W13, leaderboard:monthly:2025-03. Коли період закінчується, створіть новий ключ; старий залишається для історії. Встановіть TTL на старі ключі: 30 днів для тижневих, 90 для місячних.
Рейтинг «навколо мене»
Найбільш мотивуючий тип рейтингу для звичайних користувачів. Не «топ-100», а «ви займаєте 4573-тю позицію, ось 5 людей вище і 5 нижче». Це мотивує користувачів, які ніколи не потраплять у топ.
Реалізація на Redis: ZREVRANK для отримання рангу користувача, потім ZREVRANGE(rank-5, rank+5) для сусідніх позицій. Виконайте додатковий запит до PostgreSQL для отримання імені користувача та аватара за user_id з результатів.
Соціальні рейтинги
Рейтинги серед друзів часто мотивують більше, ніж глобальні. Реалізація складніша: глобальний sorted set не має сенсу для списку з 50 друзів.
Варіант 1: При завантаженні екрана запитайте PostgreSQL: SELECT user_id, score FROM user_scores WHERE user_id IN (:friends_list) ORDER BY score DESC. Працює добре для невеликої кількості друзів.
Варіант 2: Збережіть окремий sorted set для кожного користувача — leaderboard:user:<user_id>:friends — оновлюйте його щоразу, коли змінюється очко будь-якого друга. Вища вартість пам'яті Redis, але миттєве читання.
Відображення та UX
Виділення позиції поточного користувача в списку є обов'язковим. Анімоване оновлення рангу при отриманні нових очок (scroll + highlight). Показуйте стрілки зростання/падіння поруч із позицією — «ви піднялися на 12 позицій сьогодні». Виведіть історичний графік рейтингу користувача за період.
На Flutter: використовуйте AnimatedList для плавних оновлень. На iOS: UITableView з performBatchUpdates. Не перезавантажуйте весь список при оновленні — тільки оновіть змінені комірки.
Агресивно кешуйте аватари у рейтингах. Використовуйте SDWebImage (iOS) / Glide (Android) / cached_network_image (Flutter) з дисковим кешем. Не показуйте рейтинг без імен і аватарів — показуйте skeleton loader під час завантаження.
Часові оцінки
Базовий рейтинг з тижневими/місячними періодами та рангом користувача — 2–3 дні (клієнт) + 2–3 дні (backend на Redis). З соціальними рейтингами, «навколо мене», історією рейтингу та real-time оновленнями — 1–2 тижні. Ціна розраховується індивідуально.







