13 миллионов захваченных гексов положили сервер моей GPS-игры
Спойлер: сейчас в GeoConquest захвачено уже 24,6 миллиона гексов (или примерно 8107 км²).
Но в сентябре игра была под угрозой отключения. Две недели я потратил на оптимизации сервера и снизил нагрузку на RAM в 44 раза, значительно ускорил захваты и также снизил нагрузку на CPU.
Однажды я выкатил небольшое обновление и стал ждать обычного редеплоя. Но GeoConquest не поднялась: сервер пытался загрузить в память уже накопленные территории и не справился. К тому моменту игроки захватили больше 13 миллионов гексов.
Важно: никто не захватил 13 миллионов одним махом. Это был общий результат игры, который серверу предстояло восстановить после перезапуска. Данные не пропали, у меня были резервные копии, но игра оказалась недоступна — и я не знал, когда удастся вернуть её в строй.
Я тогда временно поднял лимит heap, чтобы сервер хотя бы запустился. Но мысль «добавить ещё памяти» не могла быть решением навсегда. Позже даже попытка поднять лимит с 4 до 5 ГБ не помогла. А в голове крутилась совсем простая мысль: что будет, когда игроки захватят ещё миллион гексов? Неужели придётся покупать сервер подороже?
Незадолго до этого игра уже пережила захват примерно 3,23 миллиона нейтральных гексов. Результат появился меньше чем через девять минут, сервер продолжал работать. Тогда это выглядело хорошим испытанием. Но оказалось, что обработать большое изменение и заново загрузить в память весь уже накопленный мир — две разные задачи. Проблема проявилась не во время того захвата, а после обычного обновления.
Две разные нагрузки
Когда игра ожила, я стал смотреть на масштабирование шире. Если я хочу дальше развивать игру и приводить новых игроков, недостаточно просто надеяться, что текущий сервер как-нибудь справится.
У GeoConquest оказалось две отдельные проблемы роста. Первая — сам мир: игроки захватывают всё больше гексов, и их нужно хранить и показывать на карте. Вторая — количество одновременных действий: игроки двигаются, оставляют следы, запрашивают карту. Оптимизация одной нагрузки не решает другую.
И ещё третья проблема - скорость расчёта захватов стала уже неприлично долгой.
Я отдельно занялся частыми запросами, следами и отображением игроков. По графику загрузки процессорных ядер стало видно, что среднее напряжение немного снизилось и пиков стало меньше. Но вопрос оставался: что будет, если кто-то захватит ещё миллион гексов — или даже сто миллионов?
А одна из первых попыток разобраться с памятью дала обратный результат: прогрев сервера занял аж 4 минуты 40 секунд, а расход RAM вырос ещё примерно на 0,8 ГБ. Тут уже стало понятно, что нужно менять сам подход к данным.
Карта перестала помещаться в одну копию
Сначала я занялся обзорной картой. До этого сервер при запуске строил для неё большую копию в оперативной памяти. Я начал сохранять готовые фрагменты карты в базе данных, чтобы сервер мог доставать их по мере необходимости, а не держать целый мир в памяти, ещё и на разных масштабах.
Переход пришлось разделить на этапы. Для проверки нужно было подготовить почти 50 тысяч фрагментов карты на основе примерно 14 миллионов занятых гексов. На первичное заполнение базы ушло около трёх часов: всплывали ошибки, приходилось исправлять их и запускать процесс заново. Это был неприятный простой, но он помог подтвердить, что новая схема сохраняет и обновляет данные корректно.
После переключения карта стала загружаться из базы, а в памяти оставались только недавно просмотренные фрагменты. Потребление памяти основным процессом снизилось примерно с 3,7 до 1,86 ГБ. Перезапуск, который раньше занимал до 170 секунд, на этом этапе сократился примерно до 72. При ручной проверке карты около 84% запросов обслуживались из памяти, среднее время ответа было около 45 миллисекунд.
Но это была проверка прокруткой карты, почти без одновременных захватов. Она не доказывала, что игра уже выдержит тысячи игроков. Зато стало ясно, что у нас появился запас для следующего шага. Я смог выдохнуть и уже не бояться нависающей угрозы.
Миллионы гексов — сотни тысяч записей
Следующая идея была в том, чтобы не хранить каждый маленький гекс отдельно, если большая территория целиком принадлежит одному владельцу. В H3 есть вложенные друг в друга уровни шестиугольников: однородную область можно представить ячейкой покрупнее, а там, где владельцы различаются, снова разбить её на более мелкие.
Перевод шёл постепенно, в фоне и со сверкой данных. Это было важно: речь шла о реальных территориях игроков, и ошибка могла стоить им захватов.
В итоге 15,64 миллиона игровых гексов удалось представить примерно 354 тысячами укрупнённых записей — примерно в 44 раза меньше. На одном из промежуточных этапов все территории занимали около 36,5 МБ RAM. После финального этапа весь игровой процесс использовал около 645 МБ оперативной памяти; собственно JavaScript heap — около 292 МБ.
Для моего сервера с 8 ГБ RAM запас более чем достаточный.
Тогда же перезапуск удалось сократить до 18–20 секунд. Но это улучшение оказалось не вполне устойчивым: после следующего выката один из перезапусков снова занял около 170 секунд. Так что я не хочу выдавать промежуточную цифру за гарантированную скорость после всех изменений.
Последний шаг был уже про ожидание захвата. В локальной эмуляции тяжёлого реалистичного примера расчёт сократился с 9,36 до 1,45 секунды — примерно в 6,5 раза. На проде к расчёту добавляется запись результата, поэтому это не обещание, что каждый захват теперь занимает ровно столько же. Но это хороший признак: расчёт наконец тоже начал ускоряться, а не только занимать меньше памяти. В реальности скорость захватов увеличилась в десятки раз, потому что записывать в БД владельцев гексов теперь тоже быстрее (из-за сворачивания через h3).
В течение 2 недель я боялся, что следующий большой мир просто не даст серверу запуститься. В тот момент оптимизация не добавила на карту новой кнопки или красивого эффекта. Зато её результат игроки должны почувствовать в обычных вещах: меньше ждать загрузки, перезапуска и расчёт гигантских захватов за секунды. Теперь я могу спокойно развивать проект дальше, уже выпустил и клиентскую оптимизацию, и приложение на Google Play, и множество других улучшений и фичей.
Попробуйте GeoConquest в Google Play или через браузер.

















