惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Девять испытаний роста нагрузки: от стартапа к приложению для 25 миллионов пользователей
Alex_Y_87 (X · 2026-05-14 · via Все публикации подряд на Хабре

Эта статья совсем не технический анализ, а увлекательный рассказ о том, как маленький, но очень перспективный стартап стал топовым приложением, а также о том, какие сложности встали на пути команды разработки, DevOps и тестирования X5 Tech.

Меня зовут Алексей Юрченко. В X5 Tech я пришёл как разработчик, занимался личным кабинетом сотрудника розницы, потом стал тимлидом команды продукта экспресс-доставки в мобильном приложении ещё на заре его существования. Постепенно команда выросла, разделилась на несколько, и сейчас я тимлид тимлидов команд, разрабатывающих функциональность доставки в мобильном приложении «Пятёрочка». Я отвечаю за часть мобильного приложения, связанную с формированием каталога, отображением карточек товаров, наполнением и оплатой корзины.

Пара слов о масштабах. На текущий момент мобильное приложение «Пятёрочка» находится в топе приложений электронной коммерции. Им ежемесячно пользуются порядка 25 миллионов уникальных пользователей. Суммарный RPS — около 10 тысяч. В нашем мобильном приложении собрана информация по ~25 тысяч магазинов и 15 тысяч товаров. А поиск в каталоге осуществляется примерно по 1 миллиарду документов, хранящихся в ElasticSearch.

Итак, поехали!

Экскурс в прошлое: как всё начиналось

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

Мы сразу заложили основные принципы нагруженного приложения: микросервисы как основа всего, полное покрытие метриками, асинхронность, кэширование на максималках. Какую-то функциональность разрабатывали сами, где-то задействовали сервисы других техкоманд из X5, а где-то и сторонние решения с рынка.

Весь код писали на Python, использовали FastAPI и другие популярные на тот момент фреймворки и технологии.

Первое испытание: latency

У нас появилась первая итерация продукта и заказы. Изначально заказов было мало, но потом наступил 2020-й год, пандемия. Люди перешли на удалёнку и начали массово заказывать товары из магазинов с доставкой на дом.

В этот момент мы столкнулись с одним из первых вызовов. Начали замечать, что при работе сервисов ощутимо рос latency. Гипотез, почему это происходит, было много. Ведь на latency влияет огромное количество факторов и не меньшее количество компонент в составе системы.

Одна из первых гипотез — нашим подам не хватает ресурсов, поэтому решили масштабироваться, чтобы снизить latency. Но гипотеза не сработала. Скейлились по-разному: сначала вручную, потом прикрутили автоскейлинг, основанный на HPA, отслеживающим метрики по утилизации CPU-памяти. Но… картина не менялась. CPU оставался статичен, тогда как latency мог внезапно взлететь до небес.

Решение: к нам едет… лимитёр

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

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

Когда это озарение на нас снизошло, мы подумали про интересную метрику «Количество одновременных запросов, которое может находиться в поде». И про возможность ограничивать её.

Сначала искали готовые решения на рынке, в том числе рассматривали Request Limiter из Uvicorn. Но по разным причинам нам ничего не подошло. Тогда решили разработать собственный инструмент.

Определили простые принципы:

  • Когда под получает запрос от клиента, лимитер должен определить, сможет ли под обработать этот запрос.

  • Если под может обработать запрос, лимитер его пропускает, а под обрабатывает и формирует корректный ответ клиенту.

  • Если под перегружен и не может обработать запрос, то он отдаёт клиенту ошибку 503: сервис недоступен. А клиент отправляет повторный запрос — retry.

Мы задумались, что хорошим решением было бы завязать эту метрику на скейлинг по HPA со стороны Kubernetes. Тогда если по агрегирующей метрике со всех подов окажется, что подов не хватает, то мы сможем их добавить.

Написали для этого свой middleware:

class Requests LimitMiddleware (BaseHTTPMiddleware):
  ...
  async def____call_(self, scope: Scope, receive: Receive, send: Send) -> None:
    ...
    if scope["path"] in self._ignored_paths:
      await self.app(scope, receive, send)
      return
    if self._counter >= self._unauth_limit:
      await Response(content="503", status_code=503) (scope, receive, send)
      return

    self._counter += 1

    try:
      await self.app(scope, request.receive, send)
      return
    finally:
      self._counter = 1

Принцип работы у middleware достаточно простой: когда в неё попадает запрос, внутренний счетчик инкрементируется на единицу. И декрементируется в момент, когда запрос выходит из пода и клиенту поступает ответ.

Указали определённые исключения. Лимитер работает далеко не по каждому пути, по которому приходит запрос. Есть ещё сервисные эндпоинты, например, ручки healthcheck’ов, readiness и liveness проб, callback’и от доверенных систем, по которым лимитер срабатывать не должен. Ведь если бы лимитер работал на этих ручках при поступлении запроса и формировании ошибки 503, то мы бы вхолостую перезапускали под со стороны контроллера Kubernetes, или пропускали важные обновления по заказам клиентов, что неправильно. Поэтому заранее обозначили пути, по которым лимитер гарантированно должен пропускать запросы.

Испытание 2. Балансировка

Метрика исходящих запросов пушилась в Prometheus. Мы обратили внимание, каким образом запросы распределяются по подам. И увидели, что небольшая их часть, 5-10%, сильно перегружены — содержат гораздо больше запросов, чем большинство других.

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

И тут мы вспомнили (барабанная дробь): а ведь принципов балансировки на самом деле несколько.

Решение: Least connection вместо Round Robin

По умолчанию везде используется стандартный round-robin, который как раз и даёт такую неравномерную ситуацию с перегруженными и недогруженными подами.

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

Внедрение лимитера и использование метрики одновременных запросов стало важным шагом, чтобы повысить наблюдаемость и прозрачность для команд разработки, тестирования и поддержки.

Испытание 3. Поиск в каталоге

В первые месяцы работы приложения периодически возникала проблема, когда при поиске в каталоге по запросу, например, «молоко», в ответ выкидывало ошибку.

Это запрос с мобильного устройства в бэкэнд приложения, где его обрабатывал под с поиском и по запросу обращался в ElasticSearch. ElasticSearch существовал в виде индекса, развёрнутого на n-ном количестве нод. А на нодах происходило то, что вы видите на графике выше: CPU внезапно упирался в потолок, загрузка в 100%. Понятно, что при таком раскладе никакой ответ мобильное приложение отдать не могло.

Начали думать, почему такое происходит. Сначала связывали это с резким ростом трафика, мол ElasticSearch с новыми цифрами не справляется.

Решение: принципы виртуализации

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

Победить эту проблему можно двумя способами. Либо гарантированным резервированием времени для виртуалок, на которых находится ElasticSearch. Либо дополнительно приложить усилия и правильно запроектировать хранение документов в ElasticSearch, чтобы повысить надёжность его работы.

Если в ElasticSearch всё хранится в одном индексе, то при появлении эффекта still time весь индекс становится неработоспособным.

Тогда мы разделили документы о товарах, их наличии и магазинах по n-ному количеству индексов. Теперь в случае still time эффекта на одной из нод, где расположен индекс, максимум один индекс становится неработоспособным. В этом случае лишь минимальный процент пользователей сталкиваются  с неработоспособностью приложения. Так мы повысили надёжность работы каталога в целом.

Испытание 4. Скорость ответов БД

Другая важная составляющая, влияющая на latency — база данных. Мы использовали bouncer, чтобы управлять пулом коннектов. Сделали из мастера в нескольких реплик, даже внедрили CQRS паттерн, чтобы можно было разделить запросы к мастеру на запись и на чтение к реплике.

Вроде, всё сделали красиво, но latency от PostgreSQL росло. А связано это было с дефолтными настройками базы, не приспособленными для работы реального высоконагруженного приложения.

Настройки statement timeout, времени нахождения сессии в определённых статусах и прочие, по умолчанию на сервере PostgreSQL выключены. Может сложиться ситуация, когда под направляет запрос в PostgreSQL, а база бесконечно долго пытается сформировать ответ. Параллельно с ожиданием будут накапливаться запросы, поступать дополнительные коннекты. И вот уже не спасает даже bouncer.

Решение: новые настройки для базы данных

Мы поняли, что нужно настроить базу данных иначе.

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

В какой-то момент мы начали использовать сессионные параметры. Когда под устанавливает соединение с PostgreSQL, он получает сессию и в этот момент может прокидывать конкретные настройки под конкретную сессию. В таком случае мы получаем сессию, чтобы сделать легковесный запрос и передать небольшие значения таймаутов. А если осуществляем миграцию, то передаём в сессию уже другие значения.

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

Испытание 5. Скрейпинг

Внезапно мы получили картинку как на изображении выше. Latency рос, снова! И в этот раз наша гипотеза, что это происходит из-за резкого роста трафика, оказалась верной.

Трафик мог вырасти в 2-6 раз, а было даже в 12. Мы начали искать причины.  Думали, что дело в релизе мобильного приложения, новой функциональности в ручках API. Но тогда новый трафик приложения был бы сопряжён с периодом выкатки и рос бы относительно плавно. А в нашем случае трафик рос моментально. Начали искать дальше.

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

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

Решение: не можешь победить, возглавь

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

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

Испытание 6. Проблемы с релизом

Прошло примерно полтора года с начала работы нашего проекта. Мы разобрались с основными проблемами, выросли как продукт, у нас много заказов и команд разработки. Мы начали больше коммитить, разрабатывать новую функциональность, и обнаружили проблему с релизами:

До релиза всё хорошо. Когда отдельный релиз случается, внезапно выскакивает, например,  ошибка 500 по важной для жизненного цикла заказа ручке и всё становится плохо. Откатываем релиз — всё снова налаживается.

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

Не секрет, что на сервере PostgreSQL есть различные процессы, накладывающие блокировки на целые таблицы баз. Такие блокировки удерживаются, например, в случае с автовакуумом, когда необходимо подчистить «мёртвые строки». И может оказаться, что… в момент релиза он сработает и заблокирует какую-то таблицу, на которую необходимо накатить миграцию.  В итоге накладываемая блокировка от миграции встаёт в очередь за блокировкой от автовакуума, а за ней встают в очередь все блокировки, накладываемые в ходе прилетающих в БД запросов на изменение данных.

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

Решение: дополнительная проверка

Чтобы решить проблему с неожиданными блокировками БД в ходе релизов с миграциями, использовали сервисную информацию из таблицы pg_stat_activity. Мы написали джобу, которая проверяет, есть ли возможность наложить блокировку и нет ли на текущий момент блокировок, наложенных на таблицы, в которые мы пытаемся сделать миграцию. В случае, если блокировки есть, пайплайн не даёт разработчику выкатить релиз. Если блокировок нет, релиз катится дальше.

А ещё мы воссоздали условия на проде в процессе тестирования. И теперь тестируем миграции под нагрузкой перед выкаткой в прод. У нас есть отдельный стенд для нагрузочного тестирования, на который мы подаём трафик, эквивалентный продовому, и в этот момент запускаем наши миграции.

После того, как миграции отработают, какое-то время ждём. Затем разработчик, ответственный за релиз, смотрит метрики, ошибки и принимает решение катить ли релиз.

Также, чтобы стабилизировать релизы, мы используем staged rollout/canary-подход: новая версия сначала получает ограниченную долю трафика, после чего команда смотрит на метрики и ошибки. Для пользовательских сценариев с состоянием отдельно контролируем консистентность маршрутизации.

Испытание 7. Аналитика

Постепенно мы накопили значительные объёмы данных. Коллеги-аналитики решили, что у нас содержится много полезной для развития продукта информации. Но, чтобы её использовать, надо её сначала выкопать.

Запросы, с которыми пользователи ходили в базы данных, были разными — простыми, сложными, лёгкими и тяжёлыми.

Тяжёлые запросы могли аффектить нашу БД. С этим нужно было что-то делать. Самое простое решение — запретить все пользовательские доступы к базе. Так мы и сделали.  

Но затем нужно было заменить такое радикальное решение элегантным и правильным.

Решение: отдельная система для пользователей

Мы решили обращать все пользовательские запросы в систему Data Management Platform (DMP), которая реплицировалась от наших реальных продовых баз с минимальным лагом не более, чем в минуту.

Так, все данные, необходимые пользователям, системным/бизнес-аналитикам и техподдержке достаются из DMP. При этом к реальным продовым базам, мастер-ноде и репликам никто доступа не имеет. И после того, как мы полностью переселили пользовательские запросы в DMP, мы существенно повысили стабильность и надёжность работы базы данных.

Испытание 8. Исторические данные

Как-то мы заметили, что пользователи слишком долго задерживаются на одном из экранов мобильного приложения, в истории заказов.

Так происходило из-за того, что у нас скопилось много данных по заказам и товарам, которые нам для работы по текущим заказам не особо и нужны. К данным по текущим заказам мы обращаемся, как правило, в течение нескольких дней, а затем они лежат в БД практически мертвым грузом. Поэтому мы решили разделить данные на холодные и горячие и стали их хранить по-разному.

Решение: разделение на холодные и горячие данные

Горячие данные положили в Clickhouse и перекладываем воркерами ежедневно по расписанию из PostgreSQL в Elastic. При этом, если, например, приходит запрос на получение списка заказов для отображения экрана с историей, мы на уровне пода синхронно делаем запрос с одной стороны в PostgreSQL, с другой стороны — в Clickhouse. Агрегируем ответ, и отдаём пользователю единое полотно со списком заказов и товаров.

Испытание 9. Метрики не совпадают с реальностью

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

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

Решение: резервный канал формирования метрик с ошибками

Чтобы повысить наблюдаемость по проблемам пользователей, у нас появилась идея сформировать резервный канал доставки метрик с ошибками и запросов в бэкэнд. При этом, если что-то на пути основного тракта приходит в негодность и не работает, то не отправляются ни запросы, ни метрики, а формируются тревожные события, например: не отображается каталог, невозможно добавить товар в корзину. Мы сделали независимый канал доставки клиентских диагностических событий, чтобы видеть проблемы, которые не доходят до основного backend-мониторинга и получили единую цельную картину из метрик бэкенда и мобилки.

Если в мобилке обнаруживали проблемы, которых нет на бэкенде, то мы сможем сделать предположение, с каким участком тракта доставки запросов до нашего бэкенда что-то пошло не так.

Выводы

Есть несколько универсальных правил, соблюдая которые, можно сделать highload продукт более надёжным, работоспособным и производительным.

  • Latency зависит от многих факторов. Чтобы взять его под контроль и понимать, что происходит, в каждом отдельно взятом случае нужно рассмотреть ситуацию «под микроскопом». Зачастую ответы не найти ни на форумах, ни в документации. Используйте метод проб и ошибок, чтобы докопаться до истины. Тот опыт, который вы получаете, строя Highload проекты, зачастую является уникальным.

  • Часто не стоит решать проблему «в лоб», иногда лучше расслабиться, подстроиться и попытаться адаптироваться к условиям её появления.

  • Если вы что-то делаете вручную более одного раза, это означает, что процесс пора автоматизировать.

  • О любых проблемах должны узнавать первыми не пользователи, а разработчики.

Также можно прочитать статьи о том, как X5 Tech масштабирует инфраструктуру и разбирается с нагрузкой в больших системах:

Утечка, которой не было: как Next.js раздувает RAM в Kubernetes
Как мы управляем инфраструктурой на более 1000 серверов при помощи Ansible