← Модуль 4/Персистентность и шардинг
EN
Модуль 4 · Онлайн-миры (1997–2005)

Персистентность и шардинг

Авторитетный сервер — это где живёт истина. Но когда игроков тысячи, истина не помещается на один процесс. Два ответа эпохи: порезать игроков (шардинг — тысячи независимых копий мира, как реалмы WoW) или порезать пространство (один мир, разнесённый по узлам кластера, как EVE) — и заплатить за это замедлением времени под нагрузкой.
~17 мин☁ инфра + БД
Суть за 30 секунд
Персистентный мир = авторитетное состояние, которое переживает разрыв связи, краш и патч и обслуживает тысячи мутаций в секунду. Архитектура двухслойная: горячий авторитетный симулятор в RAM (тик за тиком) + холодный durable-store (реляционная БД), куда мир периодически «сохраняется». Один процесс не тянет 100k игроков, поэтому масштабируют двумя способами. Шардинг (WoW): порезать популяцию — N независимых копий мира, каждая со своей БД (ключ партиционирования = реалм). Линейно масштабируется железом, но фрагментирует социальный граф: игроки разных реалмов друг друга не видят. Один шард (EVE «Tranquility»): порезать пространство — одна вселенная, разбитая на солнечные системы, каждая назначена узлу кластера; все взаимодействуют по всему миру, но пропускная способность любой системы упёрта в один узел. Когда в одну систему набивается 2670 пилотов (как в B-R5RB, 2014), узел захлёбывается → Time Dilation: замедлить симуляцию вплоть до 10% реального времени, чтобы обработать все действия по порядку. Это backpressure: меняем скорость на корректность. Цена шардинга — раздробленный мир; цена одного шарда — героическая инженерия и «булет-тайм» в большом бою.

Механизм: где физически лежит истина

Сетевая модель MMO — авторитетный клиент-сервер (см. таксономию): сервер — единственный источник правды, клиент только предсказывает и рисует. Этот урок про следующий вопрос: на каком железе живёт эта правда, когда онлайн — десятки тысяч, а персонажей в базе — миллионы, и любое состояние обязано пережить выключение клиента.

Два слоя: горячий симулятор и холодный store

Состояние мира расслаивается надвое:

Мир периодически сохраняется: горячее состояние сериализуется в холодное (на логауте, по таймеру, при важных транзакциях). Между сохранениями durable-копия отстаёт от живой. Отсюда — главный класс багов эпохи: откаты и дюпы предметов при крахе, если границы сохранения и транзакций неаккуратны.

Сохранение, окно отката и дюп

Пусть мир флашится в БД каждые T секунд. Краш узла между флашами теряет до T секунд прогресса — игрок логинится «в прошлом» (классический «ролбэк сервера»). Ожидаемая потеря при равномерном кэше:

E[Lloss] ≈ T2 ; T=300 с ⇒ в среднем 2.5 мин теряется

Дюп опаснее отката, потому что разрушает экономику. Передача предмета — это две мутации: списать у A, начислить B. Если они не атомарны и относительно границы флаша пересеклись с крашем, при восстановлении предмет может оказаться и у A (откатился к состоянию «до передачи»), и у B (начисление успело лечь) — он удвоился. Лечится не «античитом», а тем, что торговля — это ACID-транзакция над durable-store, а не ленивый write-behind двух независимых блобов персонажей.

Один процесс не тянет — два способа резать

Узел упирается в CPU/память/БД задолго до 100k игроков. Развилка масштабирования:

Шардинг (WoW): режем популяцию мир A БД A мир B БД B мир C БД C реалмы независимы · ключ = реалм A и C друг друга НЕ видят Один шард (EVE): режем пространство узел узел узел узел узел узел системы вселенной → узлы одна БД · один мир Перегруз одной системы: 2670 пилотов на один узел очередь действий растёт → лаг Time Dilation: тянем тик-часы, пока всё не влезет 100% 50% 10% (пол) — булет-тайм скорость симуляции s падает с ростом нагрузки; все действия обрабатываются по порядку и честно, мир просто идёт в slow-motion. Цена ≠ корректность.

Шардинг: режем популяцию

Шардинг — горизонтальное партиционирование игроков: мир копируется в N независимых экземпляров (реалмы / серверы), каждый со своим авторитетным состоянием и своей БД. Это буквально шардинг базы данных, где ключ партиционирования = реалм. WoW так и устроен: на экране выбора персонажа ты выбираешь реалм — то есть раздел БД. Плюсы: линейное масштабирование (нужно больше места — поднял ещё реалм), отказ одного реалма не роняет остальные, маленькая «поверхность» консистентности. Минус один, но тяжёлый: социальный граф раздроблен — друзья на разных реалмах не могут торговать и ходить в рейды; перенос персонажа = платная миграция строки между БД. Современные смягчения: connected realms, cross-realm зоны (CRZ — наоборот, сливают недонаселённые реалмы, чтобы зона «жила»), data-центры FFXIV с «World Visit».

