HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Сегодня многие компании используют геймификацию для маркетинга и вовлечения аудитории. Например, трейдинговые платформы часто запускают турниры с большими призовыми фондами. Чтобы победить в турнире, нужно заработать как можно больше денег за определенный промежуток времени. Ключевая часть любого турнира - это лидерборд (таблица с рейтингом лидеров). Рейтинги мотивируют пользователей торговать больше, чтобы обогнать соперников.</p>
1 <p>Сегодня многие компании используют геймификацию для маркетинга и вовлечения аудитории. Например, трейдинговые платформы часто запускают турниры с большими призовыми фондами. Чтобы победить в турнире, нужно заработать как можно больше денег за определенный промежуток времени. Ключевая часть любого турнира - это лидерборд (таблица с рейтингом лидеров). Рейтинги мотивируют пользователей торговать больше, чтобы обогнать соперников.</p>
2 <p>Технически организация рейтингов - интересная задача. Все данные должны храниться в надежном хранилище: например, РСУБД. Однако при средней нагрузке сервера запрос на получение данных будет выполняться долго, потому что содержит агрегации и сортировку. Чтобы клиент не ждал несколько секунд при формировании рейтинга, используется кэширование в Redis. Но как лучше хранить лидерборд в кэше?</p>
2 <p>Технически организация рейтингов - интересная задача. Все данные должны храниться в надежном хранилище: например, РСУБД. Однако при средней нагрузке сервера запрос на получение данных будет выполняться долго, потому что содержит агрегации и сортировку. Чтобы клиент не ждал несколько секунд при формировании рейтинга, используется кэширование в Redis. Но как лучше хранить лидерборд в кэше?</p>
3 <p>Рассмотрим пример с турниром на трейдинговой платформе. Данные содержат идентификаторы пользователей и количество заработанных денег. По этим данным формируется рейтинг:</p>
3 <p>Рассмотрим пример с турниром на трейдинговой платформе. Данные содержат идентификаторы пользователей и количество заработанных денег. По этим данным формируется рейтинг:</p>
4 <h3>Хранение в JSON</h3>
4 <h3>Хранение в JSON</h3>
5 <p>Самый очевидный способ - это хранить рейтинг в сериализованном виде, например, в JSON.</p>
5 <p>Самый очевидный способ - это хранить рейтинг в сериализованном виде, например, в JSON.</p>
6 <p>Этот способ имеет несколько существенных недостатков:</p>
6 <p>Этот способ имеет несколько существенных недостатков:</p>
7 <ul><li>очень много логики уходит на сторону приложения. Нужно реализовывать парсинг и обновление данных с сортировкой</li>
7 <ul><li>очень много логики уходит на сторону приложения. Нужно реализовывать парсинг и обновление данных с сортировкой</li>
8 <li>нельзя получить лидеров рейтинга простым способом. В любом случае придется доставать список всех пользователей. А если в турнире участвует более 1 млн. человек?</li>
8 <li>нельзя получить лидеров рейтинга простым способом. В любом случае придется доставать список всех пользователей. А если в турнире участвует более 1 млн. человек?</li>
9 </ul><h3>Хранение в Lists</h3>
9 </ul><h3>Хранение в Lists</h3>
10 <p>Следующий возможный способ хранения - это Lists в Redis:</p>
10 <p>Следующий возможный способ хранения - это Lists в Redis:</p>
11 <p>Из преимуществ такого подхода: появляется возможность получать ТОП-10 пользователей без чтения всего списка. Однако недостатки все еще присутствуют:</p>
11 <p>Из преимуществ такого подхода: появляется возможность получать ТОП-10 пользователей без чтения всего списка. Однако недостатки все еще присутствуют:</p>
12 <ul><li>все значения придется парсить при каждом чтении/записи со стороны сервера</li>
12 <ul><li>все значения придется парсить при каждом чтении/записи со стороны сервера</li>
13 <li>вставка/обновление рейтинга требует получения всего списка, что недопустимо при нагрузках на сервер</li>
13 <li>вставка/обновление рейтинга требует получения всего списка, что недопустимо при нагрузках на сервер</li>
14 <li>все также нужно реализовать логику сортировки на стороне приложения</li>
14 <li>все также нужно реализовать логику сортировки на стороне приложения</li>
15 </ul><h2>Redis Sorted Sets</h2>
15 </ul><h2>Redis Sorted Sets</h2>
16 <p>В Redis существует структура данных Sorted Sets (отсортированные наборы), которая позволяет эффективно хранить и работать с лидербордами. Если Set представляет собой набор уникальных строк, то Sorted Set добавляет сортировку с помощью дополнительного параметра - рейтинга.</p>
16 <p>В Redis существует структура данных Sorted Sets (отсортированные наборы), которая позволяет эффективно хранить и работать с лидербордами. Если Set представляет собой набор уникальных строк, то Sorted Set добавляет сортировку с помощью дополнительного параметра - рейтинга.</p>
17 <h3>Запись</h3>
17 <h3>Запись</h3>
18 <p>Чтобы добавить запись в лидерборд, используется команда zadd key score member [score member ...]:</p>
18 <p>Чтобы добавить запись в лидерборд, используется команда zadd key score member [score member ...]:</p>
19 <p>При успешном добавлении нового участника возвращается единица. Если участник существовал, то он перезапишется с новым значением, и команда вернет 0.</p>
19 <p>При успешном добавлении нового участника возвращается единица. Если участник существовал, то он перезапишется с новым значением, и команда вернет 0.</p>
20 <p>Если участник заработал какую-то сумму, то можно прибавить ее к рейтингу командой zincrby key increment member:</p>
20 <p>Если участник заработал какую-то сумму, то можно прибавить ее к рейтингу командой zincrby key increment member:</p>
21 <p>В ответе возвращается новый рейтинг участника.</p>
21 <p>В ответе возвращается новый рейтинг участника.</p>
22 <h3>Получение лидерборда</h3>
22 <h3>Получение лидерборда</h3>
23 <p>Для получения списка ТОП-3 используется команда zrange key min max. По умолчанию команда вернет список от наименьшего рейтинга к наибольшему. Так как в данном примере в рейтинге хранится количество заработанных денег и необходимо, чтобы пользователь с наибольшей суммой был на первом месте, нужно передать дополнительный параметр REV:</p>
23 <p>Для получения списка ТОП-3 используется команда zrange key min max. По умолчанию команда вернет список от наименьшего рейтинга к наибольшему. Так как в данном примере в рейтинге хранится количество заработанных денег и необходимо, чтобы пользователь с наибольшей суммой был на первом месте, нужно передать дополнительный параметр REV:</p>
24 <p>Отсчет индексов начинается с нуля, поэтому для получения ТОП-3 используются границы 0 и 2.</p>
24 <p>Отсчет индексов начинается с нуля, поэтому для получения ТОП-3 используются границы 0 и 2.</p>
25 <p>Если требуется получить количество денег кроме идентификаторов пользователей, то добавляется параметр WITHSCORES:</p>
25 <p>Если требуется получить количество денег кроме идентификаторов пользователей, то добавляется параметр WITHSCORES:</p>
26 <h2>Резюме</h2>
26 <h2>Резюме</h2>
27 <ul><li>для кэширования рейтингов в Redis следует использовать встроенную структуру данных Sorted Sets</li>
27 <ul><li>для кэширования рейтингов в Redis следует использовать встроенную структуру данных Sorted Sets</li>
28 <li>при использовании Sorted Sets со стороны сервера не нужно писать никакую логику</li>
28 <li>при использовании Sorted Sets со стороны сервера не нужно писать никакую логику</li>
29 <li>Sorted Sets эффективно работает при большом количестве данных. Добавление/обновление и чтение набора имеет<a>алгоритмическую сложность</a>O(log(N))</li>
29 <li>Sorted Sets эффективно работает при большом количестве данных. Добавление/обновление и чтение набора имеет<a>алгоритмическую сложность</a>O(log(N))</li>
30 </ul>
30 </ul>