Привет, %username%! Очередная задача с собесов, которая является вариацией задачи 0002, но не менее интересная из-за своих нюансов и условий – она про архитектуру.

Условия

Определимся с базовыми условиями.

Задача: Один умный PM решил запилить стартап т.к. узнал, что «миру нужен мой продукт» – веб-сервис, который будет на определенный GET-запрос (что-то в духе /sqrt?d=9 ) отдавать кубический корень числа (число всегда целое), которое передано в запросе. И вот компания понимает что будет дикий рост до 100krps и надо спроектировать инфраструктуру, чтобы спокойно держать эту нагрузку и соблюдать SLA перед пользователями «99% запросов должны отрабатывать за <50ms». Бюджет примерно $1kk на выбор будущего провайдера и запуск инфраструктуры.

Решение

В собесах обычно в процессе решения подобных задач докидывают дополнительные вводные. Не старайся делать сразу идеальное решение в духе «построю свою гугол/яндекс/амазон», обгадиться будет гораздо больше шансов. Любой бизнес развивается итеративно, следовательно твоя задача решить текущие проблемы бизнеса, а не «сделать идеально».

Вот примерный чек-лист в таких ситуациях:

  • Фиксация озвученных вводных (так будет надежнее для обеих сторон);
  • Первичные пути оптимизации (где сразу подстелить соломки);
  • Разворачивание pre-prod контура (погонять нагрузочное чтоб посчитать требуемые мощности);
  • Оценка и утверждение затрат (тупо воспользуемся калькулятором);
  • Тестирование, Корректировки и Запуск;

Первая итерация

Давай пройдемся по нашему чек-листу и зафиксируем то, на что стоит обратить внимание:

  • У нас будет единственный endpoint на который будут слать только GET-запросы вида /sqrt?d=9. Со стороны приложения основная нагрузка это математические вычисления – следовательно нам надо в инфраструктуре делать упор на производительность CPU;
  • Первым местом для оптимизации лично я вижу возможность добавить кэш для посчитанных ответов (наверняка будут запросы с одним и тем же числом несколько раз), чтобы не считать одно и то же число по несколько раз – добавим Redis. Поскольку приложение с точки зрения бизнес логики довольно примитивное и by design умеет работать в multi-instance (не составит проблем одновременный запуск 10k копий приложения), запускать каждое приложение на отдельном хосте (даже по несколько копий) будет сильно накладно – значит наш выбор Docker и оркестрация (Kubernetes/OpenShift/etc). Load balancer – пара Nginx будут фронтами и балансировщиками запросов, а перед ними WAF/AntiDDoS. За LB собственно будет стоять Kubernetes (Nginx Ingress для обработки запросов и перенаправлении их в Pod’ы с приложениями). K8s будет отвечать за горизонтальное масштабирование нашего приложения, а в случае необходимости позволит докинуть еще worker-node и расти-расти-расти;
  • В условном ООО «Мишка на Сервере» заказываем пачку физических серверов (тарифная сетка простая, но есть кастомные варианты, запрашиваем то что имеется) – 3 средних под master-node k8s, 5 мооощных под worker-node-k8s, 2 под load-balancer, 1 под разные инфраструктурные штуки и нагрузочное, 3 под Redis. Ребята из этой компании дают возможность разместить хосты в ЦОДе на colocation и нас это устраивает. Рассматриваем эту конфигурацию как базовую версию prod и (если все будет отлично) задействуем ее в дальнейшем как stag/pre-prod. Согласовав все затраты и стоимости закупаем оборудование, разворачиваем k8s и вообще подготавливаем всё для запуска нашего сервиса. После чего мы можем погонять нагрузочное тестирование и выявить какая общая конфигурация площадки нам будет необходима для обеспечения требований бизнеса – «99% запросов должны отрабатывать за <50ms»;

Вторая итерация

Теперь, когда мы начали «танковать» наше приложение и получили представление о том, какую нагрузку может выдержать наша текущая площадка – допустим 10krps выдерживает одна worker-node-k8s при утилизации ресурсов сервера на 100% – мы можем рассчитать характеристики железа под prod-окружение. Мы можем заказать железо x3 по мощности (пусть будет не 32 CPU core, а 128 CPU core). Допустим нам такое новое железо дало возможность выдерживать нагрузку в 25krps на одну worker-node-k8s при 99% запросов <50ms. Значит что для обеспечения требований бизнеса в 100krps нам надо – сколько?правильно! – надо 4 worker-node-k8s. Отличная логика, которая нифига не сработает потому, что загрузка каждой ноды будет максимальная и как следствие наш prod будет полумертвый ибо будет захлебываться. Следовательно нам стоит заложить количество worker-node-k8s чуть больше четырех. Тут вполне логичен вопрос: А сколько вешать в граммах конкретно надо?

Конкретного однозначного ответа я тебе тут не дам, как минимум потому, что бОльшая часть таких вещей выводится эмпирическим путем. Поэтому дальнейшее описание будете еще более ИМХО, чем всё вышеупомянутое. Главное, что я хочу тебе показать – логику принятых решений с обоснованием этих решений.

