Наша почта:
В Москве:
РФ (звонок бесплатный):
Как построить МОЩНЫЙ прод и что этому может помешать?
{ Основатель и руководитель консалтинговой компании в fourmines.ru }
Владимир Федоренко

Видео доклада

Презентация спикера

Как построить МОЩНЫЙ прод
Предлагаем посмотреть на сеть с непривычной стороны — не глазами сетевого инженера, а глазами разработки и эксплуатации. Сетевики делают свою работу очень хорошо, но результат их усилий далеко не всегда позволяет проду работать так, как от него ждут. Разговор пойдёт о том, где именно теряются миллисекунды, во что эти миллисекунды превращаются в деньгах и почему сеть не гарантирует ничего.

Прод как чёрный ящик

С точки зрения человека, который смотрит на систему извне, она представляет собой чёрный ящик. Чтобы понять, почему что-то работает медленно или плохо, в этот чёрный ящик нужно заглянуть, потому что это не что иное, как сервис.

Возьмём условный маленький бизнес по производству смузи. На входе — поток бананов, на выходе — смузи. До смузи доходят не все бананы: часть требований превращается в ошибки. Нет места в очереди, склад закрыт, требование умерло в процессе обработки, ошибка обслуживания, собака съела домашку. Так или иначе, то требование, которое принёс пользователь, обработать не удалось.

Прод устроен ровно так же: входной поток, выходной поток и ошибки. Система массового обслуживания, как она есть.

Что внутри

Если посмотреть на сервис внимательнее, внутри обнаруживается много интересного. Сервис состоит из компонентов — воркеров, кронов, балансировщиков, — которых извне не видно, но которые там есть. И все они работают по сети: всё, о чём идёт речь дальше, основано на том, что данные передаются по сети.

Есть внешние зависимости — набор внешних чёрных ящиков. Внутри каждого воркера тоже есть компоненты: воркер — это не монолит. Там HTTP-сервисы, код на PHP, Lua, Go, операционная система и, конечно, база данных.

Самое интересное, что внутри кода тоже всё непросто. Код не работает сам по себе — он использует CPU, память, диск и внезапно сеть. Каждый из этих ресурсов тоже можно представить как систему массового обслуживания: она принимает входные запросы, как-то их обрабатывает и отдаёт результат. Для CPU это код, который он молотит; для памяти — обслуживание запросов CPU «дай кусочек памяти, найди, запиши»; для диска то же самое, только немного дольше.

Путь пользователя

Пользователь приходит по HTTPS в WAF. WAF отправляет запрос в дата-центр на балансировщик, балансировщик терминирует HTTPS и по HTTP передаёт на бэкенд, API-хост или куда-нибудь ещё. Дальше подключаются AI-сервисы, системы хранения — кэши, базы данных, OLAP-хранилища, которые призваны быстро обрабатывать большой объём информации. Плюс мониторинг, логирование и прочие сервисы. И ещё есть security perimeter, который тоже защищается с точки зрения сети.
Получается, что внутри дата-центра каждый входной запрос пользователя порождает много-много маленьких запросов к другим компонентам. Из всего этого интересна очень небольшая часть — высокочастотная часть прода, наиболее нагруженная с точки зрения сетевого взаимодействия: бэкенды, API и системы хранения.

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

На практике между ними появляется сетевой стек: ядро Linux, обрабатывающее сетевую коммуникацию, сетевая карта, которая передаёт данные в сеть, свитч, на приёме — снова карта и ядро, и только потом пользовательский уровень. Сюда же ложится модель OSI, а по факту — стек TCP/IP, который применяется везде.

Практика: живой прод

Живой мониторинг живого продакшна: примерно 100 RPS на влёте транслируются в 2500 запросов в секунду в базу. Запросы разные, каждый порождает разное количество обращений, соединений и трафика, но соотношение примерно 1 к 25.
Этот сетап несложно протестировать. Берётся 1 миллион запросов к базе, самых простых — SELECT 1. Смысл в том, чтобы тестировать не базу, а сеть: SELECT 1 на стороне базы не обрабатывается никак, кроме как парсером. Парсер видит SELECT 1, доходит до седьмого уровня, до уровня приложения, и отдаёт единичку обратно в сеть. Единичка бегает туда и обратно. Убивать сеть желания нет, поэтому тест идёт в один поток — такое можно запускать и на продакшне.
Дальше измеряется время каждого ответа, собираются перцентили, считаются средние. Очень простой тест — и очень показательный.
База и бэкенд находятся на дистанции одного хопа, практически прямое подключение через свитч.
  • 1.
    Среднее время ответа: 150 микросекунд
  • 2.
    Пропускная способность: около 8000 запросов в секунду
  • 3.
    Время теста на 1 миллион запросов: около 2,5 минут
