О чём эта история

Яндекс Игры — это платформа бесплатных онлайн‑игр на любой вкус: боевики, головоломки, гонки, игры на двоих и многое другое. Каталог насчитывает более 15 тысяч игр, не требующих предварительной установки. В них можно играть в браузере с телефона или компьютера.

Яндекс Игры перенесли примерно 100 ГБ данных об игровом прогрессе и все остальные важные данные сервиса, в том числе сами игры, из единой базы PostgreSQL в Yandex Managed Service for YDB. Благодаря этому среднее время запросов на запись сократилось на 35% и 52% для двух больших шардированных таблиц. Время запросов на чтение уменьшилось на 50% и 58% соответственно. Сейчас среднее время выполнения запросов 14‑16 мс при росте RPS в 5 раз.

Найти распределённую СУБД

Платформа Яндекс Игр состоит из двух частей:

  • Консоль для разработчиков, позволяющая публиковать собственную игру и управлять ей на платформе.
  • Каталог, содержащий более 15 тысяч HTML5‑игр.

Изначально все игры, прогресс и важные данные сервиса Игры хранили в единой базе PostgreSQL. Это хорошо работало, пока сервис был относительно небольшим. Но в перспективе кратного роста аудитории стало очевидно, что данные, которые генерируют пользователи, нужно хранить отдельно, чтобы большое количество запросов и объём пользовательских данных не привели к деградации базы в целом.

Может быть, просто развернуть кластер PostgreSQL?

Команда Игр сформулировала требования к новому инструменту для хранения пользовательских данных. Необходимо было найти решение, которое способно:

  • Хранить JSON‑структуру, отражающую прогресс пользователей в играх.
  • Получать и записывать c высокой интенсивностью данные по Primary Key.
  • Не деградировать по скорости выполнения запросов при росте количества записей и объёма базы.

Объёмы данных и количество записей могли быстро расти. Сначала команда рассматривала развёртывание ещё одного кластера PostgreSQL как уже знакомый и простой вариант. Но, с учётом роста нагрузок, в этом варианте нужно было применить логическое шардирование базы PostgreSQL. Это привело бы к дополнительным трудозатратам на администрирование и поддержку СУБД.

Тогда коллеги из Yandex Cloud предложили команде Игр попробовать управляемую YDB. YDB — распределённая отказоустойчивая СУБД, которая автоматически масштабируется по размеру и нагрузке. Yandex Managed Service for YDB помогает разворачивать и поддерживать базы данных YDB, что позволило бы избежать затрат на настройку и последующее администрирование собственного кластера. Кроме того, команда Игр может обращаться непосредственно к разработчикам YDB при возникновении вопросов. Выбор в пользу YDB также был продиктован тем, что эта СУБД использует диалект SQL и предоставляет SDK на языке Go, — процесс работы команды был бы таким же привычным, как и с PostgreSQL.

Переход в YDB в три этапа

Миграцию разделили на три основных этапа:

  1. Запись в новую базу YDB с параллельным использованием PostgreSQL.
  2. Непосредственно миграция данных.
  3. Отказ от чтения из PostgreSQL.

Команда Игр решила перенести сначала информацию авторизованных пользователей. На первом этапе команда Игр написала обёртку для Go SDK, которая была похожа на текущий интерфейс клиента PostgreSQL и занималась записью в новую базу в YDB. Команда выбрала вычислительные ресурсы со следующими характеристиками:

  • vCPU с 10 ядрами.
  • 50 ГБ оперативной памяти.
  • 3 зоны доступности.

При создании таблицы прогресса авторизованных пользователей в YDB использовали стандартные настройки шардирования по размеру — 2048 МБ. При сегментировании может возникнуть проблема неравномерного распределения шардов, если первичный ключ инкрементируемый. В данном случае проблемы удалось избежать, так как использующийся в качестве первичного ключа идентификатор пользователя составной и имеет большой разброс значений. Чтение осуществлялось в определённом порядке: если в YDB данных не нашлось, то запрос отправлялся в PostgreSQL. Если там данные находились, то они дозаписывались в YDB и возвращались в ответе пользователю. Идентично работал и процесс записи. Такая схема работы ожидаемо привела к небольшой деградации скорости ответов из‑за обращений к резервному PostgreSQL, но в продакшне этот переходный вариант работал не дольше недели, а затем переход в YDB завершился полностью.

На втором этапе команда Игр переносила все оставшиеся данные из старой базы. Сделали дамп таблицы, разместили его на виртуальной машине и с помощью Python‑скрипта в несколько потоков начали запись данных в YDB. Таким образом команда перенесла 15 ГБ данных в YDB, при этом база продолжала работать под нагрузкой.

На последнем этапе изменили схему чтения, отключив запросы на чтение к старой базе PostgreSQL.

Первым эффектом миграции стало уменьшение времени запроса на запись на 35%, а на чтение — почти на 50% в режиме 80p. Команда Игр оценила этот результат и решила перенести в YDB также список игр неавторизованных пользователей. На тот момент планировалось расширение функциональности Игр для неавторизованных пользователей, которых всегда много. Поэтому рост объёмов данных ожидался и в этом сценарии.

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

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

В итоге для списка игр неавторизованных пользователей результат миграции был ещё лучше: время выполнения запроса на запись сократилось на 52%, а на чтение — на 58%.

Рост RPS при сохранении времени ответа

Сейчас таблица прогресса авторизованных пользователей содержит более 45 шардов общим объёмом до 70 ГБ, при этом время запроса на запись — 17 мс при 350 RPS, а на чтение — 16‑18 мс при 90 RPS. Ещё выше, по сравнению со значениями до миграции, результат для таблицы с информацией о неавторизованных пользователях. Удалось снизить время запроса на запись до 20 мс при 30 RPS, а на чтение до 14 мс при 120 RPS.

Команда Яндекс Игр выполнила все поставленные задачи: внедрила в воркфлоу распределённую базу YDB, которая хранит данные о прогрессе и списки игр пользователей и не деградирует по скорости выполнения запросов независимо от роста количества записей и объёма базы данных. Средний пиковый RPS вырос в 5 раз за 1,5 года размещения проекта в Yandex Managed Service for YDB и составляет порядка 5000. При этом среднее время выполнения запросов остаётся неизменным, на уровне 14‑16 мс.

Мнение

Александр Смолин,
старший разработчик группы разработки бэкенда Яндекс Игр
Александр Смолин,
старший разработчик группы разработки бэкенда Яндекс Игр

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