Один шард: режем пространство

EVE пошла против течения: одна вселенная для всех (сервер «Tranquility»), партиционирование не по популяции, а по пространству. Вселенная нарезана на ~8000 солнечных систем; каждая система в любой момент «принадлежит» одному узлу (процессу) в большом кластере. Игроки взаимодействуют через весь мир — один рынок, одна история, — но пропускная способность отдельной системы упёрта в один узел: бой в системе нельзя «распараллелить» на два CPU, потому что все корабли в нём взаимодействуют друг с другом (это не embarrassingly parallel). Поэтому единственный рычаг под перегрузом — скорость хода времени.

Time Dilation: backpressure через замедление времени

EVE крутит серверный тик частотой 1 Гц (симуляция продвигается на 1 секунду за тик). Пусть узлу нужно за «секунду игры» обработать работу W, а его реальная мощность за секунду стены — C. Загрузка L=W/C. Пока L<1 — порядок; на подходе к 1 очередь действий по теории очередей растёт катастрофически:

Qavg ≈ L1−L (L→1⇒Qavg→∞)

Раньше это и был «лаг смерти» больших боёв: L≥1, очередь не разгребается, узел умирает. TiDi (2011) вводит коэффициент дилатации s: симуляционное время идёт со скоростью s относительно реального. Тогда за секунду стены узлу надо сделать лишь s·W работы. Ставим:

s= max(0.1, min(1, CW ))

Эффективная загрузка Leff=s·W/C≤1 — очередь снова ограничена, и всё обрабатывается по порядку и честно. Пол s=0.1 (10%) гарантирует, что бой когда-нибудь закончится: 1 секунда симуляции за 10 секунд стены. TiDi не трогает EVE-серверное время (индустрия, обучение скиллов, таймеры структур идут по реальным часам) — тянется только «физика в космосе».

Числовой пример — почему B-R5RB шёл 21 час

В «Кровавой бане B-R5RB» (январь 2014) в одной системе одновременно было до 2670 пилотов (всего вовлечено 7548), 21 час, погибло 576 кэпиталов включая 75 «Титанов», ~11 трлн ISK (≈$300–330k реальных). Пусть узел чисто тянет действия ~250 кораблей в реальном времени (иллюстративно, не публичная цифра CCP). Тогда:

L= 2670250 ≈10.7 ⇒ s=max(0.1,1/10.7) =0.1

Загрузка вшестеро-вдесятеро выше единицы → s прижат к полу 10%. Бой буквально идёт в одну десятую скорости — отсюда «булет-тайм», в котором залп Титана летит минутами, а 21 час реального времени вмещает лишь несколько часов «игровой» битвы. Без TiDi узел бы просто умер; с TiDi сражение медленно, но детерминированно и справедливо доигралось. Это и есть payoff одного шарда: B-R5RB — реальное историческое событие, а не «эпизод на сервере №47», потому что вселенная одна.

🕹 В какие игры поиграть — и что заметить

Архитектуру персистентности видно невооружённым глазом — на экране выбора сервера (или его отсутствии). Сыграй по нарастающей: от явного «выбери шард» до «шарда нет, есть один мир, который умеет замедляться».

World of Warcraft шардинг по реалмам

Эталон «режем популяцию». При создании персонажа ты выбираешь реалм — это и есть выбор раздела БД. Внутри реалма зона ещё дробится (sharding зоны при наплыве, layering целого реалма на старте Classic 2019, позже убрали).

🎮 Сыграй: создай персонажей на двух разных реалмах — заметь, что они в отдельных вселенных: не видят друг друга, не торгуют, общий только аккаунт. Глянь цену «Character Transfer» в магазине Blizzard — ты платишь за миграцию строки между базами. Это цена шардинга, выписанная в долларах.

RuneScape / Lineage / большинство MMO явный «world select»

Та же модель, ещё нагляднее: список «миров»/«серверов» с индикатором населённости прямо в лобби. Каждый мир — независимый шард; популярные забиты, пустые стоят.

🎮 Сыграй: в RuneScape открой список миров — десятки пронумерованных шардов с счётчиком игроков. Зайди в мир с низким онлайном и в забитый: экономика, цены на Grand Exchange и людность ощутимо разные, потому что это разные миры, склеенные лишь общим аккаунт-сервисом.

Final Fantasy XIV мегасервер · data-центры · World Visit

Современный компромисс. Логические «миры» сгруппированы в data-центры; внутри мира зоны инстансируются (шардинг под капотом), но между мирами одного DC можно гостить (World Visit). Социальный граф уже не так раздроблен, как у классического WoW.