Интересно, что traceroute показывает совершенно другие цифры. Это подтверждает известное соображение: traceroute не всегда даёт реальную картину, потому что traceroute — это ICMP, а работа идёт по TCP. По-другому база не умеет: OLTP-базы не настолько умные.

Немного о перцентилях

Перцентиль — это доля запросов, которые укладываются в указанное время. В 4,45 мс укладываются все 100% запросов — это максимум. В 150 микросекунд укладывается половина запросов — это медиана.

В целом всё быстро, но есть тяжёлый хвост. Скорее всего, это ретрансмиты: данные не удалось передать за один раз даже по одному хопу — переполнились буферы или ещё что-то помешало. Пакет восстановить не смогли и запросили retransmit. Важно то, что даже на прямом соединении тяжёлый хвост есть.
Traceroute показывает практически то же самое, даже чуть быстрее. А мониторинг показывает другое:
  • 1.
    Среднее время ответа: 311 микросекунд — в два раза больше
  • 2.
    Пропускная способность: около 4000 запросов в секунду — почти вдвое меньше
  • 3.
    Время теста: 7 минут
Вот это самое интересное. Среднее время выросло в два раза, а время работы системы — почти в три. Тест здесь эмулирует обычный воркер, который приходит и что-то делает: увеличение среднего времени ответа по сети провоцирует непропорциональное увеличение времени работы всей системы. Верхние перцентили при этом становятся заметно хуже.

Почему вообще важны перцентили

Современное приложение не делает один запрос. Чтобы отрисовать одну страничку — в вебе или в мобильном приложении — нужно обработать много запросов, и только часть из них идёт к статике. Остальные идут в бэкенд и дальше в базу. За один запрос на страничку внутри сети пролетает, может быть, даже не 25, а больше сотни запросов.
Это означает, что вероятность попасть на медленный перцентиль составляет уже не одну миллионную и не одну стотысячную, а доли процента или проценты. Если где-то есть задержки, конкретный пользователь получит медленную страницу — при том что по мониторингу в целом всё будет хорошо. Если открыть сайт и понажимать F5, скорее всего, всё откроется быстро. А вот тому конкретному пользователю, которому не повезло, будет плохо, и он будет жаловаться на сервис.

Геораспределение: Москва — Питер

Многие компании делают георезерв или стараются расшардить приложения на несколько дата-центров. Классическая для России ситуация — Москва и Питер, latency обращения около 10 миллисекунд между произвольными DC пятидевяточного уровня.

Если приложение в Москве, база упала, и есть база (или сервис, обрабатывающий API-запросы) в Питере, то 25 обращений на один запрос — это 250 миллисекунд только на коммуникацию. Быстрее 250 мс не будет вообще ничего. Для среднего приложения это очень много, потому что страница должна отдаваться пользователю за одну, максимум две секунды. А чтобы сохранить прежнюю пропускную способность, потребуется очень много бэкендов. Именно поэтому геораспределённые системы часто работают медленно — или их стараются не делать вовсе, размещая всё локально, а трафик роутя по другим принципам.

Приятная часть графика: перцентиль 99,99 — четыре девятки — укладывается примерно в 10–15 миллисекунд. Неприятная часть: из 100 тысяч запросов пять уходят за 200 миллисекунд.
О чём это говорит? О том, что если кто-то думает, будто сеть что-то гарантирует, он очень сильно ошибается. В среднем сеть работает действительно быстро. Но если строится по-настоящему отказоустойчивое приложение или приложение, работающее с очень требовательными потребителями — а это может быть не человек, а технология, которая требует ответа вовремя, — эти вещи нужно понимать. Пользователь подождёт, шесть запросов по 200 мс вместо 10 мс он даже не заметит. А вот система реального времени это учитывать обязана.

Ретрансмиты

Почему так происходит? Бывают ретрансмиты. Что-то отправили, ждём — ничего не прилетает. Отправили ещё раз, ждём — ответа всё ещё нет. При достаточно большом количестве ретрансмитов сервис становится практически неработоспособным. Пример из практики: график реального хостера, с которого клиента пришлось переводить именно потому, что сетевое время очень сильно плавало.

Репликация между дата-центрами

Есть два дата-центра, в каждом — бэкенд и база. Как транслировать данные?
  • Репликатор уровня приложения.
    Данные транслирует само приложение. Он очень умный: понимает, какие данные реплицируются и что это за данные, понимает приоритеты, хорошо обрабатывает ошибки, умеет обратиться в приложение и что-то подкорректировать, если пошло не так. Очень много плюсов.
  • Репликация через базу данных.
    База не понимает, какие приложения она реплицирует. У неё есть только два типа данных: данные и не данные. Что это за данные с точки зрения приложения — она не понимает. В приоритеты не умеет: работает или просто отвалилась. Обратиться в приложение, если что-то пошло не так, не умеет.
