Всем привет, я — Сергей Бобрецов, CTO в Wildberries.  Сегодня Wildberries — самый большой маркетплейс в России и мы так часто заняты повседневным хайлоадом, что не всегда успеваем рассказать что за всем этим стоит: какие технологии и решения под капотом, как мы справляемся с адом черной пятницы и ужасами киберпонедельника. Стоит начать с того, что основным генератором прогресса в WB с самого начала и по сей день является фактор роста. По бизнес-метрикам мы растем примерно х2 каждый год уже много лет, а по техническим (количестуву запросов / транзакций / трафику / объему данных и т. д.) — рост может быть даже быстрее, и это создает множество вызовов: технических, архитектурных и организационных.  В итоге, чтобы запустить любую программу или систему в прод на мало-мальски долгий срок нужно обязательно учитывать кратный рост нагрузки, иначе совсем скоро придется вносить серьезные изменения в архитектуру или вообще все переделывать. Речь, конечно, не только про код – надо иметь стратегию масштабирования на уровне  железа, сети, данных, самого приложения, команды и т. д. 	 Сегодня я хочу рассказать немного про нашу инфраструктуру.  Много лет назад рынок онлайн ритейла только набирал обороты и технически Wildberries походил на сотни компаний вокруг: у нас было несколько серверов в коммерческом датацентре, на которых крутились сайт, бэкенд мобильного приложения и реляционная БД, в которой лежали почти все наши данные, а на соседних серверах хостились корпоративные сервисы типа 1С.  Все это как-то работало до тех пор, пока количество клиентов и заказов не стало быстро расти – сразу после этого систему начало колбасить в разных местах (чаще всего узким местом становилась БД). Появились даунтаймы, которые вредили бизнесу, напрямую влияя на конверсию.  И если с ростом нагрузки на приложение мы просто добавляли больше серверов, то с БД все было несколько сложнее. Сначала мы пытались прокачать ее: добавляли реплики для чтения, подкладывали все более мощные серверы, приглашали специально обученных гуру для оптимизаций, но потом все же приняли очевидный факт – пытаться масштабировать сетап любой БД после определенного предела слишком дорого и бесполезно, и предел этот гораздо ниже, чем кажется на первый взгляд.  Через некоторое время мы разделили данные по функциональному признаку и разложили в разные БД, при этом появился интересный побочный эффект – оказалось, что с таким подходом можно очень просто и удобно разделить и само приложение. Так мы пришли к микросервисной архитектуре.  Тут стоит заметить, что в нашем случае дробление приложения на более мелкие сервисы потребовалось не по техническим причинам: stateless-программа довольно просто и дешево масштабируется даже если это какой-то жирный монолит (кроме  совсем уж запущенных случаев) – только успевай ставить серверы. А вот масштабировать команду — такой подход очень помогает: небольшим сервисом может заниматься небольшая команда разработчиков, в которой гораздо ниже накладные расходы на коммуникации. Кроме того сервис с ограниченным функционалом, как правило, проще и имеет более низкий порог входа, что тоже снижает расходы. Итоги:  разделяйте данные и раскладывайте в разные места при первой же возможности, иначе будете страдать разбивайте функционал по разным программам, если хотите быстро масштабировать команду Число небольших сервисов быстро росло и когда оно перевалило за сотню, мы уперлись в доставку приложений в прод: в то время приложения деплоились прямиком на baremetal-серверы с помощью полуавтоматических систем доставки, и поддерживать нужный темп деплоя (а каждый сервис может  выкладываться много раз в день) стало дорого.  Поэтому мы сначала сделали самодельную оркестрацию LXC-контейнеров, а затем перешли на docker и попробовали набирающий популярность Kubernetes. В итоге нам понравилось.  Сейчас мы крутим множество кластеров K8S и используем его для запуска почти всех наших программ. Тем не менее не обошлось и без нюансов — подробно описывать не буду (эта тема потянет на отдельную статью), вкратце подведу итоги:  мы хостим K8S исключительно с L3 Networking — т.е используем L3 CNI-плагины (конкретно у нас — calico, но существуют и другие варианты) и BGP. Это позволяет легко и прозрачно приземлять входящий трафик, предназначенный конкретному сервису, именно на те ноды, на которых запущены поды этого сервиса, так как адрес сервиса анонсируется только с нужных нод.  Под сервисом здесь я понимаю именно service в терминах K8S. В случае с L2 сделать такое сильно сложнее. Кроме того L3 Networking гораздо лучше масштабируется и позволяет делать трюки типа анонса pod network, что дает возможность гонять трафик внутри кластера без оверлеев вроде VXLAN и даже без DNAT/SNAT — это очень актуально, когда нагрузка велика, а количество нод переваливает за несколько сотен.  мы почти не используем persistent storage в K8S. Гиперконвергенция хорошо звучит в рекламных проспектах, но в реальной жизни нужна какая-то очень серьезная причина, чтобы рисковать данными, а для экономии на железе существуют другие техники. Есть небольшие исключения, для которых мы используем local storage, но только там, где потеря данных некритична.  мы делаем много кластеров. Изначально у нас было по одному кластеру на каждый дата-центр, а растянутых между дата-центрами кластеров мы не делали вовсе. K8S отказоустойчив и отлично  масштабируется, но накладные расходы после определенного количества нод становятся тяжелее, а failure domain все больше и больше. В итоге мы начали создавать больше кластеров, а сейчас вообще встаем на путь наливания кластера под каждый сервис или группу сервисов, благо у нас  есть собственный инструмент для деплоя K8S — deadbeat.  Вопрос железа — всегда вопрос денег. Wildberries — частная компания, и для нас очень важна операционная прибыль. Парадокс в том, что самый простой путь сэкономить на железе — использовать больше железа.  Поясню: стандартный путь энтерпрайза, который ценит свои данные — покупать большие и надежные СХД, с резервируемыми контроллерами, проприетарными технологиями репликации данных между дата-центрами, самым умным в мире тирингом и прочими изобретениями вендоров.  Это крутые девайсы, и чаще всего на них можно положиться, но проблема в том, что они очень дорогие. По цене такой СХД можно купить кучу обычных серверов, а вопрос резервирования и шардирования данных решить на уровне самих приложений — в этом случае вы больше инвестируете в собственное приложение, но зато всю добавочную стоимость вендора оставите себе.  В некоторых случаях игра может стоить свеч даже если речь идет об одной-двух СХД, а если у вас большие объемы, то это то, о чем стоит всерьез задуматься.  Аналогичная история — с разного рода блейд-системами и прочим. Да, блейд-система выигрывает по плотности у обычных серверов, но зачем вам плотность в 2021 году? Power budget процессора не зависит от форм-фактора платформы и вы скорее всего упретесь в электричество, особенно в условиях когда в коммерческих дата-центрах аренда одного стойкоместа на 15 КВт чаще всего будет стоить дороже, чем двух по 8 Квт, что делает ценность высокой плотности блейд-систем по меньшей мере спорной.   Vendor lock-in — это зло. Вендоры хотят, чтобы их добавочная стоимость не доставалась другим вендорам. У  нас был случай, что СХД одного вендора работала только через коммутаторы этого же вендора, не говоря  уже про то, что вы не можете просто заменить диск на любой другой. С другой стороны в обычных серверах  такого себе почти никто не позволяет.  Унификация позволяет сэкономить: наше серверное многообразие свелось в итоге к двум типам нод — compute-нодам с горячими процессорами и большим кол-вом памяти и storage нодам c вместительными корзинами для дисков. Когда вам надо купить очень много одинаковых серверов вы, во-первых, получите  более низкие цены, а во-вторых, снизите косты на поддержание зипа и резерва.  Все пункты применимы не только к серверному оборудованию, но и к любому другому — к сетевому,  десктопам и т. д.  Чтобы размещать в нескольких датацентрах много железа – нужно обеспечить соответствующее количество сетевых портов. Тут все работает так же как и для серверов – лучше всего использовать много простых, но производительных  устройств, а стратегию масштабирования основать на общедоступных протоколах, не полагаясь на вендорские ноу-хау.   Еще очень важно построить логическую схему сети максимально устойчивой к отказам отдельных элементов, поэтому из классической энтерпрайз L2-сети, плохо масштабируемой, имеющей кучу ограничений и сильно связанных компонентов мы закономерно пришли к классической сети Клоза (или как  ее еще называют - Leaf & Spine, архитектуре в случае двухуровневой фабрики).   Было так: Стало так:  Углубляться в рамках этой статьи не буду, но смысл в том, что такое решение по многим причинам гораздо лучше масштабируется и строится на стандартных протоколах и механизмах типа ECMP, которые хорошо поддерживаются устройствами всех вендоров, что опять же позволяет экономить на железе.  Кроме того мы используем следующие приемы оптимизации расходов на сеть:  почти во всех стойках только 1 ToR коммутатор, компенсируем риски распределенными приложениями (см. следующий раздел) DAC-кабели вместо дорогих SFP-модулей  для максимальной унификации стараемся использовать взаимозаменяемые модели оборудования для разных уровней фабрики диверсификация вендоров  разумная и контролируемая переподписка Для того, чтобы все перечисленные выше пункты работали, одной инфраструктуры недостаточно — нужно писать программы с учетом нашей специфики:  дата-центр может отвалиться — делайте приложение доступным в разных дата-центрах  стойка может отвалиться – делайте приложение доступным в разных стойках все, что угодно может отвалиться или начать работать неустойчиво – делайте ретраи, curcuit breaker и т. д.  количество запросов будет расти намного быстрее, чем вы думаете – приложение должно легко масштабироваться добавлением инстансов  количество данных будет расти намного быстрее, чем вы думаете – разбивайте данные на куски любыми способами и раскладывайте в разные хранилища (см. раздел про БД и микросервисы)  там, где разбить не получается, предусмотрите шардирование, партиционирование, секционирование и другие подобные техники на этапе проектирования – потом будет слишком поздно  реплицируйте все, что можно. Если возможно и выбранное хранилище позволяет, то используйте разные реплики для разных целей – например, чтение с одних реплик, а запись в другие Настоящая консистентность нужна гораздо реже, чем вы думаете, но за ее обеспечение платится высокая цена. Подумайте, может быть в вашем случае стоит вместо дополнительных гарантий купить дополнительную производительность  Не бойтесь велосипедов — часто вместо использования какой-то внешней системы/зависимости эффективней написать свою реализацию под конкретную задачу.Например, в каких-то простых случаях вместо использования внешнего Redis можно закешировать что-то в памяти самостоятельно. Такое решение будет работать быстрее и обойдется дешевле в эксплуатации. Сервис по банальному перекладыванию JSON-объектов, который кроме самого себя состоит еще из RabbitMQ для очередей, etcd для хранения конфигурации, Redis для кеша, sql-базы для хранения самих JSON-объектов, сайдкаров, Nginx для балансировки и еще пары-тройки других крайне нужных компонент, выглядит очень круто, но на  этом его преимущества заканчиваются  Чаще всего принцип Time-to-Market очень важен — не пытайтесь написать самый лучший и самый красивый код в мире и избегайте лишних абстракций и оверинжиниринга. Лучшее — враг хорошего, а отсутствие каких-то абстракций почти всегда меньший грех, чем появление ненужных Все пункты выше очень важны. Без этих решений мы бы не смогли обрабатывать миллионы заказов в сутки и быстро расти.  Однако самое важное условие – это крутая и динамичная команда. Об этом в следующей статье. Крупнейший маркетплейс WILDBERRIES.RU