Модуль 8. Defense — ADR + ROI Go vs Symfony
Цель модуля: Научить студента упаковать архитектуру Git-хостинга на Go в защищаемые артефакты (4 ADR, C4 Level 2, SLO/Cost-отчёт) и пройти ролевую защиту перед «архитектурным комитетом» (CTO, техлид, финдиректор). Студент должен аргументировать каждый Trade-off количественными метриками ($/мес, p99, RTO), сравнить TCO Go vs Symfony при 10k и 100k RPS, и честно показать, где Symfony выигрывает (скорость разработки, Doctrine ORM, крупная команда). Цель защиты — не «Go лучше», а «Go выбран осознанно, с цифрами и планом отката».
Перед началом: Модули 1–7 (используются артефакты всех модулей: ADR-GG2 CGI/exec, ADR-GG5 Cache/CDN, ADR-GG7 Scale 100k) Инструменты: — (сборка ADR, диаграмм и отчётов из предыдущих модулей) Трудоёмкость: ~8 ч (4 ADR 3 ч · C4-диаграмма 1 ч · SLO/отчёт 2 ч · защита 2 ч)
🔥 Инцидент: день защиты
Вы в переговорке. Напротив — «архитектурный комитет»: CTO, техлид платформы и финансовый директор. На экране — C4-диаграмма Git-хостинга на Go, рядом — четыре ADR с цифрами, в руках — отчёт по SLO и ROI Go vs Symfony.
CTO: «Вижу Go, Kafka, Citus, Storage Nodes. Расскажи не про то, что есть, а про то, от чего ты отказался и почему. Почему Go, а не PHP, который мы эксплуатируем 10 лет?».
Техлид: «Как шардировать Git-репозитории? Что если одна Storage Node сдохнет — git clone упадёт? А exec.Command вместо libgit2 — почему?».
Финдиректор: «Сколько мы сэкономили на RoadRunner — у Symfony его нет, а у Go он не нужен? 100 000 RPS — это сколько в ₽/мес? И сколько мы теряем при downtime?».
Вы начинаете не с кода, а с бизнеса: «Git-хостинг. NFR: RTO < 5 мин, p99 < 100 мс, 100k RPS после миграции с GitLab. При downtime теряем N ₽/час (отток команд на GitHub/GitLab). Архитектура одинакова (Outbox, Saga, Cache-Aside, Storage Nodes) — сравним TCO двух стеков». Затем — ADR с цифрами: «Go: 5 ВМ × 4 vCPU на 10k = $400/мес. Symfony-FPM на 10k: 15 ВМ = $1200/мес. При 100k: Go = 10 ВМ = $800/мес (рантайм тот же), Symfony+RoadRunner = 30 ВМ = $2400/мес. ROI Go vs Symfony-FPM: −$800/мес. vs RoadRunner: −$1600/мес + пропущенный инфра-слой».
Архитектор отличается от разработчика не набором паттернов (они одни и те же в Go и Symfony), а умением связать выбор языка и инфраструктуры с ₽/мес и RTO — и защитить каждое решение цифрами и планом отката. Без этого ADR — это не архитектура, а Wiki-страница.
📖 Теория: Как защищать архитектуру Git-хостинга на Go
Глоссарий модуля:
- ADR (Architecture Decision Record) — короткий документ: контекст, альтернативы, решение, последствия с цифрами; фиксирует, почему решили именно так (а не что сделали).
- C4 — 4 уровня архитектурных диаграмм (Context → Container → Component → Code); для защиты хватает Level 2 (Container): сервисы, протоколы, потоки.
- NFR (Non-Functional Requirement) — нефункциональное требование: SLO (p99, uptime), RTO, стоимость.
- TCO (Total Cost of Ownership) — полная стоимость владения: инфраструктура + эксплуатация + стоимость простоя за период.
- ROI (Return on Investment) — окупаемость: выгода от инвестиций (снижение cloud-счёта / потерь от downtime) минус стоимость избыточной инфраструктуры.
8.1 Структура защиты (Pitch) — сверху-вниз
Защита строится по принципу сверху-вниз (как в arch m10), от бизнеса к коду:
- Бизнес-контекст и NFR: что строим (Git-хостинг), какие SLO/RTO (p99 < 100 мс, RTO < 5 мин, 100k RPS), сколько стоит час простоя (отток команд, неустойка).
- High-Level архитектура (C4 Level 2): общая схема — git-gateway (Go,
exec.Commandstreaming), Storage Nodes, Go API, Kafka, Citus, CDN. Протоколы (HTTP/Smart HTTP, Kafka, gRPC к Storage Nodes), гарантии доставки (at-least-once через Outbox). - Ключевые решения и Trade-offs: разбор 4 ADR с цифрами ($/мес, p99, RTO, % риска). Отдельный блок: почему Go, а не PHP.
- ROI и план миграции: таблица «Go 10k / Go 100k / Symfony 10k / Symfony+RR 100k» в $/мес, обоснование окупаемости, план перехода без простоя (Модуль 7). Честно: где Symfony выигрывает.
Архитектор не начинает с «мы выбрали Go». Он начинает с «100k RPS = потеря X ₽/час при downtime, p99 > 500 мс = отток Y команд/месяц. Архитектура одинаковая (Outbox, Saga, Storage Nodes) — поэтому сравним TCO двух стеков...». Язык — следствие бизнес-метрики, не наоборот.
8.2 Четыре ADR для Git-хостинга на Go
К защите студент готовит минимум 4 ADR (на основе Модулей 2, 5, 7 + архитектурный):
ADR-GG2: «Почему exec.Command + io streaming, а не libgit2 / PHP-чтение packfile».
- Контекст:
git cloneкрупного репо = чтение packfile в сотни МБ. Чтение в память Symfony → OOM на >16 МБ. libgit2 (через cgo) — риск segfault в Go-процессе, сложная CGO-сборка под Yandex Serverless. - Решение:
exec.Command("git", "upload-pack", ...).Stdin = r.Body+io.Copy(w, cmd.Stdout)— нативный стрим в Go, без буферизации (Модуль 2). - Trade-off: +зависимость от системного
gitв образе (multi-stage Docker:apk add git). Но 0 cgo, 0 OOM, 0 PHP-CGI-workaround.
ADR-GG5: «Почему Cache-Aside + CDN, а не инвалидация на запись».
- Контекст: 80 % трафика — чтение деревьев (
/tree/{sha}), immutable по SHA. - Решение: CDN на edge (1 час, immutable) + Redis Cache-Aside (
go-redis) для cache-miss на бэкенде (Модуль 5). Фоновой refresh — нативной горутинойgo refresh(ctx, id), без Messenger. - Trade-off: CDN отсекает 80 % трафика до бэкенда. Минус — устаревание в течение TTL (для immutable-SHA неактуально).
ADR-GG7a: «Почему Go long-lived, а не RoadRunner (или PHP-FPM)».
- Контекст: при 100k RPS PHP-FPM = 150 ВМ, 7 500 процессов, CPU на fork/context-switching. RoadRunner решает, но добавляет отдельный рантайм и требования (Worker recycling, stateless).
- Решение: Go binary long-lived из коробки. 5 ВМ на 10k → 10 ВМ на 100k (CDN снимает 80 % → реально back-end 20k RPS). Рантайм не меняется при росте.
- Trade-off: Go требует навыков в команде (если все/php/разработчики — нужен онбординг на язык). Итерация до MVP удлиняется. Но TCO ниже: −35 % cloud-счёта vs RoadRunner, −65 % vs FPM. План: канарейка 10 % → cut-over; откат — cut-back на существующий PHP-FPM (параллельная эксплуатация, без переписывания).
ADR-GG7b: «Почему шардирование Storage Nodes и Citus, а не вертикальное масштабирование».
- Контекст: IOPS одного диска и write-CPU мастера PG — потолок при 100k. Этот риск не зависит от языка (одинаков для Go и Symfony).
- Решение: 15 Storage Nodes (NVMe, по
repo_id) + Citus (5 data nodes, шард поrepo_id).pgx-клиент не меняется (Citus совместим на уровне протокола). - Trade-off: +сложность (resharding, cross-shard JOIN), но write-CPU 95→40 %, запас до 250k RPS. План: dual-write + backfill + cut-over.
8.3 ROI таблица: Go vs Symfony (в $/мес)
| Метрика | Go 10k | Go 100k | Symfony-FPM 10k | Symfony+RoadRunner 100k | Где выигрывает |
|---|---|---|---|---|---|
| Runtime ВМ | 5 × 4 vCPU | 10 × 4 vCPU | 15 × 4 vCPU (FPM) | 30 × 4 vCPU (RR) | Go (×3 fewer ВМ) |
| vCPU суммарно | 20 | 40 | 60 | 120 | Go (×3 меньше CPU) |
| $/мес (cloud) | $400 | $800 | $1 200 | $2 400 | Go (−65% vs FPM, −65% vs RR) |
| p99 | 1 мс | 1 мс (рантайм тот же) | 20 мс (FPM fork) | 2 мс (RoadRunner) | Go (×20 vs FPM, ×2 vs RR) |
| Runtime сменяется 10k→100k | ❌ нет | ❌ нет (тот же бинарник) | — | ✅ да (FPM → RoadRunner — отд. технология) | Go (нет миграции) |
| Streaming packfile | io.Copy (нет лимита) | то же | PHP OOM >16 МБ → Nginx CGI workaround | то же workaround | Go (нет CGI) |
| Bulkhead | chan struct{} (1 конструкт) | то же | 2 пула FPM + Nginx upstream | то же | Go (1 строка vs конфиг) |
| Outbox refresh (SWR) | go refresh() (горутина) | то же | bus->dispatch (Messenger) | то же | Tie (Go проще инфра) |
| Скорость разработки (MVP) | ⭐⭐⭐ | — | ⭐⭐⭐⭐⭐ (Doctrine, Symfony CLI, экосистема) | ⭐⭐⭐⭐ | Symfony |
| ORM / DB-слой | pgx (явный SQL) | то же | Doctrine ORM (магия, миграции) | то же | Symfony (для CRUD-heavy) |
| Найм команды | Go-разработчиков меньше на рынке | — | PHP-рынок огромный | — | Symfony |
Ключевой аргумент для финдиректора: архитектура одинакова (Outbox, Saga, Cache-Aside, Storage Nodes, Citus — все паттерны из курса «Архитектура»). Различие только в стеке. Go: 10 ВМ × $80/мес = $800 при 100k RPS. Symfony+RoadRunner: 30 ВМ = $2 400/мес. — Go экономит $1 600/мес = ~$20k/год, плюс пропущенный инфра-слой (RoadRunner — отдельная технология с поддержкой и эксплуатацией). Но если у вас крупная PHP-команда и MVP-race, то обучение команды Go стоит > $20k в первый год — Symfony-FPM может быть оптимальнее в short-term.
8.4 Жёсткие вопросы комитета (с подсказками)
-
CTO: «Почему Go, а не PHP?» Подсказка: при 100k RPS Go long-lived из коробки (10k горутин на ВМ, нет fork + bootstrap = 20–30 мс). Симметрия: PHP-FPM → RoadRunner — это миграция рантайма; на Go этого шага нет. Но если команда знает только PHP и MVP-горит — Symfony+RoadRunner валиден. Решение зависит от team-skill + NFR, не от «Go модно».
-
Финдиректор: «Сколько сэкономили на RoadRunner?» Подсказка: Go 100k = $800/мес, Symfony+RR 100k = $2 400/мес → −$1 600/мес = ~$20k/год. Плюс пропущенный операционный cost RoadRunner (licence/expertise). Но +T&M на Go-команду в первый год. Через 2 года Go окупается, через 5 — экономия $100k+.
-
Техлид: «
exec.Commandvs libgit2 — почему subprocess?» Подсказка:exec.Command+io.Copyстримит packfile без буферизации, 0 cgo, 0 segfault-risk. libgit2 через cgo: сложная сборка, риск segfault (= crash всего процесса), vendor-lock на C-API. Главное — fail-safe: subprocess падает в sandbox, Go-процесс жив. -
CTO: «Зачем Kafka, а не Redis Streams — у Redis Cluster есть?» Подсказка: Redis однопоточный (одно ядро на broker). При 100k событий/сек Redis Cluster не решит hot-партицию (партиционирование по ключу → hot repo → одна партиция). Kafka: RF=3, партиционирование по
repo_id(HashBalancer), write-path на диск. Порог перехода: > 30k событий/сек или персистентность. -
Техлид: «Как шардировать Git-репозитории? Что если Storage Node умрёт?» Подсказка: шард по
repo_id(consistent hashing → минимум перемещений при resharding). Каждая нода имеет read-реплику (rsync/git-fsck, RPO 1 мин). Падение ноды → запросы на реплику (directory-сервис переключает). RTO < 1 мин. -
Финдиректор: «А если не делать Citus — дешевле поднять ещё один PG-кластер?» Подсказка: два кластера PG = split-brain по данным, cross-cluster JOIN невозможен, double operational cost. Citus — единый кластер с прозрачным распределённым JOIN. TCO Citus ниже при равной write-пропускной способности.
-
CTO: «Где Symfony выигрывает? Будьте честны.» Подсказка: скорость разработки (Doctrine ORM, Symfony CLI, экосистема бандлов), крупный PHP-рынок найма, лучшие административные инструменты (
make:entity, EasyAdmin). Для CRUD-heavy сервисов с командой PHP-разработчиков и без 100k-RPS-NFR — Symfony-FPM оптимальный выбор. Go выбирают при highload-NFR и/или streaming-требованиях, а не «потому что модно».
8.5 Сводный чек-лист архитектора Git-хостинга (Go)
Каждый пункт должен быть подкреплён цифрами и планом отката (как в arch m10):
- Smart HTTP / exec.Command (Модуль 2):
exec.Command+io.Copy, стриминг packfile без OOM. ADR-GG2. - Bulkhead (Модуль 3):
chan struct{}— изоляция тяжёлых git-операций (одна конструкция вместо двух пулов FPM + Nginx). - Outbox (Модуль 4):
git push→ Kafka (at-least-once),FOR UPDATE SKIP LOCKED,trace_idв headers. - Cache-Aside + CDN (Модуль 5): Redis деревьев (
go-redis) + CDN/tree/{sha}immutable. Refresh — горутиной. ADR-GG5. - PR Saga (Модуль 6): merge flow на
switch+pgx, компенсация mark-merge-failed(НЕ ROLLBACK), таймаут-горутина. - Long-lived runtime (Модуль 7): Go binary — без RoadRunner. 10 ВМ на 100k RPS. ADR-GG7a.
- Storage Nodes + Citus (Модуль 7): шард по
repo_id, NVMe, реплики. ADR-GG7b. - 4 ADR с количественными Trade-offs ($/мес, p99, RTO, % риска) и планом миграции/отката.
- TCO сравнение Go vs Symfony (10k и 100k) с честным блоком «где Symfony выигрывает».
Критерий сдачи курса: студент заполняет чек-лист своими цифрами (RTO, SLO, $/мес, % риска) и защищает каждое решение перед комитетом — без единого «потому что Go модный», с честным блоком «где PHP/Symfony был бы выбором».
🛠 Практика: финальная сборка и защита
Примерный план работы:
- Финализировать 4 ADR (ADR-GG2 exec/streaming, ADR-GG5 Cache/CDN, ADR-GG7a Go long-lived, ADR-GG7b Storage/Citus) с количественными Trade-offs.
- Нарисовать C4 Level 2 Git-хостинга на Go (профиль 100k): git-gateway (exec.Command), Storage Nodes, Go API, Kafka, Citus, CDN, протоколы, sync/async потоки.
- Собрать SLO/Cost-отчёт: точка деградации, fallback, ROI Go vs Symfony (таблица из §8.3), честный блок «где Symfony выигрывает».
- Ролевая защита перед LLM-«комитетом»: 5+ жёстких вопросов (Go vs PHP TCO, exec.Command vs libgit2, Kafka, шардирование, честное «где Symfony»).
Задача 1: Финализация 4 ADR
Пример структуры (ADR-GG7a: Go long-lived vs RoadRunner):
## Контекст
100 000 RPS. Архитектура одинакова (Outbox, Saga, Storage Nodes).
Сравниваем рантайм: Symfony-FPM (150 ВМ), Symfony+RoadRunner (30 ВМ), Go (10 ВМ).
## Альтернативы
- A. Symfony-FPM (150 ВМ): $/мес = $3 000, p99 = 250 мс (fork+bootstrap).
- B. Symfony+RoadRunner (30 ВМ): $/мес = $2 400, p99 = 2 мс, +1 технология в эксплуатации.
- C. Go binary long-lived (10 ВМ): $/мес = $800, p99 = 1 мс, рантайм не меняется 10k→100k.
- D. Serverless Containers: Cold Start Symfony недопустим для синхронных API; Go cold start = 1-2 с (мarginal).
## Решение
Go binary long-lived. 5 ВМ на 10k → 10 ВМ на 100k (CDN снимает 80 %).
## Последствия (Trade-offs)
- Плюсы: −$1 600/мес vs RoadRunner, no RoadRunner-инфра, рантайм тот же при росте.
- Минусы: Go-найм (T&M на обучение PHP-команды в первый год).
- Риски: team-skill → mitigated наймом 1-2 senior Go-разработчиков как anchors.
- Метрики: RTO < 5 мин, p99 < 100 мс, запас до 250k RPS (10 ВМ × 25k RPS).
- План миграции: канарейка 10 % → 50 % → cut-over. Откат: cut-back на PHP-FPM существующего (parallel deploy, no rewrite).
✅ Готово, когда:
- Минимум 4 ADR, каждый: Контекст, Альтернативы (мин. 2), Решение, Метрики
- Каждый ADR содержит количественные Trade-offs: $/мес, p99, RTO, % риска
- Каждый ADR содержит план миграции и план отката
- ADR-GG7a явно сравнивает Go vs RoadRunner vs FPM (а не только «Go vs PHP»)
Задача 2: C4 Level 2 (профиль 100k на Go)
Создать диаграмму: git-gateway (exec.Command streaming) → Storage Nodes, Go API (long-lived), Kafka (Outbox), Citus, CDN. Типы протоколов (HTTP/Smart HTTP, Kafka, gRPC to Storage Nodes), гарантии доставки (at-least-once), разделение sync/async.
Шаблон для ориентира:
✅ Готово, когда:
- Диаграмма в нотации C4 Level 2 (Container)
- Отражены git-gateway (Go, exec.Command), Storage Nodes, Go API, Kafka, Citus, CDN
- Отражены протоколы (HTTP/Smart HTTP, Kafka, gRPC to Storage Nodes) и sync/async потоки
Задача 3: SLO/Cost-отчёт (ROI Go vs Symfony)
Связать технические метрики с бизнес-потерями. Описать: при какой нагрузке начинаются ошибки/таймауты, какой fallback срабатывает (CB, CDN stale), как соотносится с SLO. Привести таблицу ROI (§8.3) и сделать вывод. Обязательно: честный блок «где Symfony выигрывает» (MVP-скорость, Doctrine, найм).
✅ Готово, когда:
- Описана точка деградации: при какой нагрузке начинаются ошибки/таймауты
- Описан fallback (CB на git-gateway, CDN stale-while-revalidate) и связь с SLO
- Таблица ROI: Go 10k / Go 100k / Symfony-FPM 10k / Symfony+RR 100k ($/мес, p99, RTO, запас)
- Вывод: окупается ли переход на Go (количественно: −$1 600/мес vs RoadRunner, но +T&M команда)
- Честный блок «где Symfony выигрывает» (MVP, Doctrine, найм) — иначе защита провалена
Задача 4: Ролевая защита перед комитетом
LLM выступает «Злым Архитектором» и задаёт жёсткие вопросы из §8.4. Критерий сдачи: каждое решение обосновано метриками ($/мес, p99, RTO, % риска) и планом отката, а на вопрос «где Symfony выигрывает» дан честный ответ.
✅ Готово, когда:
- Ответы на все вопросы комитета даны количественными аргументами
- TCO: обоснована разница Go 100k vs Symfony+RR 100k ($800 vs $2 400 = −$1 600/мес)
- Есть честный блок «где Symfony выигрывает» (MVP-скорость, Doctrine ORM, найм) — иначе защита провалена
- Scale: описано поведение при росте до 250k RPS (запас Citus + Storage Nodes на Go)
- Migration: описан план отката для каждого слоя (Go cut-back на PHP-FPM parallel deploy, Storage directory revert, Kafka dual-publish)
- Ни один ответ не сводится к «потому что Go модный»
⚠️ Антипаттерн для разбора
textЗащита: «Мы написали Git-хостинг на Go. Он принимает push и clone. Go быстрее PHP, поэтому выбрали его. Для масштабирования добавили Kafka, потому что Redis старый. Система работает, тесты проходят, рантайм Go не нужен RoadRunner».
Вопросы студенту:
- Почему «Go быстрее PHP» — не аргумент для архитектора? (Нужно: NFR «long-lived уже, нет fork = 10 ВМ vs 30 RoadRunner = −$1 600/мес». Не «быстрее», а «TCO ниже при NFR»).
- Чего не хватает? (Бизнес-контекста, количественных Trade-offs, ROI Go vs Symfony 10k/100k, плана отката, честного блока «где Symfony выигрывает»).
- Почему «Kafka, потому что Redis старый» — провал ревью? (Нужен порог: > 30k событий/сек или требование персистентности; иначе Redis Streams дешевле).
📖 Компромиссы (Trade-offs) — сводная таблица курса
| Решение (10k на Go) | Решение (100k на Go) | Что gained | Что lost | Триггер перехода |
|---|---|---|---|---|
| Go binary (5 ВМ) | Go binary (10 ВМ, рантайм тот же) | ×2 ВМ, не нужен RoadRunner | team-skill Go | 10k → 100k (рантайм не упирается) |
| Redis Streams | Kafka (segmentio/kafka-go) | персистентность, партиционирование | +инфраструктура, +задержка | >10k событий/сек |
| Single PostgreSQL | Citus (sharded) | горизонтальный write scale | cross-shard JOIN, сложность | write CPU > 80% |
| 1 Storage Node | 10-20 Storage Nodes | IOPS на шард, изоляция | маршрутизация, репликация | I/O wait > 30% |
| Nginx proxy_cache | Yandex CDN | гео-распределение, 80% offload | настройка invalidation | пользователи в >3 регионах |
chan struct{} bulkhead | тот же (не меняется) | без изменений | — | bulkhead и на 100k работает |
Критерий сдачи курса: студент заполняет эту таблицу своими цифрами ($/мес, RPS, p99) и защищает каждый переход. Честный блок «где Symfony выигрывает» обязателен — отсутствие = защита провалена.
🧩 Проверь себя
exec.Command + io.Copy, а не libgit2 через cgo?chan struct{} bulkhead (а не два пула FPM + Nginx upstream)?🤖 Инструкция для LLM (Coding Plan Assistant) — Режим Модуля 8
Привет! Я завершаю курс «Git Hosting на Go» и готовлюсь к защите.
Дай短кий ответ (2–3 предложения): почему архитектор при защите сравнивает Go vs Symfony в TCO, а не доказывает «Go лучше»?
Покажи пример Антипаттерна защиты («Go быстрый, Kafka модно») и попроси меня переформулировать в NFR и ADR с цифрами + честный блок «где Symfony выигрывает».
Выдай финальный чек-лист архитектора Git-хостинга на Go (exec/Smart HTTP,
chanbulkhead, Outbox, Cache/CDN, Saga, long-lived runtime, Storage/Citus, 4 ADR, TCO vs Symfony).Скажи: «Я готов выступить в роли комитета (CTO/техлид/финдиректор). Пришли свои ADR и C4 — я задам 5 жёстких вопросов: зачем Go (TCO), exec.Command vs libgit2, Kafka vs Redis, шардирование, и где Symfony выигрывает. Если честного блока "Symfony" нет — защита провалена».
НЕ ПИШИ за меня ADR. Дождись материалов.
🎓 Мостик в Реальный мир (Graduation)
В начале ревью скажи студенту:
«Поздравляю! Ты прошёл путь от первого git clone через Smart HTTP на exec.Command до распределённого Git-хостинга на 100 000 RPS. Ты умеешь не только реализовывать паттерны (Outbox на горутинах, Saga на switch + pgx, Cache-Aside с go-redis SWR, Bulkhead на chan), но и понимать, где Go убирает инфраструктурные костыли (RoadRunner, CGI, два пула FPM) — а где npm-стек не решает архитектурные проблемы (Outbox, Saga, шардирование — те же). Твои 4 ADR, C4-диаграмма и отчёт Go vs Symfony — это именно те артефакты, которые показывают уровень Architect: каждое решение подкреплено $/мес и планом отката, а выбор Go обоснован не модой, а TCO и NFR — с честным блоком "где Symfony выигрывает". Ты построил Git-хостинг на Go: streaming, bulkhead, outbox, saga, scale 100k. Ты Architect. Добро пожаловать в реальный мир.»