Так почему же для cross-DC используют базы данных? Потому что это адски дёшево. Репликатор нужно написать, настроить, отладить — это время и деньги. А мастер-мастер-репликация через базу — пять минут работы и потом два-три года страданий.

Проблема и решение

Пользователь приходит в один DC, пишет данные, они появляются в локальной базе. Потом приходит во второй DC — а данные застряли на уровне репликации, и в другом DC их не видно. Обычно в таких случаях говорят, что виноваты сетевики. В действительности есть огромный список проблем, которые генерирует не сеть: ошибки эксплуатации, ошибки разработки, кто-то что-то нажал — «у меня не открывается», и до свидания.

Решение: пользователя направляют через WAF и балансировщик на тот DC, где он уже был, — туда, где гарантированно есть его данные, если этот DC, конечно, не вышел из строя. Пользователь снова счастлив, он ходит в один и тот же дата-центр. Балансировщик при этом должен быть умным, его нужно этому учить.
Решение:
пользователя направляют через WAF и балансировщик на тот DC, где он уже был, — туда, где гарантированно есть его данные, если этот DC, конечно, не вышел из строя. Пользователь снова счастлив, он ходит в один и тот же дата-центр. Балансировщик при этом должен быть умным, его нужно этому учить.
В схеме active-passive все пользователи ходят в активный дата-центр, в пассивный не ходит никто — и ошибок нет. Переключение происходит по необходимости.

Жестокость мира геораспределённых приложений в том, что никто ничего не гарантирует. Гарантировано только одно: в какой-то момент дед Фернандо заведёт свой трактор, поедет и оборвёт очередной кабель. Если он не выехал и не порвал кабель сегодня, значит, обязательно сделает это завтра — просто трактор сломался. Достаточно посмотреть список аварий на магистральных кабелях за последний год, чтобы убедиться: дед Фернандо не дремлет.
Именно поэтому кидать одного и того же пользователя в разные дата-центры — не очень хорошая идея, особенно если они в разных городах.

Если же хочется active-active, можно взять один сет пользователей и отправить в один дата-центр, другой сет — в другой, дав людям возможность работать с одним DC. Тогда одни пользователи пишут свои данные в одном месте, другие — в другом, а когда всё починится, данные успешно проедут по репликации и задублируются в обоих дата-центрах. Возникнет так называемая eventual consistency. Хорошее слово: оно заменяет фразу «у нас всё сломалось». Не «у нас split-brain», а «у нас eventual consistency».

Как вырастить быстрый и надёжный прод

Знать свой прод и понимать latency.
Без цифр по реальной системе любой разговор о производительности бессмысленен.
Учиться сети.
Это знание никогда не будет лишним — вообще никогда, чем бы человек ни занимался: разработкой и особенно эксплуатацией, особенно если речь про обслуживание сложных систем.
Делать геораспределённые системы.
Это трудно, но спокойно. Когда падает один дата-центр, вы остаётесь не с ничем, а с чем-то работающим. Несмотря на проблемы с дистанцией и с переключением, это хороший выбор.
Если резервного дата-центра нет — сделать хотя бы DRP.
Достаточно посмотреть, сколько отказов дата-центров случилось за последнее время.
Ни один хостер больше не может считаться надёжным.
Он когда-нибудь да упадёт. Если важно в любой момент обслуживать своих пользователей — нужен георезерв и запасные системы.
Финал
Сеть — это не фон, на котором работает прод, и не зона ответственности исключительно сетевых инженеров. Несколько микросекунд на хоп превращаются в десятки процентов стоимости инфраструктуры, а редкие выбросы в верхних перцентилях превращаются в жалобы конкретных пользователей, которых не видно в усреднённом мониторинге. Парадокс в том, что чем хуже написано приложение, тем меньше сеть имеет значения, — и наоборот, чем быстрее прод, тем дороже обходится каждый лишний хоп.

Сеть не гарантирует ничего: ретрансмиты случаются, кабели рвутся, хостеры падают. Единственный работающий ответ на это — знать latency своей системы, строить геораспределённые схемы с липкой привязкой пользователя к дата-центру, иметь DRP и, что не менее важно, разговаривать: разработка, эксплуатация и сетевики видят одну и ту же систему с разных сторон, и целостная картина получается только тогда, когда эти стороны сложены вместе.
В Москве:
РФ (звонок бесплатный):
Наша почта:
GetNet
Остались вопросы?
Группа компаний VoxLink © 2011-2026