Модуль 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), от бизнеса к коду:

  1. Бизнес-контекст и NFR: что строим (Git-хостинг), какие SLO/RTO (p99 < 100 мс, RTO < 5 мин, 100k RPS), сколько стоит час простоя (отток команд, неустойка).
  2. High-Level архитектура (C4 Level 2): общая схема — git-gateway (Go, exec.Command streaming), Storage Nodes, Go API, Kafka, Citus, CDN. Протоколы (HTTP/Smart HTTP, Kafka, gRPC к Storage Nodes), гарантии доставки (at-least-once через Outbox).
  3. Ключевые решения и Trade-offs: разбор 4 ADR с цифрами ($/мес, p99, RTO, % риска). Отдельный блок: почему Go, а не PHP.
  4. 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 10kGo 100kSymfony-FPM 10kSymfony+RoadRunner 100kГде выигрывает
Runtime ВМ5 × 4 vCPU10 × 4 vCPU15 × 4 vCPU (FPM)30 × 4 vCPU (RR)Go (×3 fewer ВМ)
vCPU суммарно204060120Go (×3 меньше CPU)
$/мес (cloud)$400$800$1 200$2 400Go (−65% vs FPM, −65% vs RR)
p991 мс1 мс (рантайм тот же)20 мс (FPM fork)2 мс (RoadRunner)Go (×20 vs FPM, ×2 vs RR)
Runtime сменяется 10k→100k❌ нет❌ нет (тот же бинарник)—✅ да (FPM → RoadRunner — отд. технология)Go (нет миграции)
Streaming packfileio.Copy (нет лимита)то жеPHP OOM >16 МБ → Nginx CGI workaroundто же workaroundGo (нет CGI)
Bulkheadchan 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 Жёсткие вопросы комитета (с подсказками)

  1. 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 модно».

  2. Финдиректор: «Сколько сэкономили на RoadRunner?» Подсказка: Go 100k = $800/мес, Symfony+RR 100k = $2 400/мес → −$1 600/мес = ~$20k/год. Плюс пропущенный операционный cost RoadRunner (licence/expertise). Но +T&M на Go-команду в первый год. Через 2 года Go окупается, через 5 — экономия $100k+.

  3. Техлид: «exec.Command vs 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-процесс жив.

  4. CTO: «Зачем Kafka, а не Redis Streams — у Redis Cluster есть?» Подсказка: Redis однопоточный (одно ядро на broker). При 100k событий/сек Redis Cluster не решит hot-партицию (партиционирование по ключу → hot repo → одна партиция). Kafka: RF=3, партиционирование по repo_id (HashBalancer), write-path на диск. Порог перехода: > 30k событий/сек или персистентность.

  5. Техлид: «Как шардировать Git-репозитории? Что если Storage Node умрёт?» Подсказка: шард по repo_id (consistent hashing → минимум перемещений при resharding). Каждая нода имеет read-реплику (rsync/git-fsck, RPO 1 мин). Падение ноды → запросы на реплику (directory-сервис переключает). RTO < 1 мин.

  6. Финдиректор: «А если не делать Citus — дешевле поднять ещё один PG-кластер?» Подсказка: два кластера PG = split-brain по данным, cross-cluster JOIN невозможен, double operational cost. Citus — единый кластер с прозрачным распределённым JOIN. TCO Citus ниже при равной write-пропускной способности.

  7. 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 был бы выбором».


🛠 Практика: финальная сборка и защита

Примерный план работы:

  1. Финализировать 4 ADR (ADR-GG2 exec/streaming, ADR-GG5 Cache/CDN, ADR-GG7a Go long-lived, ADR-GG7b Storage/Citus) с количественными Trade-offs.
  2. Нарисовать C4 Level 2 Git-хостинга на Go (профиль 100k): git-gateway (exec.Command), Storage Nodes, Go API, Kafka, Citus, CDN, протоколы, sync/async потоки.
  3. Собрать SLO/Cost-отчёт: точка деградации, fallback, ROI Go vs Symfony (таблица из §8.3), честный блок «где Symfony выигрывает».
  4. Ролевая защита перед 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.

Шаблон для ориентира:

Git-hosting на Go 100k: C4 Level 2 (сервисы, протоколы, потоки)

✅ Готово, когда:

  • Диаграмма в нотации 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».

Вопросы студенту:

  1. Почему «Go быстрее PHP» — не аргумент для архитектора? (Нужно: NFR «long-lived уже, нет fork = 10 ВМ vs 30 RoadRunner = −$1 600/мес». Не «быстрее», а «TCO ниже при NFR»).
  2. Чего не хватает? (Бизнес-контекста, количественных Trade-offs, ROI Go vs Symfony 10k/100k, плана отката, честного блока «где Symfony выигрывает»).
  3. Почему «Kafka, потому что Redis старый» — провал ревью? (Нужен порог: > 30k событий/сек или требование персистентности; иначе Redis Streams дешевле).

📖 Компромиссы (Trade-offs) — сводная таблица курса

Решение (10k на Go)Решение (100k на Go)Что gainedЧто lostТриггер перехода
Go binary (5 ВМ)Go binary (10 ВМ, рантайм тот же)×2 ВМ, не нужен RoadRunnerteam-skill Go10k → 100k (рантайм не упирается)
Redis StreamsKafka (segmentio/kafka-go)персистентность, партиционирование+инфраструктура, +задержка>10k событий/сек
Single PostgreSQLCitus (sharded)горизонтальный write scalecross-shard JOIN, сложностьwrite CPU > 80%
1 Storage Node10-20 Storage NodesIOPS на шард, изоляциямаршрутизация, репликацияI/O wait > 30%
Nginx proxy_cacheYandex CDNгео-распределение, 80% offloadнастройка invalidationпользователи в >3 регионах
chan struct{} bulkheadтот же (не меняется)без изменений—bulkhead и на 100k работает

Критерий сдачи курса: студент заполняет эту таблицу своими цифрами ($/мес, RPS, p99) и защищает каждый переход. Честный блок «где Symfony выигрывает» обязателен — отсутствие = защита провалена.


🧩 Проверь себя

1. CDN на /tree/{sha} на Go — зачем, если Redis-кэш (Модуль 5) уже?
2. Где Symfony выигрывает у Go (честный ответ)?
3. Почему exec.Command + io.Copy, а не libgit2 через cgo?
4. Защиту архитектуры на Go начинают с…
5. Зачем chan struct{} bulkhead (а не два пула FPM + Nginx upstream)?
6. Сколько экономит Go (10 ВМ, $800) vs Symfony+RoadRunner (30 ВМ, $2 400) при 100k RPS?

🤖 Инструкция для LLM (Coding Plan Assistant) — Режим Модуля 8

Привет! Я завершаю курс «Git Hosting на Go» и готовлюсь к защите.

  1. Дай短кий ответ (2–3 предложения): почему архитектор при защите сравнивает Go vs Symfony в TCO, а не доказывает «Go лучше»?

  2. Покажи пример Антипаттерна защиты («Go быстрый, Kafka модно») и попроси меня переформулировать в NFR и ADR с цифрами + честный блок «где Symfony выигрывает».

  3. Выдай финальный чек-лист архитектора Git-хостинга на Go (exec/Smart HTTP, chan bulkhead, Outbox, Cache/CDN, Saga, long-lived runtime, Storage/Citus, 4 ADR, TCO vs Symfony).

  4. Скажи: «Я готов выступить в роли комитета (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. Добро пожаловать в реальный мир.»