Самый лучший вариант – оговорить этот нюанс с бизнесом. Допустим согласно требованиям бизнеса наш prod должен выдерживать всплеск нагрузки x1.75 – следовательно логично сделать следующие рассчеты: (100krps / 1 worker-node-k8s выдерживает 25krps) * (1.75 + 0.25), где:

  • 100krps – требования бизнеса по нагрузке;
  • 25krps – нагрузка, которую выдерживает один worker-node-k8s;
  • 1.75 – пиковый всплеск нагрузки, который должна выдерживать площадка;
  • 0.25 – запас по утилизации ресурсов, который должен присутствовать в случае пиковой нагрузки;

Путем сложных математических вычислений на специализированном оборудовании, предназначенном для данных целей… Короче на калькуляторе если посчитаешь с указанными выше данными, то увидишь цифру восемь. Потому что для 100krps надо четыре сервера, для пиковой нагрузки в 175krps понадобится семь серверов, для 200krps (это пиковая нагрузка плюс 25% от базовой – 175krps + 25krps). Это количество серверов – восемь – позволит prod-контуру устоять даже при двукратном росте нагрузки. Да, со скрипом, но полного отказа не будет, а главное что будет – время! Время на наращивание мощностей – добавить новые ноды в кластер K8s.

Третья итерация

Только сейчас я тебя (да и себя тоже) могу поздравить – ты запустил приложение! И теперь начинается самое страшное – сопровождение и внесение изменений. Одним из первых изменений, которое прилетает – «монетизация». Чуть более развернуто запрос звучит так: «мы хотим сделать 1krps per user бесплатными, а все что свыше этого – по подписке».

В такой ситуации можно пойти вот таким путем. Ты как DevOps Superman можешь запросить у разработки landing, куда будешь редиректить превысивших лимит в 1krps, настроить на LB лимиты запросов. На первичном этапе можно лимитировать по IP, но стоит не забывать что у нас есть люди сидящие за VPN/NAT/Corp gateway – за одним общим IP-адресом. Следовательно ты можешь «зацепить» не виновных. Поэтому так же стоит обговорить вариант внедрения личного кабинета (ЛК) в каком-либо виде – надо как-то позволить пользователям платить без регистрации и СМС. ЛК позволит отслеживать и лимитировать каждого пользователя индивидуально, например по активной сессии.

Разрабочики у тебя молодцы и делают отдельный микросервис авторизации, микросервис с личным кабинетом и прочие штуки. Ты под них масштабируешь кластер – он все еще должен держать ранее оговоренные нагрузки, при возросшем количестве сервисов (особенно не занимающихся вычислениями кубического корня).

С появлением ЛК появляется потребность в БД – данные пользователей надо же где-то хранить, так ведь? Под БД нужны либо отдельные сервера (чтоб HA кластер работал и не кашлял), либо ты учишься «готовить БД для K8s» и запускаешь базу как statefull application в кластере Kubernetes. С этой БД надо как-то делать резервные копии (не думаю что тут нужны какие-то объяснения), а резервные копии надо куда-то складывать.

Инфраструктура начинает расти с каждым запросом бизнеса – это нормально, так и должно быть! Не пытайся сделать на первой итерации HA-кластер БД, который не особо-то и нужен. Короче проговаривай с бизнесом (в лице собеседующего – ты ж не забыл что ты все еще на собесе) все то, что может быть истолковано неоднозначно.

Немного разного

В процессе рассуждений по данной задаче не стоит пренебрегать и желательно явным образом проговаривать такие вещи как:

  • Мониторинг – ну конечно мы все обмажем мониторингом, и сервера, и с разработкой научимся в бизнесовые/продуктовые метрики. Прикрутим Grafana, сформируем пачку дашбордов как для себя и разработчиков, так и для PM/PO, чтобы все в любой момент могли открыв дашборд посмотреть и понять «приложение работает, пользователи довольны»;
  • Логирование – агрегация логов, отдельный кластер для хранения логов, алерты на основании событий в логах. Ставим ELK/EFK/etc, настраиваем стриминг логов в выбранный агрегатор;
  • Трейсинг запросов – позволит отследить конкретный запрос пользователя и оценить «чойта он более 15sec выполняется всегда»;
  • Алертинг – в мониторинге который не кричит о проблеме нет никакого смысла, а подробнее читай заметку «Мониторинг: что/куда/зачем?»;

Итого

Вот разобравшись как решить подобную задачу на собесе, ты можешь смело налить себе рюмочку чаю за то, что «ты что-то шаришь в этом вашем айти». Не стоит думать, что ты должен на практике такие задачи решать в одно лицо и каждый день как орехи. Тут важнее понимание того, что ты в целом способен найти решение задачам подобного рода и можешь аргументированно объяснить свои решения и предложения в таких масштабах. И не стоит стесняться отвечать на вопросы прямо как есть: «Подобного рода задачи на практике не решал еще с нуля, но могу предложить вот такое решение…» Или обосновать выбор чего-либо своим опытом: «Доводилось работать на проекте X, где нагрузка была 3krps, характеристики были такие-то, правда запросы чуть тяжелее были, но если мы вот так масштабируем эти характеристики, то сможем выдержать требуемые 100kkrps». Короче не стесняйся признаваться, что чего-то не знаешь или не делал, при этом предлагай порассуждать над этой темой/вопросом.

Ну и не забывай: ты не идеален ровно так же, как и не идеален собеседующий – ни одна из сторон не может знать всего.


Если у тебя есть вопросы, комментарии и/или замечания – заходи в чат, а так же подписывайся на канал.