Кшитий Джолли, инженер Probo

Что делает игру интересной или захватывающей? Я думаю, что это может быть одно из двух:

  • Уровни. Проходя все более и более сложные уровни, вы испытываете чувство достижения, заставляя вас часто возвращаться.
  • Таблица лидеров. Высокие баллы и возможность сравнить себя с другими игроками мотивируют вас постоянно совершенствоваться и достигать большего.

Как разработчик, если ваша игра бесконечна (игра без предопределенных уровней) и если вы все еще не включаете списки лидеров, вы определенно упускаете возможность. Люди хотят знать, как они работают по сравнению с другими. Они хотят знать, как они сравниваются со своими друзьями, и иметь представление о том, как они могут стать лучше. Именно это дает им мотивацию возвращаться в игру снова и снова.

Прежде чем перейти к ранней реализации таблицы лидеров, которую мы сделали для нашего варианта использования с использованием MySQL, позвольте мне кратко рассказать о приложении/платформе, над которой мы работаем.

Probo — это, по сути, социальная и торговая платформа, которая позволяет вам делиться мнениями и занимать позиции относительно различных реальных будущих событий. Он охватывает широкий спектр категорий для этих событий, включая спорт, политику, новости, финансы, развлечения и т. д. Вы можете торговать на будущем событии и зарабатывать, если в конечном итоге окажетесь правы, когда событие завершится.

Довольно захватывающие вещи, верно? Тем более, что это предполагает заработок реальных денег. Но все же это было бы неполным без списков лидеров, чтобы показать пользователей, зарабатывающих больше всего через приложение. Кто разбирается в финансах? Кто лучше всех предсказывает события в политике? Являюсь ли я самым осведомленным в своей группе сверстников, когда дело доходит до прогнозов на крикет? Нет никаких сомнений в том, что таблицы лидеров для них просто необходимы.

Мы внедрили таблицу лидеров для нашего приложения, используя запросы MySQL к таблице сделок, которая содержала все сделки пользователя на мероприятии.

Эта реализация для получения результатов с разбивкой на страницы для таблицы лидеров с использованием MySQL включала следующие шаги:

  1. Получить сумму прибыли по всем сделкам для пользователя
SELECT SUM(profit) FROM trades WHERE user_id = 123; //Answer: x

2. Получить ранг для текущего пользователя на основе суммы прибыли других пользователей

SELECT COUNT(distinct user_id) FROM trades WHERE user_id != 123
GROUP BY user_id
HAVING SUM(profit) > x;

3. Получите сумму прибыли для всех пользователей на странице

SELECT SUM(profit) FROM trades
GROUP BY user_id
ORDER BY SUM(profit) DESC
LIMIT 0,20

4. Присвойте рейтинг каждому пользователю на текущей странице.

Для размера до 10 000 пользователей этот процесс казался достаточно быстрым. Но у нас были серьезные проблемы, как только это число приблизилось к 1 миллиону!

Таблица лидеров загружалась до 30 секунд, а нумерация страниц была беспорядочной. В довершение всего, по завершении мероприятия, когда многие пользователи одновременно открывали таблицу лидеров, загрузка ЦП для нашего MySQL на основе RDS подскакивала до 100% и замедляла все остальные процессы.

Мы пытались оптимизировать процесс, добавляя индексы в MySQL, предварительно вычисляя совокупную прибыль для каждого пользователя — все это реализовать не получится. Даже агрегирование прибыли не дало желаемой производительности, и у нас также было много фильтров по дате, категориям событий, подписчикам и т. д., которые невозможно было учесть.

Итак, мы начали изучать другие способы реализации таблицы лидеров для нашего приложения. Мы уже использовали Redis в качестве кэша, поэтому одним из первых вариантов, который мы исследовали, было использование Redis для хранения списков лидеров.

Наткнулся на эту прекрасную статью о примере использования списков лидеров на официальном сайте Redis:

  1. Списки лидеров | Редис
  2. Концепция таблицы лидеров — таблица результатов, показывающая ранжированные имена и текущие результаты (или другие данные) игроков…
  3. redis.com

Основная идея реализации таблицы лидеров основана на сортированных наборах (ZSET). Отсортированные наборы — это, по сути, набор элементов, где элементы отсортированы в соответствии с содержащимся в них значением. Он поддерживает все основные операции над наборами, а также поддерживает разбиение на страницы.

Вот шаги по реализации таблицы лидеров в нашем приложении с использованием Redis:

  1. Создайте отсортированный набор, содержащий пользователей вместе с накопленной прибылью от событий. Используйте ZADD, чтобы добавлять участников с прибылью. (шаг предварительного расчета).
ZADD ldb_event_1 27.5 "123" //Adds member 123 with profit 27.5

2. Найдите рейтинг пользователя, используя ZCOUNT, чтобы найти количество всех пользователей, имеющих прибыль за вычетом этой прибыли пользователя. Мы не использовали ZRANK здесь, поскольку ZRANK предполагает разные дополнительные ранги для пользователей с одинаковой прибылью.

ZCOUNT ldb_event_1 (27.5 +inf
//Counts number of members having profit > 27.5

3. Найдите пользователей таблицы лидеров для страницы, используя ZREVRANGE с начальным и конечным ограничениями.

ZREVRANGE ldb_event_1 20 40
//Gets members from numbered 20-40 in reverse sorted list of users

4. Присвойте ранги пользователям на странице.

Реализация была сделана специально с использованием ioredis для Node.js.

Реализация была намного чище по сравнению с нашей предыдущей реализацией с использованием MySQL. И это было невероятно быстро, сократив общее время API с более чем 30 секунд до менее чем 50 миллисекунд (извлечение URL-адресов изображений и имен пользователей для пользователей занимает большую часть времени)! Только в случае сбоя Redis или перестроения кеша мы разработали его так, чтобы он возвращался к базе данных для построения таблицы лидеров.

Это в значительной степени подводит итог еще одному классному варианту использования Redis. Эта настройка для списков лидеров очень хорошо работает для нашего приложения. Некоторые из проблем, с которыми мы сталкивались до сих пор, включают:

  1. Для больших списков лидеров однократная загрузка занимает много времени. ZADD поддерживает пакетную загрузку, но при количестве пользователей до 5 миллионов все еще требуется время при размере пакета 50 КБ.
  2. При большом количестве одновременных операций записи/обновления в Redis такие запросы, как ZCOUNT, замедляются и достигают 180 мс во времени ответа.

Спасибо, что прочитали.

Будем рады любым отзывам или вашим знаниям в разделе комментариев ниже.

Передайте привет 👋 Кшитию Джолли в LinkedInInstagramTwitter