Персистентность и шардинг
Механизм: где физически лежит истина
Сетевая модель MMO — авторитетный клиент-сервер (см. таксономию): сервер — единственный источник правды, клиент только предсказывает и рисует. Этот урок про следующий вопрос: на каком железе живёт эта правда, когда онлайн — десятки тысяч, а персонажей в базе — миллионы, и любое состояние обязано пережить выключение клиента.
Два слоя: горячий симулятор и холодный store
Состояние мира расслаивается надвое:
- Горячий слой (RAM). Авторитетная симуляция онлайн-игроков и загруженных зон крутится в памяти серверного процесса на тикрейте (позиции, бой, спавны NPC). Быстро, эфемерно.
- Холодный слой (durable БД). Реляционная база (SQL Server / MySQL / Postgres): персонажи, инвентарь как foreign keys на предметы, гильдии, почта. Медленно, надёжно. Хранит всех — включая офлайн-персонажей, которых нельзя держать в RAM.
Мир периодически сохраняется: горячее состояние сериализуется в холодное (на логауте, по таймеру, при важных транзакциях). Между сохранениями durable-копия отстаёт от живой. Отсюда — главный класс багов эпохи: откаты и дюпы предметов при крахе, если границы сохранения и транзакций неаккуратны.
Сохранение, окно отката и дюп
Пусть мир флашится в БД каждые секунд. Краш узла между флашами теряет до секунд прогресса — игрок логинится «в прошлом» (классический «ролбэк сервера»). Ожидаемая потеря при равномерном кэше:
Дюп опаснее отката, потому что разрушает экономику. Передача предмета — это две мутации: списать у A, начислить B. Если они не атомарны и относительно границы флаша пересеклись с крашем, при восстановлении предмет может оказаться и у A (откатился к состоянию «до передачи»), и у B (начисление успело лечь) — он удвоился. Лечится не «античитом», а тем, что торговля — это ACID-транзакция над durable-store, а не ленивый write-behind двух независимых блобов персонажей.
Один процесс не тянет — два способа резать
Узел упирается в CPU/память/БД задолго до 100k игроков. Развилка масштабирования:
Шардинг: режем популяцию
Шардинг — горизонтальное партиционирование игроков: мир копируется в N независимых экземпляров (реалмы / серверы), каждый со своим авторитетным состоянием и своей БД. Это буквально шардинг базы данных, где ключ партиционирования = реалм. WoW так и устроен: на экране выбора персонажа ты выбираешь реалм — то есть раздел БД. Плюсы: линейное масштабирование (нужно больше места — поднял ещё реалм), отказ одного реалма не роняет остальные, маленькая «поверхность» консистентности. Минус один, но тяжёлый: социальный граф раздроблен — друзья на разных реалмах не могут торговать и ходить в рейды; перенос персонажа = платная миграция строки между БД. Современные смягчения: connected realms, cross-realm зоны (CRZ — наоборот, сливают недонаселённые реалмы, чтобы зона «жила»), data-центры FFXIV с «World Visit».
Один шард: режем пространство
EVE пошла против течения: одна вселенная для всех (сервер «Tranquility»), партиционирование не по популяции, а по пространству. Вселенная нарезана на ~8000 солнечных систем; каждая система в любой момент «принадлежит» одному узлу (процессу) в большом кластере. Игроки взаимодействуют через весь мир — один рынок, одна история, — но пропускная способность отдельной системы упёрта в один узел: бой в системе нельзя «распараллелить» на два CPU, потому что все корабли в нём взаимодействуют друг с другом (это не embarrassingly parallel). Поэтому единственный рычаг под перегрузом — скорость хода времени.
Time Dilation: backpressure через замедление времени
EVE крутит серверный тик частотой 1 Гц (симуляция продвигается на 1 секунду за тик). Пусть узлу нужно за «секунду игры» обработать работу , а его реальная мощность за секунду стены — . Загрузка . Пока — порядок; на подходе к 1 очередь действий по теории очередей растёт катастрофически:
Раньше это и был «лаг смерти» больших боёв: , очередь не разгребается, узел умирает. TiDi (2011) вводит коэффициент дилатации : симуляционное время идёт со скоростью относительно реального. Тогда за секунду стены узлу надо сделать лишь работы. Ставим:
Эффективная загрузка — очередь снова ограничена, и всё обрабатывается по порядку и честно. Пол (10%) гарантирует, что бой когда-нибудь закончится: 1 секунда симуляции за 10 секунд стены. TiDi не трогает EVE-серверное время (индустрия, обучение скиллов, таймеры структур идут по реальным часам) — тянется только «физика в космосе».
Числовой пример — почему B-R5RB шёл 21 час
В «Кровавой бане B-R5RB» (январь 2014) в одной системе одновременно было до 2670 пилотов (всего вовлечено 7548), 21 час, погибло 576 кэпиталов включая 75 «Титанов», ~11 трлн ISK (≈$300–330k реальных). Пусть узел чисто тянет действия ~250 кораблей в реальном времени (иллюстративно, не публичная цифра CCP). Тогда:
Загрузка вшестеро-вдесятеро выше единицы → прижат к полу 10%. Бой буквально идёт в одну десятую скорости — отсюда «булет-тайм», в котором залп Титана летит минутами, а 21 час реального времени вмещает лишь несколько часов «игровой» битвы. Без TiDi узел бы просто умер; с TiDi сражение медленно, но детерминированно и справедливо доигралось. Это и есть payoff одного шарда: B-R5RB — реальное историческое событие, а не «эпизод на сервере №47», потому что вселенная одна.
🕹 В какие игры поиграть — и что заметить
Архитектуру персистентности видно невооружённым глазом — на экране выбора сервера (или его отсутствии). Сыграй по нарастающей: от явного «выбери шард» до «шарда нет, есть один мир, который умеет замедляться».
Эталон «режем популяцию». При создании персонажа ты выбираешь реалм — это и есть выбор раздела БД. Внутри реалма зона ещё дробится (sharding зоны при наплыве, layering целого реалма на старте Classic 2019, позже убрали).
🎮 Сыграй: создай персонажей на двух разных реалмах — заметь, что они в отдельных вселенных: не видят друг друга, не торгуют, общий только аккаунт. Глянь цену «Character Transfer» в магазине Blizzard — ты платишь за миграцию строки между базами. Это цена шардинга, выписанная в долларах.
Та же модель, ещё нагляднее: список «миров»/«серверов» с индикатором населённости прямо в лобби. Каждый мир — независимый шард; популярные забиты, пустые стоят.
🎮 Сыграй: в RuneScape открой список миров — десятки пронумерованных шардов с счётчиком игроков. Зайди в мир с низким онлайном и в забитый: экономика, цены на Grand Exchange и людность ощутимо разные, потому что это разные миры, склеенные лишь общим аккаунт-сервисом.
Современный компромисс. Логические «миры» сгруппированы в data-центры; внутри мира зоны инстансируются (шардинг под капотом), но между мирами одного DC можно гостить (World Visit). Социальный граф уже не так раздроблен, как у классического WoW.
🎮 Сыграй: в FFXIV сделай World Visit на соседний мир своего data-центра — заметь, что инстанс-зоны (Limsa, рынки) распадаются на «дубли» под нагрузкой, и при этом ты физически переехал на другой мировой процесс. Гибрид: режут и популяцию (миры), и пространство (инстансы).
Противоположный полюс: ты вообще не выбирал сервер. Одна вселенная, один рынок, одна история на всех. Цена — Time Dilation в большом бою.
🎮 Сыграй: зайди в EVE и слетай в систему с известным крупным флитом (или посмотри стрим осады). Найди индикатор TiDi (значок часов / процент скорости) — увидишь, как при набивании системы он падает к 10%, и всё вокруг переходит в slow-motion. Сравни с тихой системой (100%). Ты буквально наблюдаешь backpressure: сервер тормозит время, чтобы не потерять ни одного действия.
Хардкор · инженерия: горячий/холодный, write-behind и откуда берутся дюпыможно пропустить
Сердце персистентности — граница между горячим авторитетным состоянием в RAM и холодным durable-store. Как её пройти — определяет и производительность, и весь класс экономических багов.
Write-through vs write-behind
- Write-through: каждая значимая мутация сразу коммитится в БД. Надёжно, но БД становится бутылочным горлышком на 10k+ транзакций/с — нельзя гонять каждый шаг персонажа в SQL.
- Write-behind (типично для MMO): мутации копятся в RAM, БД флашится пачками по таймеру/на логауте. Быстро, но открывает окно отката и требует осторожности с порядком записи.
Поэтому делят по важности: позиция/HP — write-behind (потерять не страшно), а деньги и предметы — write-through внутри транзакции (потерять/удвоить — катастрофа).
Анатомия дюпа
Передача предмета — две записи: A.inventory -= item и B.inventory += item. Если это не одна транзакция, а два независимых write-behind блоба, и узел падает в момент, когда успел персиститься B += item, но не A -= item (или восстановление переигрывает лог в «удобном» порядке) — предмет оказывается у обоих. Реальные дюп-эксплойты в UO/Diablo/WoW почти все этого класса: не «хакер», а нарушенная атомарность распределённой транзакции на стыке горячего и холодного слоёв, часто спровоцированная крашем/дисконнектом в нужный момент.
Лечение
- Торговля/аукцион — ACID-транзакция над durable-store (single-writer на партицию предмета, либо двухфазный коммит, если персонажи в разных шардах БД).
- Идемпотентность и монотонные ID операций — повтор лога после краша не должен начислять дважды.
- Передача владения через атомарную смену foreign-key, а не «удалить у одного / создать у другого».
Хардкор · хостинг: топология кластера, reinforced nodes и цена выбораможно пропустить
Где «режем пространство», размещение нагрузки на железо — это и есть геймплей-инфраструктура.
Узел = владелец пространственных ячеек
В одношардовой архитектуре кластер — это пул процессов (в EVE их исторически зовут SOL-узлами), и диспетчер раскидывает солнечные системы по узлам. Обычно много тихих систем живут на одном узле; горячая система может получить узел целиком. Перенос системы между узлами (или объединение) — нетривиальная операция: надо передать всё авторитетное состояние без «двойного владения».
Reinforced node — планируемая осада
EVE позволяет заранее запросить усиление узла: если альянсы анонсировали бой за систему, CCP переносит её на выделенный, более мощный узел до начала. Это ручное «horizontal pre-scaling» под предсказуемый пик — роскошь, доступная именно потому, что мир один и события в нём публичны.
Цена двух моделей
- Шардинг масштабируется почти линейно и дёшево: новый реалм = коробка железа + инстанс БД, операционно просто. Поэтому массовые theme-park MMO (WoW, тысячи реалмов) выбирают его.
- Один шард требует героической инженерии (TiDi, динамическое размещение узлов, обработка хэндоффов) и терпит «булет-тайм». Окупается только если единый мир — это сама суть игры (песочница, эмерджентная политика, один рынок).
Бизнес-следствие обеих: когда популяция падает, шардовые игры делают server merges (склейка пустых реалмов — та же боль, что CRZ), а одношардовые этим в принципе не болеют.
Хардкор · теория: почему очередь взрывается у L→1 и почему пол именно 10%можно пропустить
TiDi — это admission control поверх системы массового обслуживания. Узел, обрабатывающий действия игроков, моделируется очередью: поступление с интенсивностью , обслуживание с интенсивностью , загрузка .
Нелинейность у единицы
Для M/M/1 среднее число в системе , а среднее время в системе по Литтлу . При в системе ~9 заявок; при — ~99. Вот почему лаг больших боёв не линеен по числу игроков: пока есть запас — почти незаметно, у границы — обрыв. TiDi масштабирует эффективное поступление: замедляя симуляционные часы в раз, мы делим скорость, с которой действия «созревают» к обработке, удерживая .
Почему именно пол, а не «сколько надо»
Без пола под экстремальной нагрузкой ушёл бы в ~0 — бой не закончился бы никогда, и узел всё равно копил бы джиттер. Жёсткий пол гарантирует прогресс: даже в худшем случае 1 секунда боя проходит за 10 секунд стены. Это сознательный размен: при запредельной нагрузке узел может всё-таки начать отставать даже на полу (тогда действия буферизуются), но игра остаётся когерентной и честной — никто не «телепортируется», порядок действий сохранён. Backpressure вместо потери данных.
ML / AI: это две оси параллелизма обучения дословно. Data parallelism = шардинг: каждый воркер держит полную копию модели и свой кусок батча (как реалмы — независимые копии мира), синхронизация через all-reduce/parameter-server = «connected realms». Model / tensor parallelism = один шард с пространственным разрезом: одна логическая модель распилена по GPU (как вселенная EVE по узлам), а activations на границах слоёв — это межсистемные хэндоффы (дорогой кросс-девайс обмен). Когда девайс/коллектив насыщается, gradient accumulation и микробатчинг играют роль TiDi: снижаем эффективную скорость шага, чтобы остаться корректными под лимитом памяти/связи, вместо того чтобы ронять обновления. А checkpoint-каденс — ровно компромисс «save every T»: реже чекпоинтишь — теряешь до прогресса при падении узла. Шардированные KV-/feature-store для онлайн-инференса — та же партиция по ключу.
Базы данных: шардинг = горизонтальное партиционирование по ключу (consistent hashing, hot-shard problem, ресэрдинг); один-writer-на-партицию против двухфазного коммита между шардами — дословно «дюп при кросс-шардовой торговле». Дилемма WoW↔EVE — это AP-вкус (независимые партиции) против CP-вкуса (единое состояние с graceful degradation).
Распределённые системы / инфра: cell-based architecture (ячейки AWS = шарды, изоляция отказов), sticky sessions, stateful-сервис placement = размещение систем на узлах. Backpressure / load shedding (тормозить продюсера, а не терять сообщения) — это и есть TiDi: замедлить вход, сохранить корректность.
Бизнес: server merges при падении онлайна = слияние недозагруженных регионов/тенантов; цена фрагментации базы (юзеров или данных) — всегда дороже, чем кажется на старте.
Принцип: когда нагрузка превышает один узел, выбирай осознанно — порезать на независимые копии (просто, но фрагментирует) или держать единое состояние (мощно, но требует backpressure под пиком). И никогда не теряй истину молча: лучше замедлиться, чем соврать.
UPDATE a SET item=NULL и UPDATE b SET item=:x. Сначала выполни их без транзакции и «убей» процесс (Ctrl-C / kill) между ними — посмотри на состояние: предмет потерян или задвоен в зависимости от порядка. Затем оберни в BEGIN … COMMIT и повтори краш — инвариант «предмет ровно в одном месте» держится. Это вживую и есть анатомия дюпа.EVE замедляет время вместо того, чтобы докинуть железа в перегруженную систему — почему нельзя просто добавить CPU?
У WoW игроков на порядки больше, чем у EVE — почему тогда EVE считается технически сложнее?
Если один шард даёт единый мир и историю, почему не делают так все?
Дюп предметов — это же «хакеры». Почему ты называешь это инженерным багом?
Зачем вообще БД — почему не держать весь мир в RAM, раз так быстрее?
- CCP Games, девблог «Introducing Time Dilation (TiDi)» (2011) + «Time Dilation — How's That Going?» — первоисточник по механике замедления.
- «The Bloodbath of B-R5RB» (eveonline.com) — разбор крупнейшего PvP-боя; payoff единого шарда.
- Leslie Lamport, «Paxos Made Simple» (2001) — консистентность состояния между серверами БД.
- Tim Sweeney, «Network Architecture for Massively Multiplayer Games» (2001) — переход от P2P к авторитетному клиент-серверу.
- Warcraft Wiki: «Sharding», «Layering», «Cross-realm zones» — терминология дробления у theme-park MMO.
- Модуль 4 (
04-online-worlds-1997-2005.md), разделы «Database Design for Persistent Worlds» + «EVE Online's Distributed Architecture».