Multi-Region и Global Architecture
#Когда одного региона недостаточно для планеты
Вы запустили сервис. Всё работает. Сервер в us-east-1, пользователи довольны, инвесторы счастливы. Вы масштабируетесь — добавляете реплики, кэши, CDN. Всё летает.
А потом вы смотрите в метрики и видите: пользователи из Европы ждут по 800 мс. Из Австралии — 2.5 секунды. Ваш конкурент открыл офис в Сингапуре, и азиатские пользователи уходят к нему просто потому, что их приложение открывается мгновенно.
Поздравляю, вы дошли до точки, когда одного региона (region) больше не хватает.
Multi-region архитектура — это не про «крутость» и не про «масштаб». Это про физику. Скорость света — 300 000 км/с. В оптоволокне — примерно 200 000 км/с. Сигнал из Нью-Йорка в Сидней идёт ~80 мс только в одну сторону. А если ваш сервер должен сходить в базу, проверить авторизацию, загрузить данные и вернуть ответ — прощайте, sub-second загрузка для половины планеты.
В этой главе мы разберём, как строить системы, которые работают быстро для пользователей по всему миру. Как не потерять данные при отказе целого региона. И главное — когда это вообще нужно, а когда можно ограничиться CDN.
#25.1 Активная или пассивная: две философии multi-region
Первое, что вы решаете — как именно ваши регионы будут работать. Тут есть два принципиально разных подхода.
#25.1.1 Active-Passive
Один регион — основной (active). Он принимает трафик, пишет данные, делает всю работу. Второй регион — пассивный (passive или standby). Он стоит, ждёт и реплицирует данные из основного.
Нормальная работа:
Users → [Region A (active)] → Primary DB
[Region B (passive)] → Replica DB (async replication)
Сбой Region A:
Users → [Region B (promoted to active)] → Replica → PrimaryПлюсы:
- Простота. Одна точка записи — нет конфликтов данных.
- Предсказуемое поведение. Нет split-brain, нет state, который расходится.
- Значительно дешевле active-active (меньше инфраструктуры).
Минусы:
- Пассивный регион простаивает. Вы платите за инфраструктуру, которая не приносит пользы 99.9% времени.
- Переключение (failover) — не моментальное. DNS propagation, проверка целостности, promotion реплики. Реалистичный RTO (Recovery Time Objective) — от минут до часов.
- Read latency для удалённых пользователей не улучшается. Если ваш active регион в Виргинии, пользователь в Токио всё равно ждёт round-trip до Америки.
Вариация: Active-Passive с read replicas
Можно сделать пассивный регион «читающим» — отдавать из него read-only трафик для локальных пользователей, но запись по-прежнему гонять в основной регион.
Users in Asia → [Region B (replica)] → read from local replica
→ write to Region A (async)Это компромисс: read latency падает, write latency остаётся высокой. И надо быть готовым к тому, что данные в регионе B могут быть не консистентны на несколько секунд (replication lag). Для соцсети с лентой новостей — ок. Для банковского перевода — нет.
#25.1.2 Active-Active
Оба региона активны. Каждый принимает трафик, каждый пишет данные. Пользователь из Европы идёт в европейский регион, из Азии — в азиатский.
Users in EU → [Region A] → Local DB ← replication →
Users in AS → [Region B] → Local DB ← replication →Плюсы:
- Низкая задержка для всех пользователей. Каждый регион обслуживает «своих».
- Полная утилизация ресурсов. Нет простаивающих мощностей.
- Быстрое восстановление при отказе региона — просто перенаправляем трафик на живой.
Минусы:
- Конфликты записи. Если пользователь изменил данные в Европе, а через секунду другой пользователь изменил те же данные в Азии — чьи данные правильные? Вы только что открыли ящик Пандоры с conflict resolution (разрешением конфликтов).
- Значительно сложнее. Репликация данных между регионами — это не просто «сделай реплику». Это синхронизация, обнаружение конфликтов, их разрешение.
- Дороже. Нужна полноценная инфраструктура в каждом регионе.
- Сетевые задержки между регионами. Даже синхронная репликация между Нью-Йорком и Лондоном — это ~20 мс round-trip. Каждая запись будет ждать подтверждения из другого региона.
#Decision Matrix: когда что выбирать
| Критерий | Active-Passive | Active-Passive + Read Replicas | Active-Active |
|---|---|---|---|
| Write latency для удалённых пользователей | 🔴 Высокая | 🔴 Высокая | 🟢 Низкая |
| Read latency для удалённых пользователей | 🔴 Высокая | 🟢 Низкая | 🟢 Низкая |
| RTO при отказе региона | 🟡 Минуты-часы | 🟡 Минуты-часы | 🟢 Секунды |
| Сложность | 🟢 Низкая | 🟡 Средняя | 🔴 Высокая |
| Data consistency | 🟢 Strong | 🟡 Eventual (для реплик) | 🟡 Eventual / Custom |
| Стоимость | 🟡 Средняя | 🟡 Средняя | 🔴 Высокая |
| Когда выбирать | Disaster recovery, compliance | Улучшение read latency | Глобальный сервис, низкая latency |
Мой совет: начинайте с active-passive. Active-active — это уровень Google, Netflix и Slack после лет эволюции. Если вы дочитали до этой главы и думаете «нам нужно active-active» — убедитесь, что вы хотя бы пережили отказ одного региона с active-passive и поняли, почему вам нужен второй.
#25.2 Global Load Balancing — как направить пользователя в правильный регион
Допустим, у вас есть регионы в Европе, Америке и Азии. Как сделать так, чтобы пользователь из Франции попал во Франкфурт, а не в Виргинию?
Есть два основных подхода.
#25.2.1 GeoDNS (гео-маршрутизация по DNS)
Идея: DNS-сервер возвращает разный IP-адрес в зависимости от географического положения клиента.
DNS запрос из Франции:
→ myapp.example.com? → GeoDNS видит: Франция
→ Возвращает: 51.xx.xx.xx (Франкфурт)
DNS запрос из Японии:
→ myapp.example.com? → GeoDNS видит: Япония
→ Возвращает: 54.xx.xx.xx (Токио)Так работают AWS Route 53 с политикой Geolocation Routing, Google Cloud Load Balancing, Azure Traffic Manager.
Плюсы:
- Простота. Настраивается на уровне DNS, не требует изменений в приложении.
- Поддержка всеми облачными провайдерами.
Минусы:
- DNS-кэширование. Браузеры и DNS-резолверы кэшируют записи (TTL — от 30 секунд до минут). Если регион упал, вы не сможете быстро перенаправить трафик — часть пользователей будет долбиться в мёртвый IP.
- Точность GeoIP. Определение местоположения по IP — не идеально. Пользователь может сидеть через VPN, и DNS увидит его в другой стране.
- Нет балансировки по нагрузке. DNS не знает, сколько у вас свободных мощностей в каждом регионе.
Когда использовать: когда нужна простая гео-маршрутизация для сервисов, где допустима задержка переключения в 1-5 минут.
#25.2.2 Anycast (BGP anycast)
Anycast — это когда один и тот же IP-адрес анонсируется из нескольких дата-центров через BGP. Маршрутизаторы в интернете сами направляют пользователя к ближайшему дата-центру.
IP 203.0.113.42 анонсирован из:
- AS15169 (Frankfurt)
- AS15169 (Tokyo)
- AS15169 (Virginia)
Пользователь в Париже → BGP видит: Франкфурт — ближайший
Пользователь в Осаке → BGP видит: Токио — ближайшийТак работает DNS-система Cloudflare, их CDN, и большинство крупных CDN-провайдеров.
Плюсы:
- Мгновенное переключение при отказе региона — BGP пересчитывает маршруты за секунды.
- Балансировка по сети — трафик естественным образом распределяется по регионам.
- Нет проблем с DNS-кэшированием.
Минусы:
- BGP — сложно. Вам нужна своя AS (Autonomous System), IP-блок, настройка BGP-пиринга с провайдерами.
- Нет тонкого контроля. BGP решает по топологии сети, а не по реальной нагрузке на серверы.
- Anycast не работает для TCP-соединений с состоянием (stateful TCP). Если соединение установилось через Франкфурт, а маршрут изменился — пакеты пойдут в Токио, и TCP-соединение разорвётся.
Когда использовать: CDN, DNS-серверы, stateless API endpoints. Для stateful приложений — только в комбинации с Layer 7 балансировкой.
#25.2.3 Application Layer Routing
Самый гибкий, но и самый сложный подход — маршрутизация на уровне приложения или API Gateway. Вы смотрите на заголовки запроса (гео-данные от CDN, язык браузера, token пользователя) и направляете запрос в нужный регион.
# Псевдокод: глобальный API Gateway
async def route_request(request):
region = get_user_preferred_region(request)
if region_healthy(region):
return proxy_to_region(region, request)
# fallback: ближайший здоровый регион
for fallback in get_ordered_fallbacks(region):
if region_healthy(fallback):
return proxy_to_region(fallback, request)
return 503 # все регионы упалиПлюсы:
- Полный контроль. Вы знаете, какие регионы здоровы, какая у них нагрузка, какие данные доступны.
- Можно делать sticky sessions (привязывать пользователя к региону по cookie/token).
- Гранулярная маршрутизация (по пользователю, по типу запроса, по A/B эксперименту).
Минусы:
- Дополнительная инфраструктура. Нужен глобальный load balancer, который знает про все регионы.
- Дополнительная задержка. Первый запрос пользователя проходит дополнительный hop.
#Комбинированный подход (что используют в реальности)
Большинство крупных систем используют комбинацию:
1. Anycast DNS → направляет пользователя к ближайшему entry point (Edge)
2. Edge Load Balancer → проверяет здоровье регионов
3. API Gateway → принимает финальное решение о маршрутизацииCloudflare Workers, AWS Global Accelerator, Google Cloud Global Load Balancer — все они работают примерно так. Первый hop — Anycast для минимальной задержки, а внутри — Layer 7 маршрутизация с health checks.
#25.3 Database Replication между регионами: ищем консенсус
Вот где начинается настоящее веселье. Репликация данных между регионами — это не «сделайте read replica в другом регионе». Это сложная распределённая задача с компромиссами по CAP-теореме (помните главу 3?).
#25.3.1 Synchronous vs Asynchronous Replication
Синхронная репликация:
Каждая запись подтверждается только после того, как как минимум N регионов подтвердили, что записали данные.
Client → Region A (write) → wait for ack from Region B → response to client
→ wait for ack from Region C →- Плюс: Нет потери данных при отказе региона. Сильная консистентность (strong consistency).
- Минус: Задержка записи = latency до самого дальшего региона. При отказе региона — запись блокируется до таймаута.
- Компромисс: Можно настроить «полу-синхронный» режим — ждать подтверждения от одного из регионов, а не от всех. Но тогда при отказе всех синхронных реплик — переходим на асинхронный режим с риском потери.
Асинхронная репликация:
Запись подтверждается сразу в локальном регионе. Репликация в другие регионы происходит асинхронно — через CDC, log shipping, или streaming.
Client → Region A (write) → response to client (быстро!)
Region A → (async) → Region B (через 0.1-5 секунд)- Плюс: Низкая задержка записи, независимо от расстояния между регионами.
- Минус: При отказе основного региона до завершения репликации — данные потеряны.
- Реальность: Даже у Google Spanner (который «синхронный») есть async fallback для некоторых сценариев.
PACELC — практический компромисс:
Если сеть работает (normal operation) — выбираем между консистентностью и задержкой. Если сеть отказала (partition) — выбираем между доступностью и консистентностью.
| Сценарий | Synchronous | Asynchronous |
|---|---|---|
| Normal ops | Strong consistency, latency ~100ms | Eventual consistency, latency ~5ms |
| Region failure | Writes blocked until recovery 🔴 | Writes continue, possible data loss 🟡 |
| Recovery | Automatic, no data loss 🟢 | Read repair / manual reconciliation 🔴 |
#25.3.2 Технологии multi-region replication
PostgreSQL — Logical Replication
-- На мастере: публикуем изменения
CREATE PUBLICATION global_pub FOR ALL TABLES;
-- На реплике: подписываемся
CREATE SUBSCRIPTION global_sub
CONNECTION 'host=master-host dbname=mydb'
PUBLICATION global_pub;Logical replication в PG 16+ — уже вполне боевая технология. Она асинхронная, передаёт изменения на уровне строк, поддерживает фильтрацию. Но: нет автоматического разрешения конфликтов при multi-master. Если вы пишете в обе базы — вы сами отвечаете за то, чтобы не создать конфликт.
MySQL — Group Replication / InnoDB Cluster
MySQL Group Replication поддерживает multi-primary режим. Но, как и PostgreSQL, не умеет автоматически разрешать конфликты — последняя запись побеждает (last-write-wins), что может привести к потере данных.
Cassandra / ScyllaDB
Cassandra изначально спроектирована как multi-region, multi-master. У неё есть:
- Configurable consistency level (ONE, QUORUM, ALL)
- Hinted Handoff — если регион недоступен, записи хранятся локально и доставляются при восстановлении
- Read Repair — при чтении проверяется консистентность между репликами
-- Запись с консистентностью LOCAL_QUORUM:
-- Ждём подтверждения от большинства реплик в локальном регионе
INSERT INTO users (id, name, email) VALUES (?, ?, ?)
USING CONSISTENCY LOCAL_QUORUM;
-- Чтение с консистентностью QUORUM:
-- Проверяем большинство реплик во всех регионах
SELECT * FROM users WHERE id = ?
USING CONSISTENCY QUORUM;Cassandra — пожалуй, самый простой путь к multi-region active-active. Но он требует понимания trade-offs: операции с консистентностью ALL — медленные, а с ONE — могут вернуть устаревшие данные.
DynamoDB Global Tables
Amazon DynamoDB Global Tables — managed multi-region replication. Конфликты разрешаются по last-write-wins. Простота AWS managed сервиса — да, но:
- Нет синхронной репликации (только eventual consistency между регионами)
- Конфликты решаются примитивно
- Стоимость может быть высокой при большом количестве регионов
CockroachDB / YugabyteDB
PostgreSQL-совместимые distributed SQL базы с native multi-region поддержкой. Они используют Raft консенсус для синхронной репликации между регионами. Но:
- Latency записи = round-trip до самого дальнего региона
- Сложность настройки топологий (survival goals, lease preferences)
- Дороже в эксплуатации, чем managed PostgreSQL
#25.3.3 Реальность: компромиссы на практике
В реальном мире редко используют один подход для всех данных. Более типичная картина:
Глобальный сервис с multi-region:
User Data (профиль, настройки):
- Primary регион пользователя (определяется при регистрации)
- Асинхронная репликация в другие регионы для read-only
- При смене региона — миграция данных
Session Data (кэш, временные данные):
- Локальный для региона (Redis)
- Не реплицируется — потеря сессии не критична
Content / Catalog (товары, статьи):
- Изменяется редко
- Репликация + инвалидация через CDN
- Strong consistency не обязательна
Financial Data (баланс, транзакции):
- Только один регион для записи (active-passive)
- Дублируется в DR регион в реальном времени
- Read-only реплики для локального чтенияЭтот подход называется data sovereignty per entity — сущность имеет «домашний» регион, и все записи идут в него. Репликация в другие регионы — только для чтения.
#25.4 Conflict Resolution: теория и практика
Active-active — это конфликты. Их не избежать. Вопрос только: как вы их разрешаете.
#25.4.1 Last-Write-Wins (LWW)
Самый простой подход: последняя запись побеждает. Берём timestamp (или version vector), сравниваем, побеждает запись с большим значением.
class LWWRegister:
def __init__(self):
self.value = None
self.timestamp = 0
def write(self, value, timestamp):
if timestamp > self.timestamp:
self.value = value
self.timestamp = timestamp
return True
return False # отбрасываем "старую" запись
# При репликации: два региона обменялись записями
# A: значение "Привет", timestamp 1000
# B: значение "Hello", timestamp 1001
# → Побеждает "Hello" (новее)Проблема: Теряются данные. Если пользователь в Европе поменял имя, а в Азии через секунду вернул старое — данные «старого» изменения потеряны безвозвратно.
Когда это нормально:
- Тимлайны сообщений в чате
- Посты в соцсетях (важно, что опубликовано, порядок не критичен)
- Last-update timestamp
Когда это опасно:
- Финансовые данные
- Инвентаризация (два региона продали последний товар — оба думают, что он есть)
- Совместное редактирование
#25.4.2 CRDT — Conflict-Free Replicated Data Types
CRDT (Conflict-Free Replicated Data Types) — это структуры данных, которые математически гарантируют отсутствие конфликтов при слиянии. Независимо от того, в каком порядке применяются изменения — результат всегда одинаковый (convergent).
Виды CRDT:
G-Counter (Grow-only Counter):
class GCounter:
"""Счётчик, который можно только увеличивать."""
def __init__(self, node_id, num_nodes):
self.node_id = node_id
self.counts = [0] * num_nodes
def increment(self):
self.counts[self.node_id] += 1
def merge(self, other):
for i in range(len(self.counts)):
self.counts[i] = max(self.counts[i], other.counts[i])
def value(self):
return sum(self.counts)
# Region A: local count = [5, 0, 0]
# Region B: local count = [0, 3, 0]
# После merge: [5, 3, 0] → value = 8 ✅ не зависит от порядкаPN-Counter (счётчик с увеличением и уменьшением): Работает как комбинация двух G-Counter — один для инкрементов, другой для декрементов.
LWW-Register (Last-Write-Wins Register как CRDT): Да, это тоже CRDT. Берёт версию с наибольшим timestamp. Проблема та же — потеря данных. Но это предсказуемая потеря, что уже лучше.
OR-Set (Observed-Remove Set): Множество, где можно добавлять и удалять элементы. Гарантирует, что если элемент добавлен — он не исчезнет при merge, даже если в одном регионе его удалили, а в другом не знают об удалении.
class ORSet:
"""Observed-Remove Set — элементы с тегами."""
def __init__(self):
self.elements = {} # element → {tag, node_id}
def add(self, element, tag):
self.elements[element] = tag
def remove(self, element):
self.elements.pop(element, None)
def merge(self, other):
for elem, tag in other.elements.items():
if elem not in self.elements or self.elements[elem] < tag:
self.elements[elem] = tag
# Region A: add("item1", tag=1) → {"item1": 1}
# Region B: remove("item1") → {}
# Region C: add("item1", tag=2) → {"item1": 2}
# После merge всех трёх: {"item1": 2} ← тег 2 > тега 1Реальные примеры CRDT:
- Figma — использует CRDT для много-пользовательского редактирования
- Redis CRDT — Redis Enterprise поддерживает CRDT-базированную репликацию
- Automerge / Yrs — библиотеки для collaborative editing
- SoundCloud — использует CRDT для управления очередью воспроизведения
#25.4.3 Custom Conflict Resolution
Когда CRDT или last-write-wins не подходят, приходится писать свою логику.
Типы custom resolution:
-
CRDT-like для бизнес-логики:
- Вместо «последняя побеждает» — «наибольшая сумма» или «наименьшее значение»
- Например, для инвентаря: merge = min(Region A stock, Region B stock) — никогда не продадим того, чего нет
-
Manual review queue:
- Конфликты складываются в очередь, оператор решает вручную
- Используется в финансовых системах, где цена ошибки высока
- Проблема: очередь может расти быстрее, чем операторы успевают её разбирать
-
Domain-specific merge:
# Пример: календарь (Google Calendar sync) # Если встреча добавлена в Region A в 14:00, # а в Region B — в 15:00: # → Сохраняем обе встречи (CRDT-like add-only) # Если встреча в 14:00 удалена в Region A, # а в Region B изменена: # → Вероятно, пользователь не хотел удалять изменённую встречу # → Сохраняем изменённую, удаляем только исходную (OR-Set semantics) -
Application-level reconciliation:
- Некоторые системы пишут «операции» (commands), а не «состояние» (state)
- При конфликте воспроизводят операции в правильном порядке
- Это практически Event Sourcing между регионами — но требует идемпотентности (см. главу 17)
#Decision Matrix: Conflict Resolution
| Подход | Гарантии | Complexity | Потеря данных | Когда |
|---|---|---|---|---|
| Last-Write-Wins | Eventual | 🟢 Низкая | 🟡 Возможна | Некритичные данные |
| CRDT | Strong convergence | 🟡 Средняя | 🟢 Нет (для counter/set) | Совместное редактирование |
| Custom merge | Domain-specific | 🔴 Высокая | 🟢 По логике | Финансы, инвентарь |
| Manual review | Человек решает | 🔴 Очень высокая | 🟢 Нет (но долго) | Compliance-critical |
| Single-writer region | Strong | 🟢 Минимальная | 🟢 Нет | Когда можно отказаться от active-active |
#25.5 Cross-Region Failover: как тестировать то, что надеешься никогда не использовать
Failover между регионами — как огнетушитель. Он либо работает идеально, либо вы узнаёте об этом в самый неподходящий момент.
#25.5.1 Что должно произойти при отказе региона
-
Detection — как вы узнаёте, что регион упал?
- Health checks снаружи региона (если health check идёт из того же региона — он может быть неактуален при сетевом разделении).
- Synthetic monitoring из разных точек мира.
- Аномалии в метриках: резкое падение трафика, рост ошибок, timeout соединений.
-
Decision — кто принимает решение о failover?
- Автоматически или вручную?
- Анекдот: «Automated failover никогда не делают вручную, ручной failover никогда не делают автоматически».
Моя рекомендация: автоматическое обнаружение (detection), ручное принятие решения (decision). Автоматический failover — для Netflix и Google, у которых SRE нанимают ракетчиков. Для всех остальных — ручной триггер.
-
DNS Update / Traffic Drain — перенаправление трафика.
- Если используете GeoDNS — меняете DNS-записи (помните про кэширование?).
- Если Anycast — withdraw BGP-анонс упавшего региона.
- Если Application Layer Routing — обновляете конфиг route-распределения.
-
Data Promotion — делаем регион B основным.
- Для active-passive: promote реплику до primary.
- Для active-active: просто перенаправляем трафик (если здоровый регион справляется с нагрузкой обоих).
-
Verification — проверяем, что система работает.
- Smoke test критических путей.
- Мониторинг ошибок и задержек.
- Бизнес-метрики (регистрации, транзакции).
#25.5.2 Что идёт не так (мой опыт)
DNS propagation — ваш враг.
Вы переключили DNS на здоровый регион. И тут выясняется, что провайдер в Юго-Восточной Азии кэширует DNS-записи на 5 минут (хотя TTL выставлен 30 секунд). И ещё 3% пользователей упорно долбятся в мёртвый регион.
Решение: используйте короткие TTL (30-60 секунд) для critical DNS-записей. Но — короткий TTL значит больше DNS-запросов и больше расходов на DNS-серверы.
Data consistency после failover.
Вы переключили трафик на Region B. Асинхронная репликация не успела передать последние 500 записей из Region A. Region B работает, но потерял данные. Как узнавать, что Region A оживёт — и эти данные появятся?
- Проблема: Время жизни региона в отключке и объём потерянных данных — обратно пропорциональны числу седых волос у инженера.
- Решение: Pre-flight checks перед failover — проверьте replication lag. Если он слишком велик, может быть, проще подождать, чем запускать failover с потерей данных.
Cascade failure.
Регион А упал → трафик перенаправлен в регион B → регион B не рассчитан на двойную нагрузку → регион B тоже лёг. Теперь у вас outage на всей планете.
Решение: capacity planning для региона B с запасом. Регион B должен выдерживать 100% трафика (или хотя бы столько, сколько у вас активных пользователей). Если это дорого — примите, что при отказе региона сервис будет деградировать (degradation), а не падать полностью (outage).
#25.5.3 Chaos Engineering для failover
Failover надо тестировать. Не раз в год «для галочки», а регулярно.
Game Day сценарий:
- Раз в квартал — плановый failover. Перенаправляем трафик из Region A в Region B.
- Сначала в «теневом режиме» (shadow traffic) — дублируем запросы, но не переключаем реальный трафик.
- Потом с реальным трафиком — но в «тихий час» (например, 4 утра воскресенья в регионе с минимумом пользователей).
- Потом — в рабочее время. Да, страшно. Но это единственный способ проверить, что ваша команда умеет реагировать.
Chaos Monkey для multi-region: Region Monkey — инструмент, который отключает целый регион и проверяет, что failover сработал.
#25.6 Data Gravity — почему данные притягивают вычисления
Термин «data gravity» (гравитация данных) ввёл Dave McCrory в 2010 году. Идея простая: данные имеют массу. Чем больше данных — тем сильнее они притягивают приложения и сервисы.
Когда у вас данные в одном регионе, а compute в другом — каждое чтение и запись идёт через сеть. А сеть между регионами — это:
- Задержка (latency) — 50-200 ms round-trip
- Пропускная способность (bandwidth) — дороже, чем внутри региона
- Надёжность — межрегиональные соединения обрываются чаще, чем внутрирегиональные
#Data Gravity в действии
Проблема:
Data в us-east-1 (100 TB)
Compute в eu-west-1 (там живут ваши Kubernetes кластеры)
Каждый запрос: eu-west-1 → us-east-1 → eu-west-1 → клиенту
Latency: ~90 ms только на межрегиональное соединениеРешение: перенесите compute туда, где данные.
Data в us-east-1
Compute в us-east-1 (добавьте ещё один K8s кластер здесь)
Каждый запрос: us-east-1 → us-east-1 → клиенту
Latency: ~2 ms внутри регионаСледствие: нельзя полностью изолировать данные в одном регионе и при этом иметь compute, размазанный по всему миру. Данные диктуют, где живут вычисления.
#Стратегии работы с Data Gravity
-
Partition by region: разделяйте данные по «домашнему» региону пользователя.
- Пользователи из Европы → данные в Европе, compute в Европе
- Пользователи из Азии → данные в Азии, compute в Азии
- Проблема: пользователь переезжает, или работает из трёх стран
-
Replicate data globally: храните копии данных во всех регионах.
- Решает latency, создаёт проблемы консистентности
- Работает для read-heavy, редко изменяемых данных
-
Separate hot/cold data:
- Hot data (активные пользователи, последние транзакции) — реплицируется
- Cold data (архив, история) — хранится в одном регионе
- Пример: Slack хранит историю сообщений в регионе команды, а текущие — реплицирует
-
Follow-the-sun: бизнес-процессы естественным образом мигрируют за солнцем.
- Утро в Азии — batch jobs на азиатских данных
- День в Европе — аналитика на европейских данных
- Ночь — глобальные операции, которые требуют все данные
#Практическое правило
Если ваш датасет больше 10 TB — data gravity становится вашей главной архитектурной проблемой.
Для меньших датасетов можно тупо копировать данные между регионами (стоимость хранения дешевле стоимости инженерного времени на оптимизацию).
#25.7 Кейс: Google Spanner — глобальная база данных, которая не врёт
Spanner — это не просто база данных. Это ответ Google на вопрос: «Можно ли иметь ACID-транзакции с глобальной консистентностью на планете Земля?»
#Проблема
У Google есть сервисы, которые работают глобально: Google Ads, Google Photos, Gmail (ну, хотя бы метаданные), YouTube. Им нужна база данных, где:
- Транзакции ACID (а не «почти ACID»)
- Strong consistency на всех континентах
- Read latency < 10 ms для пользователя в любом регионе
- 5 nines доступности
Ни одна существующая база не удовлетворяла этим требованиям. Даже Bigtable (их собственная NoSQL) не даёт ACID-транзакций между строками.
#Как это работает
Spanner использует несколько ключевых технологий:
TrueTime API
Обычные распределённые базы используют logical clocks (Lamport clocks, version vectors) для упорядочивания событий. Это работает, но сложно и требует дополнительных протоколов для синхронизации.
Spanner использует физическое время. Он знает точное время с погрешностью (error bound) ~1-10 мс благодаря:
- GPS-приёмникам в каждом дата-центре
- Атомным часам
- Специальному протоколу синхронизации времени (Google Time Service)
// TrueTime гарантирует: текущее время ∈ [TT.now().earliest, TT.now().latest]
// Коммит транзакции:
// Если commit timestamp = T, то TT.after(T) истинно для всех мировых нод
// Это даёт external consistency — как будто работает на одной машинеPaxos между регионами
Spanner шардирует данные по узлам. Каждый шард реплицируется через Paxos (протокол консенсуса, предшественник Raft) в несколько регионов. Обычно 5 реплик, из которых 3 подтверждают запись.
Шард 1:
us-east1: Leader (Paxos)
us-central1: Follower
europe-west1: Follower
asia-east1: Follower
us-west1: Follower
Запись → идёт к Leader → Paxos подтверждает от 3 из 5
→ данные записаны глобально
→ ответ клиентуFollower Reads
Чтобы read latency была низкой для удалённых пользователей, Spanner поддерживает чтение с реплик-фолловеров. Данные могут быть слегка устаревшими, но Spanner гарантирует, что читатель увидит все данные, записанные до определённого момента времени (read timestamp).
-- Spanner SQL: читаем с ближайшей реплики
-- (может быть слегка устаревшей, но bounded staleness)
SELECT * FROM AdCampaigns
WHERE campaign_id = @id
@{
read_timestamp: timestamp
-- или exact_staleness: 5s
}#TrueTime — это не про скорость сети
Важный момент: TrueTime не делает запись быстрее. Он делает её консистентной. Запись в Spanner всё равно ждёт подтверждения от других регионов (Paxos). TrueTime просто позволяет упорядочивать транзакции по всему миру без сложных distributed locks.
Запись в Spanner с репликами на трёх континентах — это ~50-200 ms. Но вы получаете ACID, external consistency и отсутствие конфликтов.
#Когда Spanner — это overkill
Spanner — потрясающая технология. Но она не нужна 99.9% проектов.
Spanner для стартапа — это как купить Boeing 747, чтобы летать на дачу.
Если ваш датасет влезает на одну машину — PostgreSQL с streaming replication решит ваши проблемы за 1% стоимости Spanner. Если вам нужно multi-region — подумайте, может быть, CockroachDB или managed PostgreSQL с read replicas в других регионах — это то, что вам нужно.
Spanner — выбор для компаний, у которых уже есть:
- Десятки терабайт данных
- Миллионы пользователей по всему миру
- Финансовые транзакции, где цена ошибки консистентности — деньги
- Команда, которая понимает распределённые системы
#25.8 Кейс: Slack — от single-region к multi-region Active-Active
Slack — интересный кейс, потому что они прошли путь, который многие проходят: от монолита в одном регионе к глобальной архитектуре.
#Проблема
До 2019 года Slack работал из одного региона AWS — us-west-2 (Орегон). Для американских пользователей — отлично. Для европейских — задержка 100-200 мс. Для азиатских — 200-400 мс.
Slack — это real-time коммуникация. Задержка в 400 мс для сообщения в чате — это не «чуть-чуть медленнее». Это разница между «нормальный разговор» и «все говорят и перебивают друг друга, потому что не видят сообщений в реальном времени».
#Подход
Slack выбрал active-active архитектуру. Почему не active-passive?
У Slack есть две критические особенности:
- WebSocket persistence — пользователь подключается к WebSocket и держит соединение для real-time сообщений. При failover все WebSocket-соединения рвутся, и пользователи переподключаются — это заметно.
- Сообщения — это данные, которые пишутся постоянно. Каждое сообщение — запись в БД. Если все записи гонять в один регион, пользователи в Европе будут ждать 100+ мс для отправки каждого сообщения.
#Архитектура
Slack использует подход workspace locality:
У каждой рабочей области (workspace) есть «домашний» регион, определяемый при создании (или по географии большинства участников). Все данные workspace — сообщения, файлы, каналы — живут в его домашнем регионе.
Workspace из Лондона → находится в eu-west-1
- Сообщения пишутся в eu-west-1 (low latency)
- Файлы хранятся в eu-west-1
- История каналов читается из eu-west-1
Workspace из Токио → находится в ap-northeast-1
- Сообщения пишутся в ap-northeast-1
- Файлы хранятся в ap-northeast-1Но есть данные, которые глобальны: поиск по всем workspace, shared channels (каналы между разными workspace), user presence. Для них Slack использует централизованные глобальные сервисы с репликацией.
#Технологии
- База данных: MySQL + Vitess (шардинг). Vitess позволяет им шардировать данные по workspace внутри региона и реплицировать между регионами.
- Кэш: Memcached (да, они до сих пор используют Memcached, не Redis).
- Сообщения: Своя система на основе Kafka для доставки сообщений в реальном времени.
- Sync: Собственная система для асинхронной репликации данных между регионами.
#Что пошло не так
Slack честно рассказывали про проблемы на QCon:
-
Зависимости между регионами. Некоторые сервисы случайно оказывались «глобальными» — запрос из Европы неожиданно уходил в Америку для проверки лицензии/авторизации. Пришлось перепроектировать так, чтобы все зависимости были локальными или асинхронными.
-
Shared state. Наличие общих данных (user presence, shared channels) означает, что полной изоляции регионов добиться нельзя. Приходится выбирать: latency для этих данных или eventual consistency.
-
Сообщения в реальном времени. Если пользователь из workspace в Европе, но сейчас в командировке в Токио — его сообщения должны доставляться быстро, но его данные для записи находятся в Европе. Slack решили это через глобальный слой доставки сообщений (WebSocket gateway в каждом регионе), который не зависит от региона хранения данных.
-
Bootstrapping. Переезд существующего workspace из одного региона в другой — это полноценная миграция данных с даунтаймом. Slack сделали возможность переезда, но это не мгновенный процесс.
#Уроки
Слак, в отличие от многих, не строил распределённую базу данных. Они использовали существующие технологии (MySQL + Vitess) и сконцентрировались на архитектурном паттерне: локальность данных по workspace + глобальный слой для общих данных.
Это прагматичный подход. Вы не Google, у вас нет ресурсов на написание Spanner. Но workspace locality — паттерн, который применим к огромному количеству сервисов: разделите пользователей по регионам, и пусть их данные живут локально.
#25.9 Когда это применимо? Context Assessment
#Для стартапа
Не делайте multi-region. Серьёзно. Возьмите CDN для статики, Cloudflare для кэша, и если ваш бэкенд в одном регионе — это ок. Для первых 10 миллионов пользователей одного региона AWS (или вашего облачного провайдера) достаточно.
Исключение: ваш рынок с первого дня — глобальный, и вы конкурируете за latency-sensitive пользователей (real-time gaming, trading). Но даже тогда — active-passive с read replicas, не active-active.
#Для средней компании
- Active-passive обязателен для DR (Disaster Recovery). Если ваш бизнес зависит от доступности — план восстановления в другом регионе должен быть.
- Read replicas в стратегических регионах — для улучшения user experience.
- Data gravity — начните замечать, что данные «притягивают» compute.
#Для enterprise / high-load
- Active-Active — если у вас глобальная аудитория и latency — конкурентное преимущество.
- CRDT для конфликтующих данных — если не можете терпеть last-write-wins.
- Chaos engineering для failover — регулярные game days.
- Data sovereignty — разделение данных по региональным требованиям (GDPR, местные законы).
#Уровень нагрузки
| Нагрузка | Multi-region |
|---|---|
| Low (< 1K RPS) | Не нужен |
| Medium (1K-50K RPS) | Active-Passive DR |
| High (50K-500K RPS) | Active-Active (если глобально) |
| Extreme (> 500K RPS) | Multi-region обязателен + sharding внутри регионов |
#25.10 Чек-лист: Готовы ли вы к multi-region?
- У вас есть измеренная проблема с latency для удалённых пользователей (цифры, не ощущения)
- Вы понимаете RPO (Recovery Point Objective) и RTO (Recovery Time Objective) для вашего бизнеса
- У вас настроен мониторинг и alerting, который отличает «регион упал» от «нагрузка выросла»
- Вы протестировали failover хотя бы раз (не в презентации, а реально — с отключением региона)
- Ваши сервисы stateless (или state вынесен в Redis/БД) — без этого multi-region не имеет смысла
- Вы знаете свои конфликтующие данные и выбрали стратегию resolution (LWW, CRDT, custom)
- У вас есть capacity planning — второй регион выдержит 100% трафика при отказе первого
- DNS TTL настроены минимально (30-60 секунд для critical записей)
- Вы понимаете свою data gravity — какие данные где живут и почему
- CI/CD pipeline умеет деплоить в оба региона одновременно (или хотя бы последовательно)
- У вас есть план миграции пользователей/данных между регионами (кто-то переезжает)
- Вы проверили compliance требования — можно ли данные европейских пользователей хранить в США?
#25.11 «Не делай так»: Active-Active без понимания конфликтов
Это не анти-паттерн. Это архитектурное самоубийство. И я видел это своими глазами.
#История
Стартап, B2B SaaS. Решили покорять мир. «Нам нужен active-active прямо сейчас» — сказал CTO. «Пользователи в Европе жалуются на тормоза».
Архитектор нарисовал красивую схему: два региона, оба активные, репликация через CDC, всё на PostgreSQL multi-master.
Первый месяц — всё прекрасно. Сайт летает, пользователи счастливы.
Второй месяц — начинаются странности. Товар, которого нет в наличии, продаётся дважды — в разных регионах. Один пользователь меняет email, а через минуту система показывает старый. Заявки тасуются — в Европе создали заявку, в Америке её закрыли, а в Европе она висит как открытая.
Третий месяц — аврал. База данных в рассинхроне. PostgreSQL multi-master не умеет разрешать конфликты «из коробки» — он просто применяет записи в порядке поступления, и последняя запись побеждает. Результат: в таблице inventory для одного товара — остаток «5» в Европе и «-2» в Америке. Заказы, которые нельзя выполнить. Клиенты, которые заплатили за товар, которого нет.
Четвёртый месяц — CTO уволен. Система переведена в single-region. Ещё полгода ушло на восстановление консистентности данных.
#Почему это произошло
Три ошибки, которые убивают active-active:
-
«PostgreSQL multi-master решит все проблемы» — нет, не решит. Реляционные базы не спроектированы для multi-master консистентности. Они используют last-write-wins, потому что ничего лучше не умеют «из коробки».
-
«Напишем свою conflict resolution на коленке» — если у вас нет команды, которая понимает распределённые системы и CRDT — ваша самописная система будет хуже, чем last-write-wins.
-
«Active-active для всего» — не нужно делать active-active для всех данных. Read-only данные, кэш, некритичные профили — да. Финансы, инвентарь, заказы — категорически нет без проработанной стратегии конфликтов.
#Правило большого пальца
Если вы не можете ответить на вопрос «что произойдёт с данными, если два пользователя одновременно изменят одну запись в разных регионах?» — у вас проблемы с active-active.
#Что делать вместо этого
- Active-passive с read replicas — 90% пользы при 10% сложности.
- Active-active для read-only данных — replicate, cache, CDN. Пишите в один регион.
- Workspace locality — как Slack. Разделите пользователей по регионам. Внутри региона — всё просто (single-master).
- CRDT-only для конфликтующих данных — используйте готовые библиотеки (Redis CRDT, Cassandra, или CRDT библиотеки в приложении).
И помните: один здоровый регион лучше, чем два рассинхронизированных. Multi-region начинается не с «крутости». Он начинается с боли — когда пользователи реально страдают от латентности, когда отказ одного дата-центра угрожает бизнесу, когда требования регуляторов не позволяют хранить данные удалённо.
Не делайте multi-region «на вырост». Делайте, когда вырастете.
#Ключевые выводы
-
Active-passive — дефолт. Active-active — только если вы готовы платить за сложность деньгами, временем и седыми волосами инженеров.
-
Гео-маршрутизация начинается с DNS (GeoDNS) и Anycast. Для stateless сервисов — Anycast (CDN, кэш). Для stateful — GeoDNS + application layer routing.
-
Межрегиональная репликация данных — это выбор между консистентностью и задержкой (PACELC). Синхронная репликация консистентна, но медленна. Асинхронная — быстра, но риск потери данных.
-
Конфликты неизбежны в active-active. CRDT — математический способ гарантировать конвергенцию данных без потерь. Но не все данные можно представить в виде CRDT.
-
Data gravity реальна. Чем больше ваши данные, тем сильнее они привязывают compute к региону. Разделяйте данные по «домашнему» региону пользователя (workspace locality).
-
Failover нужно тестировать. Раз в квартал — плановое переключение региона. Раз в год — chaos engineering с имитацией отказа.
-
Multi-region — это не про деньги в банке. Это про физику, математику и дисциплину. Начинайте с простого, усложняйте только когда боль от текущего решения становится сильнее боли от внедрения нового.
Forward reference: AI-агенты уже начинают использоваться для автоматической маршрутизации трафика и самовосстановления систем в multi-region архитектуре. Как AI меняет подход к глобальной инфраструктуре — в Главе 38 «Будущее — архитектор + AI».