🎮 Сыграй: в FFXIV сделай World Visit на соседний мир своего data-центра — заметь, что инстанс-зоны (Limsa, рынки) распадаются на «дубли» под нагрузкой, и при этом ты физически переехал на другой мировой процесс. Гибрид: режут и популяцию (миры), и пространство (инстансы).

EVE Online один шард Tranquility + TiDi

Противоположный полюс: ты вообще не выбирал сервер. Одна вселенная, один рынок, одна история на всех. Цена — 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, БД флашится пачками по таймеру/на логауте. Быстро, но открывает окно отката T и требует осторожности с порядком записи.

Поэтому делят по важности: позиция/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 среднее число в системе N=ρ/(1−ρ), а среднее время в системе по Литтлу N=λ·W. При ρ=0.9 в системе ~9 заявок; при ρ=0.99 — ~99. Вот почему лаг больших боёв не линеен по числу игроков: пока есть запас — почти незаметно, у границы — обрыв. TiDi масштабирует эффективное поступление: замедляя симуляционные часы в s раз, мы делим скорость, с которой действия «созревают» к обработке, удерживая ρeff≤1.

Почему именно пол, а не «сколько надо»

Без пола под экстремальной нагрузкой s ушёл бы в ~0 — бой не закончился бы никогда, и узел всё равно копил бы джиттер. Жёсткий пол s=0.1 гарантирует прогресс: даже в худшем случае 1 секунда боя проходит за 10 секунд стены. Это сознательный размен: при запредельной нагрузке узел может всё-таки начать отставать даже на полу (тогда действия буферизуются), но игра остаётся когерентной и честной — никто не «телепортируется», порядок действий сохранён. Backpressure вместо потери данных.

Аналогия
Сетевой ресторан. Шардинг — открыть 50 одинаковых филиалов: каждый со своей кухней и кассой, очередь делится, масштабируется тривиально — но твои друзья из «филиала B» физически не могут сесть за твой столик в «филиале A», и перевод брони между филиалами — отдельная процедура. Один шард — один гигантский ресторан на всех: можно встретить кого угодно, одно меню, одна история заведения. Но когда в один зал набивается толпа, официанты не успевают — и заведение включает «замедленное время»: обслуживает всех по очереди, честно, просто медленнее, чтобы никого не потерять и не перепутать заказы. Никто не уходит без еды; вечер просто тянется.
Почему это важно
Персистентность — это место, где геймдев упирается в чистую распределённую систему: где живёт авторитетная истина, как она переживает отказ, и что делать, когда нагрузка превышает один узел. Выбор «резать популяцию или резать пространство» — не про графику, а про то, каким миром игра вообще может быть: дробный и легко масштабируемый против единого и дорогого. И TiDi — редкий честный ответ на вопрос «что делать, когда железо физически не успевает»: не врать клиенту, не терять действия, а замедлить время и сохранить корректность. Тот же выбор — шардировать ради простоты или держать единое состояние ценой героизма — стоит перед любым, кто строит системы за пределами игр.
🔁 За пределами игр — куда это переносится
Урок — это два каноничных приёма распределённых систем плюс backpressure, показанные на живом примере.

ML / AI: это две оси параллелизма обучения дословно. Data parallelism = шардинг: каждый воркер держит полную копию модели и свой кусок батча (как реалмы — независимые копии мира), синхронизация через all-reduce/parameter-server = «connected realms». Model / tensor parallelism = один шард с пространственным разрезом: одна логическая модель распилена по GPU (как вселенная EVE по узлам), а activations на границах слоёв — это межсистемные хэндоффы (дорогой кросс-девайс обмен). Когда девайс/коллектив насыщается, gradient accumulation и микробатчинг играют роль TiDi: снижаем эффективную скорость шага, чтобы остаться корректными под лимитом памяти/связи, вместо того чтобы ронять обновления. А checkpoint-каденс — ровно компромисс «save every T»: реже чекпоинтишь — теряешь до 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 под пиком). И никогда не теряй истину молча: лучше замедлиться, чем соврать.

