5. System design. Забиваем гвозди микроскопом

Надеюсь ты, читатель, выжил и с пользой провёл этот новогодний фриз. Тонны салатов, вкусной еды и тотального разложения продуктивности не смогли сломить твою тягу к знаниям и развитию.

С учётом пройденного пути кажется, что всё готово. Требования выбиты, потоки данных распутаны, компромиссы между надёжностью, масштабируемостью и сопровождаемостью найдены, обратная связь продумана. В голове — стройная архитектурная концепция. Пора бы уже и код писать.

Вот он, тот самый момент, которого все мы так ждали. Выбор инструментов. Тех самых квадратиков, которые ты будешь рисовать на схеме.

Рука так и тянется к модному, к «стандартам индустрии». Хочется взять Kafka, потому что «все большие так делают». Запустить десяток микросервисов на Go. Запихнуть всё в Kubernetes и поставить сверху service mesh. А базу, конечно, ту самую очередную Distributed SQL, про которую читал на Хабре.

Астанавись. Выбор инструментов это не награда за проделанную работу. Это не «финальный босс», которого нужно победить эпической убервафлей. Это ещё одна серия компромиссов, где твоим главным врагом становится хайп, а союзником скучный, нудный документ с требованиями.

Почему «просто взять лучшее» не работает?

Потому что у каждого инструмента есть своя цена. И речь не только о лицензии.

_Цена обучения._ Команда знает PostgreSQL как свои пять пальцев, но ты хочешь CockroachDB для глобального шардинга. Готовы ли всё на месяцы замедлиться, изучая новые паттерны, отладку и тонкости?

_Цена эксплуатации._ Этот блестящий распределённый кеш самовосстанавливаться при отказе двух узлов? Здорово. А кто будет его мониторить, настраивать и разбираться, почему он вдруг начал терять 1% записей? Это твои SRE? Они уже согласны?

_Цена связности._ Каждый новый «квадратик» не просто функция. Это новая точка отказа, новый протокол, новый клиент в коде, новый драйвер, новый источник задержек в сетевых вызовах. Микросервис на Rust может быть быстрым, но если для связи с ним всем остальным сервисам нужна кастомная бинарная сериализация, ты добавляешь сложность во всю систему.

_Цена моды._ Инструмент, о котором все кричат сегодня, завтра может оказаться «legacy-технологией, от которой все уходят». Выбирая его, ты берёшь на себя обязательство по его миграции через N лет.

Так, как же выбирать то?

Отталкивайся не от хайпа, а от следствия.

Твоя отправная точка не список «крутых технологий», смотри на:

_Требования из твоего документа._ Нужна строгая консистентность? Забудь про eventual consistency stores для записи ядра. Нужны джойны сложных агрегатов? Привет, реляционные базы или тщательно спроектированные документные.

_Характеристики потоков данных._ Данные пишутся редко, а читаются всегда? Кеш, или read-реплики. Нужна гарантированная доставка событий между контурами? Пора смотреть на брокеры (прости господи). Но не «воткнём Кафку», а «нам нужен персистентный лог с консьюмер-группами».

_Выбранные компромиссы._ Пожертвовали скоростью записи ради надёжности? Инструмент должен давать сильные гарантии durability. Пожертвовали консистентностью ради масштабируемости? Ищите базу с tuneable consistency.

Пример: Выбираем хранилище для «ленты как у Threads».

Из требований помнишь: скорость чтения критична, актуальность лайков/комментариев может отставать на 5 секунд, рост медленный.

_Искушение:_ Взять Cassandra или ScyllaDB. Горизонтальное масштабирование, высокая доступность, запись быстрая. Вайбово, надо брать!

Реальность:  Модель данных: лента для каждого пользователя, которая часто пересчитывается. Это запросы по ключу (user_id) с range scan по времени. Cassandra неэффективна для range scans в рамках одного партишна. А денормализовать ленту для каждого подписчика это гигантский объём данных при малом росте.

Скучное решение:  PostgreSQL с таблицей feeds (user_id, post_id, timestamp), правильными индексами и репликой для чтения. На старте выдержит легко. Команда знает. При росте сначала тюнинг, потом партициирование, и только потом, возможно, шардинг. Инструмент соответствует реальным, а не гипотетическим потокам данных.

Критерии выбора: чек-лист перед тем, как «воткнуть»

  1. Решает ли он КОНКРЕТНУЮ проблему из моего дизайна? Не «как бы нам использовать Redis», а «нам нужно хранить сессию пользователя с TTL 15 минут».
  2. Соответствует ли он «бюджету боли» по сопровождению? Есть ли в команде экспертиза? Есть ли готовые Terraform-модули, Helm-чарты, дашборды в Grafana?
  3. Что происходит при его отказе? Это SPOF? Как он интегрируется в нашу обратную связь (circuit breakers, fallbacks)?
  4. Как он себя ведёт в наших паттернах доступа? Под нагрузкой, в наших хвостах распределения (p99, p999)?
  5. Насколько он нас загоняет в угол? Легко ли будет заменить его через год, если требования изменятся кардинально?

Что в итоге?

_Сначала_: принципиальное решение (например, «нам нужен персистентный лог событий»). _Потом_: варианты (Kafka, NATS JetStream, Pulsar, RabbitMQ с quorum queues). _Затем_: проверка по чек-листу против ТВОИХ требований и контекста (нагрузка, экспертиза, инфраструктура). _В финале_: конкретный инструмент.

Правильно выбранный инструмент тот, что тихо растворяется в архитектуре, позволяя системе работать так, как ты задумал. Не тот, что требует к себе постоянного внимания и героических усилий.

Архитектура рождается из требований и компромиссов, а не из списка технологий. Инструмент это всего лишь наиболее точная реализация твоего решения на данном этапе. Лучше взять простое и знакомое решение. Это лучше, чем выбирать модное без понимания, зачем.

А что дальше? Дальше код, деплой, метрики и... встреча с суровой реальностью. А почему это ещё не конец, я расскажу в следующий раз.

Читайте книги, пишите код, кушайте кашу.

Что ещё почитать: