Эволюция архитектуры — истории рефакторинга
Реальные истории трансформации архитектуры. Без прикрас, без «и жили они долго и счастливо». Только то, что реально пошло не так, и то, что реально сработало.
#Введение: почему мы учимся только на своих ошибках
Есть старый анекдот про архитектора, который говорит джуниору: «Ты будешь делать те же ошибки, что и я. Просто будешь делать их на 50% быстрее, потому что я тебе их покажу».
С рефакторингом архитектуры — та же история. Каждое поколение разработчиков проходит через одни и те же грабли. Monolith → микросервисы → обратно в монолит. Rewrite → disaster → «надо было рефакторить». On-premise → cloud → «а почему счёт такой огромный?».
В этой главе — пять реальных историй. Имена изменены, но я ручаюсь, что каждая из них произошла в реальной компании (а часто — не в одной, а в десятках). Я сам участвовал в некоторых из них. Остальные собраны из десятков разговоров с инженерами на конференциях, в подкастах и в личной переписке.
Почему это важно? Потому что архитектурная мудрость — это не знание паттернов. Это знание того, где эти паттерны ломаются. И единственный способ это узнать — либо наступить на грабли самому, либо прочитать, как на них наступили другие.
Эта глава — про второе.
#История 1: Monolith → Microservices → Modular Monolith — что пошло не так и почему вернулись
#Компания N: продуктовый стартап, ставший "единорогом"
Давайте представим компанию (назовём её «N»). В 2015 году это был типичный стартап: команда из 15 разработчиков, монолит на Rails (или Django — неважно), одна база данных PostgreSQL, всё задеплоено на пару EC2-инстансов. Продукт растёт, пользователей становится всё больше, команда — тоже.
Где-то в 2017 году CTO читает Мартина Фаулера. Смотрит на Netflix. Читает Убера. И принимает решение: пора на микросервисы. Ведь «монолит не масштабируется», «мы не можем деплоить независимо», «команды слишком зависят друг от друга».
Знакомо? Поехали.
#Шаг 1: Эйфория (месяцы 1-6)
Выделяется команда «платформы». Они пилят новую архитектуру: Kubernetes, Docker, service mesh, CI/CD для каждого сервиса. Старый монолит решено «распилить» на 6-8 сервисов: Users, Orders, Payments, Notifications, Search, Analytics.
Первые сервисы выходят быстро. Users-сервис — отдельная база, отдельный деплой. Команда ликует. Графики на дашбордах красивые. На all-hands meeting CTO рассказывает, как они «перешли на современную архитектуру».
#Шаг 2: Боль (месяцы 7-18)
А потом начинается.
Проблема 1: Транзакции. Раньше заказ создавался в одной транзакции: списать товар со склада, создать заказ, списать деньги, отправить уведомление. В монолите это было
BEGIN TRANSACTION ... COMMIT. Теперь это — вызов Users → Orders → Payments → Notifications через HTTP. Что если Payments упал, а Orders уже создан? Что если Notifications ответил timeout, а деньги уже списаны?Появляется Saga. Сначала Choreography, потом Orchestration. Kafka. Dead letter queues. Retry logic with exponential backoff. За месяц команда платформы пишет больше кода для инфраструктуры, чем для бизнес-логики.
Проблема 2: Data consistency. Раньше JOIN между Users и Orders делался в один запрос. Теперь данные раскиданы по разным базам. Хочешь показать «заказы пользователя» — делай два запроса: сначала в Users (получить user_id), потом в Orders (получить заказы). А если хочешь «все заказы с именем пользователя» — это уже batch job.
Появляется CQRS. Eventual consistency. Все говорят умные слова, но пользователи жалуются, что их заказы иногда «исчезают» на пару секунд.
Проблема 3: Команды. Оказалось, что 15 разработчиков не могут поддерживать 8 микросервисов. Каждый сервис требует своего CI/CD, своего мониторинга, своего logging, своей документации. Онбординг нового разработчика вырос с 2 дней до 2 недель.
Проблема 4: Debugging. Раньше ошибка была в логах одного процесса. Теперь trace проходит через 5 сервисов, и каждый пишет в свой лог. Без distributed tracing (Jaeger/Zipkin) ты слеп. Но distributed tracing — это ещё один кусок инфраструктуры.
#Шаг 3: Прозрение (месяцы 19-24)
Через полтора года команда «N» смотрит на то, что они построили. У них не микросервисы. У них распределённый монолит (distributed monolith).
Симптомы классические:
- Изменение в Orders требует деплоя Payments и Notifications
- Shared database между сервисами (потому что «временное решение»)
- Синхронные HTTP-цепочки: check-order → check-inventory → check-payment
- Покрытие тестами упало, потому что интеграционные тесты между сервисами — это ад
Самое обидное: скорость разработки не выросла. Она упала. Фичи, которые в монолите делались за 2 дня, теперь делаются за неделю.
#Шаг 4: Возвращение (месяцы 25-36)
Решение, к которому приходит команда — модульный монолит (modular monolith). Они объединяют код обратно в один процесс, но сохраняют доменные границы (bounded contexts), которые проработали во время микросервисной эпопеи.
Как это выглядит:
- Один процесс, один деплой
- Чёткие модули (в Java — modules, в Node.js — packages с явными зависимостями)
- Модули общаются через вызовы в памяти, не через HTTP
- Чёткие контракты между модулями
- Одна база данных, но модули не имеют прямого доступа к таблицам друг друга
Результат:
- Скорость разработки вернулась на уровень «до микросервисов»
- Онбординг — 3 дня вместо 2 недель
- Тестирование снова стало простым
- Инфраструктурная сложность упала на 70%
#Что на самом деле произошло?
Компания N не зря прошла этот путь. Микросервисы заставили их:
- Чётко определить границы доменов (bounded contexts)
- Научиться управлять контрактами между модулями
- Понять, что не всё нужно делать распределённо
Парадокс: они стали архитектурно грамотнее, хотя технологически вернулись туда, откуда начали.
Когда это нормально? Когда у вас одна команда (или несколько колокейтированных команд) и вы не имеете проблем с масштабированием на уровне кода. Модульный монолит — отличное решение для 90% случаев.
Когда микросервисы всё-таки нужны? Когда у вас есть реальная проблема, которую решает микросервисная архитектура:
- Разные команды должны деплоить независимо (по-настоящему независимо)
- Разные части системы имеют радикально разные требования к масштабированию
- Вы используете разные технологические стэки для разных задач (и это оправдано)
Но не делайте ошибку «N»: микросервисы — это решение про команды и организацию, а не про масштабирование.
#История 2: Startup → IPO → Enterprise — как архитектура растёт с компанией
#Компания S: от гаража до публичной компании
Компания «S» запускается в 2012 году. Два основателя, один контрактник, PHP + MySQL на shared hosting. Код — спагетти, но он работает. Продукт набирает пользователей.
2014 год: раунд A, 15 человек. Код переезжает на выделенные серверы. Появляется первый DevOps. База шардируется. Добавляется Redis для кэша.
2016 год: раунд C, 80 человек. Начинаются проблемы.
#Проблемы роста (3 стадии)
Стадия 1: «А король-то голый» (15-50 человек)
На этой стадии компания осознаёт, что их архитектура не выдерживает критики.
Кодовая база — Big Ball of Mud. Одна модель User содержит 80 полей, потому что все фичи добавляли поля в одну таблицу. Один контроллер обрабатывает и аутентификацию, и платёжные уведомления, и админку.
Типичный день разработчика: «Я добавил поле в модель User, и упал тест в модуле Payments. Почему? Потому что Payments использовал User.email из той же модели, а я поменял формат email на username. Как они связаны? Не знаю. Теперь сижу и разбираюсь.»
Что обычно делают: начинают наводить порядок. Выделяют отдельные модули. Вводят code review. Перестают «коммитить в master сразу». Появляется первый архитектор (обычно это самый опытный разработчик, которого повысили «за заслуги»).
Стадия 2: «Мы выросли из штанишек» (50-200 человек)
Продукт стабильно растёт. Команда разделяется на несколько: Platform, Payments, Mobile, Web. Каждая команда хочет двигаться с разной скоростью.
Проблема в том, что код всё ещё общий. Web-команда не может релизить, потому что Platform-команда сломала API. Platform-команда не может рефакторить, потому что все зависят от их кода.
Что делает компания S: в 2017 году они принимают болезненное решение — выделять сервисы. Не потому что «микросервисы модно», а потому что организационно не могут иначе. Закон Конвея бьёт в полную силу.
Выделение идёт постепенно:
- Сначала отделяют Payments (регуляторные требования — отдельная база)
- Потом Auth (все сервисы нуждаются в авторизации)
- Потом Search (отдельный Elasticsearch кластер)
- Потом Analytics (отдельный data pipeline)
Ключевое отличие от Истории 1: они не делают «big bang» миграцию. Они выделяют сервисы по одному, когда это реально необходимо.
Стадия 3: «Мы больше не стартап» (200-1000+ человек)
2020 год. Компания S готовится к IPO. Теперь они — enterprise.
Новые проблемы:
- Compliance. SOX, SOC2, GDPR. Каждое архитектурное решение теперь проверяется аудиторами.
- Multi-team dependencies. 15 команд, каждая со своим роадмапом. API-контракты согласовываются неделями.
- Legacy. Код 2012 года всё ещё работает. Никто не хочет его трогать, но он тянет всю архитектуру назад.
#Что реально сработало
1. API Platform как внутренний продукт
Компания S создала внутреннюю платформу API Gateway, которая стала единой точкой входа для всех сервисов. Это не было технически гениальным — это было организационно необходимо. Когда 15 команд общаются через HTTP, нужен единый стандарт: аутентификация, rate limiting, версионирование, документация.
2. Architecture Decision Records (ADR)
Каждое архитектурное решение — от «почему мы взяли PostgreSQL, а не MySQL для нового сервиса» до «почему мы не используем Kafka для этого сценария» — документируется в ADR. Не ради процесса, а ради ответа на вопрос «почему мы сделали это так» через полгода.
3. Feature flags как способ раскатки
Компания S вложилась в feature flag систему (сначала самописную, потом на LaunchDarkly). Это позволило раскатывать фичи без блокировок: включил флаг → тестируем → выключил → фиксим → включил снова. Без этого релизы превратились бы в ад из-за cross-team dependencies.
4. Принцип «you build it, you run it»
Каждая команда владеет своими сервисами энд-ту-энд. Если Payments упал в 3 часа ночи — Payment team чинит. Это болезненно, но это единственный способ не дать командам генерировать «брошенный код».
#Что не сработало
Микроменеджмент архитектуры. На волне enterprise-роста компания наняла «архитектурный комитет», который утверждал каждое технологическое решение. Бюрократия убила скорость. Через год комитет распустили, оставив только рекомендательные архитектурные гайдлайны.
#Урок для тех, кто проходит этот путь
Архитектура стартапа, которая привела к IPO, — это не «правильная» архитектура. Это архитектура, которая достаточно хороша на каждом этапе.
Если бы компания S в 2012 году начала проектировать enterprise-архитектуру для 1000 человек, они бы никогда не запустились. Если бы они в 2020 году продолжали писать спагетти-код, как в 2012, — они бы утонули.
Секрет: рефакторить архитектуру нужно постоянно, но только под текущие боли, не под будущие страхи.
#История 3: The Rewrite — уроки Joel Spolsky и что реально работает
#Теорема Джоэла (весна 2000)
В апреле 2000 года Джоэл Спольски (Joel Spolsky) написал эссе «Things You Should Never Do, Part I» [Anecdotal]. Основная мысль:
«Переписывать код с нуля — самая дорогая ошибка в разработке ПО. Вы думаете, что старый код — дерьмо, а новый будет идеальным. Вы ошибаетесь. Новый код будет просто другим дерьмом, но пройдёт годы, прежде чем в нём накопится столько же фикс-багов, сколько было в старом.»
Спольски был прав в главном: rewrite — это почти всегда катастрофа. Но давайте разберёмся, почему, и — что важнее — когда rewrite всё-таки работает.
#История 3a: Rewrite Netscape (1998) — подтверждение теоремы
Netscape решил переписать браузер с нуля. Netscape Communicator 4.0 был «не very good», как выразился Спольски. Баги, медленный, глючный. Команда сказала: «Давайте напишем заново!».
Результат: пять лет разработки, пропущенный рынок, увольнение 70% инженеров, продажа компании AOL, и в итоге — Mozilla, которая появилась из руин, но когда Интернет Эксплорер уже занял 90% рынка.
Урок: rewrite убивает компании. Не потому что rewrite технически невозможен. А потому что время, которое вы тратите на rewrite, ваши конкуренты тратят на фичи.
#История 3b: Rewrite FreshBooks (2017) — когда теорема работает в обратную сторону
А теперь — история, где rewrite сработал. FreshBooks, канадская платформа для бухгалтерии, в 2017 году объявила, что переписывает свой продукт с нуля. И это было правильное решение.
Почему их случай отличался от Netscape?
- Бизнес не стоял на месте. Старый продукт продолжал продаваться и приносить деньги. Rewrite не остановил разработку фич — параллельно велись оба трека.
- Не все в одном релизе. FreshBooks не делал «big bang» — они мигрировали пользователей постепенно, фича за фичей.
- У них был чёткий критерий готовности. Не «когда всё будет идеально», а «когда новая платформа покроет 80% сценариев старых пользователей».
Результат: прощай старый код, здравствуй новая архитектура. Да, было больно. Да, заняло три года. Но бизнес выжил, продукт стал лучше.
#Когда rewrite реален (честный чек-лист)
Rewrite срабатывает, когда выполнены все эти условия:
- Старый код невозможно поддерживать. Не «неудобно» или «хочется красивее», а реально невозможно. Пример: код на Cobol, на котором некому писать. Или код, который использует библиотеку с CVE, не имеющую апдейта.
- У вас есть ресурс. Выделенная команда на 1-3 года, которая может работать параллельно с продуктовой разработкой. Если ваш rewrite — это «та же команда делает то же самое, только с нуля» — вы в зоне риска.
- Вы можете мигрировать инкрементально. Никакого big bang. Каждая фича мигрируется и тестируется отдельно. Пользователь не должен заметить переключения.
- Бизнес готов ждать. Rewrite не принесёт новых фич в ближайшие 6-12 месяцев. Если конкуренты дышат в спину — вам не до rewrite.
- Вы понимаете, что новый код будет не идеален. Вы не пишете идеальную систему — вы пишете систему, которая будет легче поддерживать следующие 5 лет.
#Что реально работает вместо rewrite
В 90% случаев rewrite не нужен. Нужен стратегический рефакторинг. Вот проверенный подход:
#The Strangler Fig Pattern
Мартин Фаулер популяризовал паттерн «Strangler Fig» (удушающий фикус) — постепенно замещать куски старой системы новой реализацией, пока от старой не останется ничего.
Как это выглядит:
- Пишете API Gateway (или reverse proxy), который направляет запросы
- Каждую новую фичу реализуете на новой архитектуре
- Каждый старый модуль, который требует изменений, переписываете и переключаете трафик
- Через 2-3 года старая архитектура умирает естественной смертью
Это не быстро. Но это надёжно. И бизнес продолжает работать, пока вы рефакторите.
#Анти-паттерн: Golden Hammer
Не делайте ошибку — не используйте rewrite как решение для проблем, которые решаются проще. Если у вас медленная база — не переписывайте приложение, настройте индексы и кэши.
#Принцип Спольски на практике
Главный урок rewritten — не «никогда не делайте rewrite» (хотя в большинстве случаев это правда). Главный урок: осознавайте цену перезапуска.
Прежде чем сказать «давайте перепишем с нуля», ответьте на вопросы:
- Сколько накопленных баг-фиксов мы теряем?
- Сколько лет кода, в который вложены человеко-годы бизнес-логики?
- Сколько пользовательских сценариев (включая те, о которых мы забыли) работает в старом коде?
- Какой процент старого кода — реально важная логика, а какой — boilerplate, который легко воспроизвести?
И только если цена перезапуска меньше цены поддержки старого кода — rewrite оправдан.
#История 4: Database migration — MySQL → PostgreSQL, 150TB, 0 downtime, 6 месяцев
#Контекст
Представьте: у вас 150 терабайт данных в MySQL. Это не просто много данных — это core business. Платёжные транзакции, история заказов за 8 лет, аналитические данные. Более 5000 приложений и сервисов читают и пишут в эту базу.
И вам нужно переехать на PostgreSQL.
Зачем? Причины могут быть разными:
- MySQL перестал справляться с определённым типом запросов (например, оконные функции или CTE)
- Проблемы с репликацией в конкретной версии
- Стратегическое решение: PostgreSQL open source, а не Oracle под капотом MySQL
- Нужен более зрелый экстеншен для геоданных (PostGIS) или full-text search
Причина вторична. Важно то, что переезд базы данных — одна из самых рискованных операций в разработке. И это история о том, как это сделать правильно.
#Стратегия: Double Write + Incremental Migration
Команда (назовём её «М» из крупной европейской e-commerce компании) разработала стратегию, которая состоит из трёх фаз.
#Фаза 1: Подготовка (2 месяца)
Шаг 1: Аудит схемы. MySQL и PostgreSQL несовместимы на уровне типов данных, синтаксиса, и поведения.
TINYINT(1)→BOOLEANилиSMALLINTVARCHAR— в целом совместимо, но кодировка может отличатьсяAUTO_INCREMENT→SERIALилиIDENTITYENGINE=InnoDB→ просто таблица- MySQL
GROUP BYменее строгий, чем PostgreSQL — всё, что не агрегировано, должно быть в GROUP BY. Миллион запросов сломается (TODO: это реальная проблема).Шаг 2: Конвертация типов. Каждый тип данных MySQL нужно сопоставить с PostgreSQL. Команда «М» написала автоматический анализатор, который просканировал все схемы (150TB!) и выдал отчёт о несовместимостях.
Шаг 3: Schema dump без данных. Запустили пустую PostgreSQL базу со сконвертированной схемой. Проверили, что приложение может с ней соединиться.
Шаг 4: Инструмент репликации. Сделали кастомный CDC (Change Data Capture) между MySQL и PostgreSQL. Это критический компонент: пока вы мигрируете данные, старые записи меняются. Нужно ловить эти изменения и применять их к новой базе.
Использовали комбинацию Debezium (для чтения MySQL binlog) и Kafka (для буферизации изменений). Не идеально, но работало.
#Фаза 2: Миграция данных (3 месяца)
Шаг 1: Полный dump и restore. Первая загрузка всех 150TB из MySQL в PostgreSQL.
Детали, которые имеют значение:
- Использовали
pgloader— инструмент, который умеет конвертировать схемы на лету и делать bulk insert- Dump делали с read replica, чтобы не нагружать master
- Bulk insert в PostgreSQL делали с
COPY— это на порядок быстрее INSERT- Отключили индексы и constraints на время загрузки (создали их потом)
- Использовали параллельные потоки — не один процесс, а 16 одновременных transfer-воркеров
Шаг 2: Continuous sync. Debezium читает MySQL binlog и отправляет изменения в Kafka. Специальный consumer применяет эти изменения к PostgreSQL.
Здесь ключевой момент: синхронизация должна быть идемпотентной — если одно изменение применится дважды, данные не должны испортиться.
Шаг 3: Валидация. Каждую ночь — автоматическая сверка данных: случайная выборка записей из MySQL и PostgreSQL сравниваются по всем полям. Расхождения логируются и исследуются.
За два месяца команда «М» нашла:
- 3 бага в конвертере типов (MySQL
TIMEс отрицательными значениями)- 2 особенности в поведении
GROUP BY(которые сломали бы продакшен)- 1 кейс, где MySQL позволял вставить дубликат в unique constraint (а PostgreSQL — нет)
#Фаза 3: Переключение (1 месяц)
Шаг 1: Double Write. Приложения начинают писать в обе базы одновременно. Код меняется так:
# Было: db.execute("INSERT INTO orders ...") # Стало: db_mysql.execute("INSERT INTO orders ...") db_pg.execute("INSERT INTO orders ...")Но это хитрый момент: гарантировать консистентность между двумя записями нельзя. Если MySQL INSERT прошёл, а PostgreSQL упал — что делать? Команда «М» решила: писать в обе базы, но считать истиной MySQL (old source of truth), пока не завершится миграция. PostgreSQL может проседать — потом нагонит через CDC.
Шаг 2: Read test. Аналитики переключаются на чтение из PostgreSQL. Если падают запросы — возвращаются на MySQL. Это длится 2 недели.
В результате:
- 12% аналитических запросов упали (разный синтаксис, несовместимость функций)
- 3% данных имели расхождения (исправлены вручную через скрипты)
- 1 критический баг: PostgreSQL требует более строгих прав для
DELETEбезWHEREШаг 3: Cutover. Выбирается окно в 4 часа утра воскресенья (самый низкий трафик). Все приложения переключаются на PostgreSQL как на primary. MySQL остаётся как read-only (на случай отката).
Шаг 4: Monitoring. Первые 48 часов — полный мониторинг каждого запроса. Любая аномалия — и команда откатывается на MySQL.
Команда «М» провела 4 сухих запуска (dry-run) перед реальным cutover. На реальном — всё прошло за 2 часа.
#Почему это заняло 6 месяцев, а не 2 недели
Потому что 90% времени — это не переливание данных. Это:
- Тестирование совместимости (2 месяца)
- Валидация (3 месяца непрерывной сверки)
- Координация с 5000 приложений (каждое нужно проверить)
- Психологическая готовность команды (это реально страшно)
#Что пошло не так (честно)
- CDC lag. На пике нагрузки Debezium отставал на 15 минут. Пришлось тюнить Kafka и консьюмер-группы.
- Schema drift. Пока команда мигрировала, разработчики меняли схему в MySQL. Каждое изменение нужно было зеркалить в PostgreSQL. Пропустили два alter table — пришлось делать ручную синхронизацию.
- Разное поведение в edge cases. MySQL позволяет вставить
'0000-00-00'как дату. PostgreSQL — нет. 150 тысяч записей с такой датой пришлось чистить перед миграцией.- Performance regression. Некоторые запросы, которые летали в MySQL (с его query optimizer), в PostgreSQL работали в 10 раз медленнее. Пришлось переписывать запросы (а это значило — менять код приложений). На это ушёл ещё месяц.
#Чек-лист для миграции базы данных
- Проверить все типы данных и их совместимость
- Написать CDC/replication между старой и новой базой
- Сделать полный dry-run хотя бы дважды
- Настроить непрерывную валидацию данных (automated comparison)
- Подготовить rollback план (MySQL остаётся read-only)
- Заранее оптимизировать тяжёлые запросы на новой базе
- Проверить edge cases: пустые строки, null, special characters, даты
- Написать скрипт для schema drift sync
- Провести нагрузочное тестирование (старая vs новая)
- Быть готовым, что 5-10% запросов придётся переписать
#История 5: Cloud migration — on-premise → AWS/GCP/Azure
#Компания L: банк, который решил переехать в облако
Компания «L» — крупный европейский банк, 20+ лет истории. Дата-центры: три, разбросаны по Европе. Железо: HP, Dell, Cisco. Сеть: MPLS с пропускной способностью 40G между ЦОДами. Команда: сотни DevOps, системных администраторов, DBA.
В 2018 году правление принимает стратегическое решение: «Мы переезжаем в облако». AWS как primary provider, Azure как backup.
Причины:
- CapEx vs OpEx — банку надоело покупать железо на 3 года вперёд
- Скорость — выделить новый сервер в ЦОДе занимало 3 недели. В облаке — 3 минуты
- Compliance — регуляторы требуют disaster recovery с RPO < 15 минут (сделать это в ЦОДе дорого)
#Наивный план
«Берём все виртуалки, конвертируем в AMI, заливаем в AWS, Profit!»
Первый месяц: команда конвертирует 1500 VM в AMI (через AWS Import/Export). Ещё 500 VM — через CloudEndure (continuous replication). Результат: инфраструктура переехала, но всё работает ровно так же, как работало в ЦОДе. Просто теперь это дорогие виртуалки в AWS вместо дешёвых в ЦОДе.
Облачная экономика не работает на lift-and-shift.
#Фаза 1: Lift and Shift (месяцы 1-6)
3000 VM перенесены в AWS. Результаты:
- Счёт за инфраструктуру вырос на 40% (AWS дороже, чем собственное железо)
- Никакого выигрыша в скорости разработки
- Операционные проблемы: настройка VPC, security groups, IAM — всё новое
- Команда в шоке: «Мы переехали в облако, а оно дороже и медленнее»
Ошибка: банк перенёс не только приложения, но и архитектурные ограничения on-premise. В ЦОДе у вас есть «виртуалка на 32 CPU и 256GB RAM». В AWS у вас тоже может быть такая виртуалка. Но смысл облака — не в виртуалках.
#Фаза 2: Реархитектура (месяцы 7-18)
Команда осознаёт: облако — это не «ЦОД как сервис». Облако — это другой способ мышления.
Что изменили:
Stateless everywhere. Все приложения переведены на stateless архитектуру. Данные — в RDS, ElastiCache, S3. Сами приложения — в Auto Scaling Groups или ECS. Если одна инстанция умирает — новая поднимается и подхватывает трафик.
Managed services вместо самописного зоопарка. Вместо собственного Kafka кластера — Amazon MSK. Вместо своего RabbitMQ — Amazon MQ. Вместо своего Elasticsearch — Amazon OpenSearch. Да, это дороже, но не надо держать команду, которая чинит Kafka в 3 часа ночи.
Immutable infrastructure. Вместо «зайти на сервер по SSH и поправить конфиг» — Packer + Terraform. Сервер пересоздаётся, не патчится.
Disaster recovery. Раньше DR означало «второй ЦОД с таким же железом». Теперь — Terraform apply в другом регионе. Стоимость DR упала в 10 раз.
Что не изменили:
- Не все legacy-монолиты удалось распилить. Некоторые так и живут на больших EC2 инстанциях.
- Mainframe (IBM z/OS) не переехал в облако. Остался в ЦОДе. AWS не умеет запускать мейнфреймы.
#Фаза 3: Оптимизация (месяцы 19-36)
Через полтора года банк начинает реально экономить:
- Reserved Instances. Купили RI на 3 года для базовых нагрузок. Счёт упал на 40% относительно on-demand.
- Spot instances. Для нечувствительных ко времени задач (data processing, batch jobs) — spot. Экономия ещё 60% на этих задачах.
- S3 lifecycle. Данные старше 30 дней — в S3 Infrequent Access. Старше года — в S3 Glacier. Старше 7 лет — delete.
- Right-sizing. AWS Trusted Advisor показал, что 30% виртуалок переразмерены. Уменьшили. Счёт ещё -15%.
#Что пошло не так
1. Hidden costs
Банк не учёл стоимость data transfer. Когда 200 микросервисов в разных availability zones общаются друг с другом — это терабайты трафика. AZ-to-AZ трафик в AWS платный. Счёт за data transfer оказался вторым по величине после compute.
Решение: размещать связанные сервисы в одной AZ (или хотя бы осознавать цену cross-AZ трафика).
2. Security group hell
3000 VM × 5 security groups каждая = 15000 security group rules. Это не управляемо.
Решение: централизованная система управления security groups через Terraform + CI/CD. Любое изменение — через Pull Request.
3. Compliance audit shock
Регулятор (ECB — European Central Bank) пришёл с аудитом. Первый вопрос: «Где физически находятся ваши данные?» AWS не даёт клиентам выбирать конкретный стойку в дата-центре — только регион. Для банка это была проблема: по регуляторным требованиям, данные должны быть в ЕС, на территории конкретной страны.
Решение: AWS Outposts (физические стойки AWS в ЦОДе банка) для критических данных + стандартный AWS для всего остального.
4. «Мы переехали, но ничего не изменилось»
Самый болезненный урок. Банк потратил 18 месяцев на переезд, но разработчики продолжали писать код, как для on-premise. Stateless? «Нет, мы храним сессии на сервере». Автомасштабирование? «Нет, у нас фиксированные инстанции». Managed services? «Нет, мы поднимаем свой Kafka, как раньше».
Культурная трансформация заняла ещё 12 месяцев. И это нормально: облако — это не технологии, это культура.
#Ключевые показатели после 3 лет
Метрика On-premise AWS (год 1) AWS (год 3) Время выделения сервера 3 недели 3 минуты 3 минуты CapEx/год на инфраструктуру €12M €0 €0 OpEx/год €8M (электричество, охлаждение, персонал) €22M €18M DR готовность 8 часов RTO 4 часа RTO 1 час RTO Время релиза новой фичи 2 недели 1 неделя 3 дня Команда инфраструктуры 45 человек 50 человек 30 человек Да, облако обошлось дороже, чем on-premise (если считать только инфраструктуру). Но если считать скорость доставки ценности — облако выиграло.
#Итог для тех, кто планирует cloud migration
- Не делайте lift-and-shift. Это дороже on-premise. Облачная экономика работает, только если вы меняете архитектуру.
- Готовьтесь к hidden costs. Data transfer, NAT Gateway, ALB — каждая мелочь влетает в копеечку. Считайте заранее.
- Облако не убирает необходимость в DevOps. Оно меняет DevOps. Вместо управления серверами — управление Terraform стейтами и IAM политиками.
- Культура важнее технологии. Если команда продолжает писать stateful код на фиксированных инстанциях — облако не поможет.
- Гибрид — это нормально. Не всё должно быть в облаке. Mainframe, legacy COBOL, real-time trading системы могут остаться on-premise.
#А что в итоге?
Пять историй — пять разных уроков. Давайте соберём общее.
#Главные выводы
Микросервисы не лечат проблемы скорости. Они часто их усугубляют. Если вы не имеете организационной потребности в независимом деплое — модульный монолит работает не хуже, а проще.
Архитектура должна расти с компанией. То, что работало для 15 человек, сломается на 200. То, что нужно 200 — убьёт стартап на 15. Рефакторьте под текущие боли, не под будущие страхи.
Rewrite — это почти всегда плохая идея, но не абсолютно. Strangler Fig Pattern работает в 9 из 10 случаев. Полный rewrite — когда старый код нельзя поддерживать, и у вас есть ресурс на 1-3 года параллельной разработки.
Миграция базы данных — это 90% тестирования и 10% переливания. Не недооценивайте CDC, валидацию, и edge cases. План отката — обязателен.
Облако — это не ЦОД как сервис. Без архитектурных изменений облако будет дороже on-premise. Экономия начинается, когда вы переходите на stateless + managed services.
#Анти-паттерны (ещё раз, кратко)
- Big Bang. Переключать всё сразу — гарантированный способ получить P0 инцидент. Мигрируйте инкрементально.
- Rewrite с теми же людьми без остановки разработки. Spoiler: ни то, ни другое не будет сделано хорошо.
- Lift-and-shift в облако. Вы заплатите больше и получите те же проблемы.
- Микросервисы без организационной готовности. Distributed monolith — худшее из двух миров.
- «Сделаем идеально». Архитектура — это компромиссы. Идеальной не будет.
#Чек-лист для любой архитектурной трансформации
- Есть ли у вас чёткий критерий успеха («что считается законченным»)?
- Есть ли план отката (rollback)?
- Знаете ли вы, какие компромиссы вы принимаете?
- Бизнес понимает, что вы делаете и сколько это займёт?
- Команда обучена новому стэку?
- Можете ли вы мигрировать инкрементально (а не big bang)?
- Есть ли у вас автотесты для нового кода?
- Есть ли у вас мониторинг для обнаружения проблем после миграции?
- Как быстро вы узнаете, что что-то пошло не так? (observability)
- Готовы ли вы признать ошибку и откатиться, если что-то пошло не так?
#Ключевые тезисы
Архитектурная трансформация — это про культуру и организацию, а не про технологии. Если не изменить то, как команда принимает решения, технологический переезд не даст результата.
Нет единственно правильного пути. Monolith, microservices, modular monolith — всё может быть правильным в разное время в одной и той же компании.
Рефакторинг никогда не заканчивается. Как только вы закончили одну миграцию, готовьтесь к следующей. Архитектура — это непрерывный процесс принятия решений, а не проект с датой завершения.
Цена ошибки при миграции — время, а не код. Код можно переписать. Время, упущенное перед конкурентами, и доверие пользователей — нет.
Признать ошибку — нормально. Почти каждая компания из этой главы проходила через «мы сделали неправильный выбор и вернулись назад» или «мы потратили больше, чем планировали». Это не стыдно. Стыдно — не сделать выводов.
Forward reference: AI-assisted рефакторинг — одна из самых эффективных областей применения AI-агентов (Claude Code показывает 85-95% success на multi-file refactor). Как AI меняет подход к миграции legacy-кода и рефакторингу — в Главе 34 «Как AI трансформирует SDLC».