🔧 Запусти и поковыряй — на домашнем компе
Во что поиграть — выше (🕹). Здесь — для тех, кто хочет потрогать саму границу горячего и холодного слоя руками.
🔧 Поковырять (debug) ~40 мин, локальный Postgres
Подними локальный Postgres/SQLite. Смоделируй «торговлю» двумя апдейтами: UPDATE a SET item=NULL и UPDATE b SET item=:x. Сначала выполни их без транзакции и «убей» процесс (Ctrl-C / kill) между ними — посмотри на состояние: предмет потерян или задвоен в зависимости от порядка. Затем оберни в BEGIN … COMMIT и повтори краш — инвариант «предмет ровно в одном месте» держится. Это вживую и есть анатомия дюпа.
🧪 Потестить (глазами QA) ~15 мин, EVE / стрим
Найди в EVE (или на стриме осады) систему под нагрузкой и подебажь TiDi глазами: при каком числе пилотов индикатор уходит с 100%? Доходит ли до пола 10%? Останавливаются ли при этом таймеры скиллов/структур (не должны — это EVE-время, не дилатируется)? Затем мысленный QA на хэндофф: пилот летит из системы A (узел 1) в B (узел 2) — какой инвариант не даёт кораблю существовать в обеих сразу во время передачи?
Чеклист: воспроизвёл дюп без транзакции и починил ACID-обёрткой; увидел падение TiDi к 10% и проверил, что серверное время не дилатируется; сформулировал инвариант передачи владения при хэндоффе узла.
Связи
основа
Таксономия нетокода — авторитетный клиент-сервер: что кладём на провод и кто источник правды. Этот урок — где эта правда физически лежит и как переживает масштаб.
основа
Клиент-предсказание — авторитетный сервер вблизи; персистентность — та же авторитетность, сделанная durable и размазанная по кластеру.
контраст
Роллбэк-нетокод — P2P без авторитета, крошечное эфемерное состояние, сейв каждый кадр для отката вперёд; здесь — авторитет, огромное durable-состояние, сейв ради выживания мира. Та же «частая сериализация» — ради противоположных целей.
дальше
Виртуальные экономики — единый шард EVE — это и есть то, что делает возможным один рынок и измеримую экономику; шардинг же дробит экономику на N независимых.
Вопросы пытливого ума
EVE замедляет время вместо того, чтобы докинуть железа в перегруженную систему — почему нельзя просто добавить CPU?
Потому что одна солнечная система — это один узел, а бой в ней не embarrassingly parallel: все корабли стреляют друг в друга, применяют модули, считают дальности — состояние плотно связано. Распилить эту систему на два CPU значит гонять между ними почти всё состояние каждый тик (кросс-talk сожрёт выигрыш) и решать консистентность в реальном времени. Дешевле и корректнее оставить систему на одном узле и крутить единственный безопасный рычаг — скорость хода времени. «Добавить железа» работает между системами (их раскидывают по узлам), но не внутри одного боя.
У WoW игроков на порядки больше, чем у EVE — почему тогда EVE считается технически сложнее?
Потому что у WoW никогда не бывает 7000 человек, которые могут стрелять друг в друга в одном согласованном состоянии. WoW — это тысячи маленьких независимых миров (реалмов), плюс шардинг зон: нагрузка дробится по построению. EVE сознательно выбрала один мир — и тем самым подписалась на задачу, которую WoW обходит архитектурой: держать единое авторитетное состояние с тысячами взаимодействующих сущностей в одной точке. Больше суммарных игроков ≠ сложнее; сложнее — когда они все в одной консистентной симуляции.
Если один шард даёт единый мир и историю, почему не делают так все?
Потому что фрагментация социального графа для theme-park MMO — это фича, а не баг: реалмы держат население плотным и управляемым, а контент-дрип — отмеренным. WoW-дизайнеру единый мир не покупает ничего (контент всё равно инстансированный и сценарный), но стоит TiDi-инженерии и дизайна, терпящего «булет-тайм». Один шард окупается только там, где единая песочница, эмерджентная политика и один рынок — сама суть игры (EVE). Это выбор продукта, навязывающий выбор архитектуры, а не наоборот.
Дюп предметов — это же «хакеры». Почему ты называешь это инженерным багом?
Потому что подавляющее большинство исторических дюпов — нарушенная атомарность транзакции на стыке горячего RAM-состояния и холодной БД, а не пробитая криптография. Игрок лишь провоцирует нужный тайминг (дисконнект/краш/двойной клик в момент передачи), а собственно удвоение делает сама система, переигрывая лог в «удобном» порядке или персистя две независимые мутации частично. Поэтому фикс — не «банить читеров», а сделать торговлю одной ACID-транзакцией с идемпотентностью. Это ровно класс багов распределённых транзакций, просто в антураже инвентаря.
Зачем вообще БД — почему не держать весь мир в RAM, раз так быстрее?
Из-за durability и объёма. Durability: процессы падают, серверы перезагружают на патчи, дата-центр может моргнуть — без durable-store любой такой момент стирает прогресс всех. Объём: горячими можно держать только онлайн-игроков в загруженных зонах; миллионы офлайн-персонажей со всем инвентарём в RAM не влезут и не нужны там. Поэтому расслоение обязательно: горячий слой — кто сейчас играет, холодный — истина обо всех. Вопрос лишь в каденсе и атомарности перехода между ними — там и живут откаты с дюпами.
Что почитать