Управление поиском цен на отели в сервисе бронирования — это как ремонт работающего двигателя. Работа с запросами происходит в реальном времени, и простого варианта «отель N на майские» недостаточно, чтобы получить то, что нужно. Скрейпинг, массовые запросы, настройка баланса просмотров и бронирований при работе с самописными базами поставщиков и их ограниченными серверными мощностями — задача почти невыполнимая. Почти…
Привет, Хабр! Меня зовут Иван Чернов. Я 12 лет в IT, 6 из них работаю в «Островок!». Успел побывать и бэкендером, и СТО. Затем снова вернулся в бэкенд. А сейчас мутирую в системного архитектора. В свободное время веду подкасты: «Два Ивана (название обсуждается)» про IT и «Вечерний даунтайм» про настолки.
В этой статье расскажу, как справиться с нагрузкой и поддерживать бесперебойную работу системы. Рассмотрим масштабирование Redis, использование Aerospike, фильтр Блума и решим задачку со звёздочкой. Поговорим о маленьком кусочке схемы, который непосредственно работает с поставщиками в поиске. Это самая нагруженная часть, где возникают наибольшие проблемы с highload. Но именно она нужна, чтобы пользователи получили лучшие цены.
![](https://habrastorage.org/getpro/habr/upload_files/65a/ef7/f63/65aef7f634029aecf1525a321369bc15.jpeg)
350 технических сотрудников «Островка!» обеспечивают работу высоконагруженной системы из 2,5 миллионов отелей. У нас больше 30 тысяч бронирований в день и 210 тысяч поисковых запросов на сайте в секунду.
Где взять столько отелей
Единой базы отелей во всём мире — не существует! Во-первых: их очень много. Во-вторых: люди пока не могут договориться об этом. Есть крупные сети типа Marriott, Wyndham или Holiday Inn, но они скорее исключение. Да и филиалы их часто ведут собственные базы в Excel, без привязки к остальным отелям сети.
Мы можем создать свою площадку и предложить отельерам зарегистрироваться, чтобы номера бронировали онлайн. Так работает «Островок!» и другие сервисы бронирования, например, Booking.
Вы можете вбить в поисковике запрос: «Booking, system design, interview», и увидеть примерно такую картинку:
![](https://habrastorage.org/getpro/habr/upload_files/e22/f09/03c/e22f0903c06b290c414f927e2385354b.png)
В этой схеме много всего. Разобьём её на логические блоки, чтобы стало понятнее:
Система для менеджмента отеля: регистрация, заполнение информации — описание, адрес, фотографии.
![](https://habrastorage.org/getpro/habr/upload_files/ed9/cc9/b67/ed9cc9b67af6d6f1c291965ef5f8a9ec.png)
Через Kafka переход в Elasticsearch на конкретный поисковый кластер, например, московский.
![](https://habrastorage.org/getpro/habr/upload_files/25d/5d3/c27/25d5d3c27a567f33cd255f5776573d71.png)
3. Теперь остаётся только забронировать отель и списать деньги. Кажется, что всё работает хорошо. Но на самом деле нет.
![](https://habrastorage.org/getpro/habr/upload_files/7a4/115/ad6/7a4115ad69dd1c35183a21d922139eeb.png)
До появления Booking в конце 90-х отельеры сами вели инвентарную базу и менеджерили отели. Часто сами писали софт. Поэтому многие отели не появляются на Booking автоматически и вообще не пытаются туда попасть. За них это приходится делать другим компаниям. Они приходят к отельерам и предлагают сделать интеграцию: выкачивать данные, передать их дальше и получить свою комиссию за посредничество. А если вдруг в онлайн приходит больше бронирований, то запрашивают скидки. Так в схеме появляются поставщики. А как только поставщиков для одних и тех же отелей становится несколько, возникает необходимость в консолидаторах.
Идея в том, чтобы взять всех поставщиков, найти лучшие по цене предложения и именно их предложить пользователю. Как раз этим и занимается «Островок!».
На первый взгляд принципиальная архитектура такого решения напоминает предыдущую.
![](https://habrastorage.org/getpro/habr/upload_files/bde/766/968/bde76696861d3bccd6db6aa0b93e79a8.png)
Точно также, как на прошлой схеме у нас есть блок управления отелями, поисковый кластер и сервис для бронирования. Отличие в том, что появляется новый кубик — это поставщики, которые отдают данные.
Мы хотим выстроить Anti-Corruption Layer, чтобы наша внутренняя бизнес-логика и работа поставщика не пересекались. Для этого зеркалим сервисы по кубикам:
Скачиваем данные про отель — адрес, фото, название.
Эти данные меняются довольно редко, поэтому их можно скачивать раз в неделю. Но есть две сложности:
Поставщик может отдать zip-архив с XML на 4 ТБ, и разбирай его сам как знаешь.
Нужно как-то разобраться с тем, что находится в zip-архивах от разных поставщиков. Отель Космос и Космос-отель — это может быть один и тот же отель. Наша задача — понять это и сделать все полученные данные единообразными.
Организовываем поиск. Он делается по запросу вроде: «Дай мне цены на майские праздники на отель Космос». В ответ на запрос выводится информация о том, какие цены доступны.
Бронирование. Этот этап ещё более сложный, потому что в случае бронирования на Booking нужно следить за консистентностью между внутренними сервисами. В нашем случае мы должны учитывать, что обращаемся за данными вовне.
Ловушка хороших цен
Проблема цен в том, что это игра с закрытой информацией. Вы можете отправить запрос «отель Космос на майские» и получить ответ. Но, конечно, это не будет zip-архив со всеми ценами и календарем. Можно попробовать скрейпить: взять все отели, все 365 дней в году и направлять запросы. Но это не так просто, как кажется.
В договор включено конкретное соотношение между просмотрами и бронированиями, и вы должны его выдерживать. Если этого не делать, цены либо будут выше, либо вас отключат. Поэтому задача так не решается.
Из-за этого в нашей сфере существует самая плохая корреляция во всей индустрии. Она заключается в том, что чем лучшие цены от партнера вы видите, тем, как правило, он более технологически не развит. Яркий пример — отели all-inclusive в Турции. Они рады продаваться через туроператоров просто по запросу номера телефона и не хотят никуда подключаться. У них в офисе стоит сервер, который выдерживает 10 запросов в минуту, и больше ничего с этим сделать нельзя. А подключить надо, ведь пользователям это нужно.
Если резюмировать, то у проблемы такой сетап: большое количество подключений, большое количество отелей, большое количество поисков, и для всего этого нужна схема.
На первый взгляд проблема типовая, и очевидным кажется решение с кэшем: когда вся архитектура сводится к тому, чтобы при поисковом запросе пользователя мы обращались к кэшу, а если это не сработает, то к поставщику.
![](https://habrastorage.org/getpro/habr/upload_files/6a6/4c3/645/6a64c364536987177a2051331c002007.png)
Когда мы слышим слово «кэш», то обычно думаем о Redis. Потому что это простое, понятное, быстрое in memory решение, в которое можно вместить всё, что угодно. В Redis есть множество разных нестандартных структур. Очевидно, что при всех плюсах хочется сразу запустить Redis в прод. Но, сделав это, увидим, что всё горит. Кэш, поставщики и наш внутренний сервис запросов не выдерживают. В результате приходится постоянно решать возникающие проблемы. Расскажу, как это сделать.
Формируем ключ
Начнём с кэша. Так как это key–value база данных, давайте разберемся, как вообще формируется ключ.
Понятно, что в ключ входят отель и даты, но ещё туда входят:
Количество гостей + специфика их размещения. Два гостя в двух номерах — это не то же самое, что двое в одном номере. Как минимум будет разница в количестве уборок и обслуживании. Отдельный нюанс — дети, которые могут спать с родителями в отдельной платной или бесплатной кроватке. Цены на все эти варианты различаются.
Календарь, в котором поиск на 7 дней ≠ 7 поискам на один день. Кажется, что можно просто выгрузить цены по одному дню и сложить, но так не работает. С номерами , как с ценами на лимонад — двухлитровая бутылка дешевле, чем две по литру. Также и здесь: выгоднее искать отель на семь дней. И сами отельеры стимулируют людей заезжать не на один день. У них даже существует регламент: минимум три дня. А чем дольше, тем лучше, потому что в этом случае они несут меньше операционных расходов на уборку, оснащение номера необходимыми расходниками. Да и легче избежать длительных простоев номеров, когда заезды длинные. Профит!
Поиск в глубину. Искать на майские в апреле ≠ искать на майские в феврале. Доступность и цены будут кардинально отличаться. Не так, как в такси, когда при высоком спросе поездка дороже. Но в отельном сегменте тоже свои нюансы. Если вы посмотрели все цены на полгода вперед, то, скорее всего, в ближайшую неделю они меняться не будут. Но при поиске день в день нужно постоянно следить за обновлениями.
Всё это приводит к определенному соотношению между тем, сколько мы читаем и сколько пишем.
Вывод из этого простой: много ключей в один большой вертикальный Redis не вместить. А значит, систему нужно делать распределённой.
Масштабируем Redis
Есть несколько вариантов масштабирования, разберем их подробнее.
В мануале Redis написано, что есть дефолтная схема master ←→ replica. Вы можете установить Redis, сделать к нему N реплик, и всё будет хорошо. Казалось бы, с десяти реплик можно читать в десять раз больше. Но проблема в том, что схема масштабирует только чтение. Реплики замедляют запись, а хочется, чтобы везде были консистентные данные. Но так не получится.
Шардирование по поставщикам. Попробуем решить проблему с бизнесовой точки зрения — выдадим каждому поставщику по Redis или даже по микросервису.
И вот у нас подключено 250 поставщиков, и если каждому поставить по одному Redis и по две реплики, получится 750 контейнеров, подов и виртуалок. Как такое количество оркестрировать — отдельный вопрос.
Но и это не всё, есть ещё одна сложность. Redis требует установить дополнительный софт: Sentinel, twemproxy, etc, если нужна high availability. То есть нужно много софта, ресурсов и инженеров, которые должны обслуживать кластер.
Нашу компанию не устраивал Total Cost of Ownership этого решения. Слишком много подвижных частей, которые могут упасть.
Требования
Чтобы найти альтернативное решение, определили требования, и получился такой список:
Много ключей.
Толстые данные, когда спрашиваешь «отель Космос на майские» и получаешь цену, количество номеров каждого типа: стандартов, люксов, номеров с кроватями разной конфигурации.
Частое обновление ключей (WPS ~ RPS).
Отказоустойчивость.
Низкий Total Cost of Ownership — потому что хотим дёшево.
Выбор DB
Мы сузили область проблем, а значит следующим этапом могли сузить область решений. Для этого просмотрели топ-10 на сайте DB-Engines:
![](https://habrastorage.org/getpro/habr/upload_files/9c8/2ab/466/9c82ab46621351423cf1d63de9f7db65.png)
Redis — на первом месте. С ним всё понятно.
DynamoDB от Amazon и CosmosDB от Microsoft — идут следующими. Для меня они в одной категории, хоть и по-разному устроены. Ведь их основной смысл в том, чтобы вы платили больше денег за большую производительность. А это, увы, не соответствует критерию «дёшево».
Memcached — только in-memory, а ведь надо выстраивать pipeline.
etcd и Aerospike — выглядят интереснее. Когда мы искали решение, etcd не было. Мне кажется, что он на седьмом месте просто потому, что используется в K8s.
Aerospike — на него мы обратили внимание и пошли читать официальные отчеты.
Создатели Aerospike обещают:
![](https://habrastorage.org/getpro/habr/upload_files/585/95e/227/58595e2277211d2c895b2112de29a21b.png)
Они утверждают, что в сравнении с Redis потребуется в 10 раз меньше серверов для обслуживания того же потока. У них 300 тысяч TPS. И если Redis нужно 120 серверов, то Aerospike хватит всего 12, причём аналогичной конфигурации — профит!
Как устроен Aerospike?
Чтобы разобраться, как такое возможно, рассмотрим устройство Aerospike подробнее.
Связан со всеми нодами
![](https://habrastorage.org/getpro/habr/upload_files/c16/033/fa1/c16033fa11f423a039674d198f9c418d.png)
Клиентский слой Aerospike устанавливает connect с одной из нод кластера. Но дальше получает информацию о всём кластере и поддерживает connect уже со всеми нодами. Это нужно чтобы при выбивании ноды автоматически происходил connect с другой и приложение работало беспрерывно.
Health-чеки, встроенные в Aerospike, есть как внутри кластера, так и внутри клиента. То есть Aerospike сам держит эту информацию. А при отсутствии сетевой доступности к клиенту роутится другим путём и записывает в кеш с информацию о доступности отеля. Но, по сути, может записывать всё, что угодно.
Раскидывает данные по нодам
![](https://habrastorage.org/getpro/habr/upload_files/bb0/31a/5c4/bb031a5c4cb21f37039b6befe1d8e7ed.png)
Внутри Aerospike — 4096 партиций. Мы берём хэш от ключа и начинаем их раскидывать. При этом партиции мапятся на разные сервера. Например, партиция 1 — это мастер, который попадает на ноду 1. Но она может выступать и репликой для других данных. То есть данные раскидываются по всему кластеру, где выступают то мастером, и в них можно писать, то репликой, и их можно только читать. Это помогает в случае проблем с конкретной нодой.
Автоматически перераспределяет ноды
![](https://habrastorage.org/getpro/habr/upload_files/34d/104/6f3/34d1046f35b0ca97922f791452b82127.png)
Например, выставили replication factor 3, и при наличии одного мастера и трёх реплик выпадает эта нода. При этом сам по себе кластер больше, и какие-то ноды просто не использовались. Когда всё падает, нода встаёт недостающей репликой, и начинается миграция данных, которые до этого были успешно скопированы. Мы говорим, что реплика — up, и начинаем с неё читать.
Модель хранения
![](https://habrastorage.org/getpro/habr/upload_files/de1/062/2c7/de10622c73e2c6b936b9fd5fadcd4b29.png)
В Aerospike можно хранить значения и в памяти, и на диске. А когда данных много и они толстые — модель гибридная. То есть ключ хранится в памяти, а запись идёт непосредственно на диск. При этом Aerospike рекомендуют либо отдавать /dev/sda, и тогда мы сами знаем, как и что будет там лежать. Либо использовать файловую систему с Direct Access.
В итоге оказалось, что весь наш поиск держат всего 8 серверов по 48 ядер и 128 гигов оперативки. Туда суммарно прилетает около 30 тысяч TPS — учитывая и запись, и чтение. Всё это хранят 400 миллионов ключей (с учётом replication factor) на каждой из точек.
![](https://habrastorage.org/getpro/habr/upload_files/087/eeb/fd1/087eebfd10c6ef15d59f68587dcf00db.png)
Это решение казалось нам более годным, чем гипотетические 750 от Redis. Поэтому мы изменили схему:
![](https://habrastorage.org/getpro/habr/upload_files/bd6/1d2/7cc/bd61d27cc330e49816b4843c7d213671.png)
Мы использовали Aerospike в качестве кэша и нас всё устраивало. Но в один день всё изменилось…
Бум!
Aerospike тоже взорвался! Пошли разбираться, смотреть графики и оказалось, что:
Кластер перестал принимать новые записи.
CPU / IOPS / % на диске — в норме.
Память ушла в потолок.
Нужно было внимательно изучить данные и понять почему ключ Primary Index перестал помещаться в оперативке. Но у нас много ключей, и нужно было понять, какие именно там лежат и откуда они берутся.
У нас два вида ключей:
Доступные отели, где есть цена, комнаты, тарифы с завтраком, без завтрака и так далее.
Недоступные отели — очень странный ключ, показывающий, в какие даты отель недоступен. То есть когда при запросе к поставщику: «Дай мне отель Космос на майские», вам ответят, что его нет. Это единственная информация, которую мы получили от поставщика. Поэтому вы не узнаете, какие там были номера и по каким ценам.
Когда мы изучили графики, то увидели, что в целом поставщики со сплитом 50 на 50 отвечают, что половина отелей доступна, а половина нет.
![](https://habrastorage.org/getpro/habr/upload_files/7d5/297/d54/7d5297d5482ef0fd604cfb13a6bfb897.png)
Мы посмотрели оперативную память Primary Index и обнаружили, что половина ключей — пустые. Они содержат лишь информацию о том, что отель недоступен и не используется. Так мы столкнулись с ограничением Aerospike — использованием 64 байт в оперативке, чтобы отметить, что в базе есть этот ключ. И ожидаемо упёрлись в лимит.
Новое решение — фильтр Блума
Нам нужно было придумать, как хранить ключи по-другому. Мы хотели, чтобы новая структура умела проверять вхождение элемента, делала это быстро и была эффективна по памяти. Потому что просто так в Aerospike мы её положить не можем.
У нас было несколько вариантов:
Хэш-таблица. В Computer Science нам сказали бы, что нужна хэш-таблица, но Aerospike и был такой хэш-таблицей.
Не подходит.
Set. Можно было выделить один ключ, который будет множеством. Так мы обеспечим быструю проверку. Но это не так хорошо с точки зрения памяти, как кажется. Ведь ключи тоже большие и формируются из большого количества параметров.
Тоже не подходит.
Фильтр Блума. Это вероятностная структура, которая состоит из битовой маски и набора хэшей. Когда мы хотим записать элемент, он прогоняется через набор хэшей. В результате мы обновляем в битовой маске соответствующие позиции. Потом, когда хотим проверить, проходимся тем же алгоритмом. Если видим, что во всех позициях единички, то считаем, что этот ключ там есть.
![](https://habrastorage.org/getpro/habr/upload_files/69b/017/24a/69b01724a652797c0f22e9f711cf08e7.png)
Но может произойти коллизия. Когда какой-то другой ключ на тех же хэшах в эту же позицию даст единички. При коллизии мы получим ответ «ОК». Подтверждение, что ключ, которого на самом деле нет, там есть.
Хорошо, что математики рассчитали, как контролировать эту вероятность:
(1-e-kn/m)k
Здесь k, n и m зависят от того, насколько большая битовая маска, сколько вставок предполагается и какой набор хэшей будет. Если мы хотим 0,01% ошибок, можно вычислить эти параметры исходя из того, сколько вставок ожидали.
Формируем ключ
После того как разобрались с коллизиями, сформируем сам ключ. В него входит отель, количество гостей, поставщиков и даты, которые каждый день меняются. Например, мы записали, что отель недоступен с 24 по 26 июня. Но недоступность может растянуться на большее количество дней.
Вот так отельеры ведут учёт доступности.
![](https://habrastorage.org/getpro/habr/upload_files/486/0c9/0dd/4860c90dd29140be69e7f0d1f74ddcd0.png)
Это так называемая шахматка, где по строкам прописаны номер и тариф, а по столбцам — даты. А ещё есть небольшой pop-up с двумя видами ограничений:
За какое время можно найти тариф — например, забронировать номер возможно минимум за неделю до заезда.
Минимальное/максимальное время проживания. Например, определённая цена доступна, только если ты заехал минимум на 5 дней.
Получается, наш ключ из дат должен хранить количество дней до заезда и количество дней проживания, потому что эта информация не будет обновляться между строками дат. Значит, мы можем хранить её у себя дольше.
Где и как хранить
С формированием разобрались, осталось выбрать где хранить ключ.
Наверное, можно в оперативке, ведь это просто битовая маска. Преимущества:
Есть готовые библиотеки для большинства языков. У нас эти сервисы написаны на Go.
Работает быстро и компактно.
В Go любят делать бенчмарки по локациям, чтобы всё работало быстро да гладко. Но получаем минус. Мы не шарим знания между нодами. У нас есть кластер на 40 точек. Каждый хранит информацию о недоступных отелях у себя, и это не очень хорошо.
Redis даёт:
Встроенное решение с упором на % ошибок;
Шаринг знаний между серверами;
Latency на сеть.
У Redis встроенная структура, которая заводится как фильтр Блума. Она сама раскидает, что и какого объема хранить. С помощью фильтра мы шарим знания о недоступных отелях, и наша схема улучшается.
![](https://habrastorage.org/getpro/habr/upload_files/768/6d7/c5b/7686d7c5b3da9a7029328036ee70df63.png)
Теперь у нас два кэша, чтобы всё работало в два раза быстрее. Redis используется только для недоступных отелей, а Aerospike — для доступных номеров и тарифов, потому что это толстые данные.
Для этого мы строили эффективный кэш, но осталась проблема с запросами.
Запросов всё ещё много
Поставщики сказали, что у нас всё ещё много запросов. И мы начали думать, почему так происходит.
![](https://habrastorage.org/getpro/habr/upload_files/508/2f8/461/5082f84615140e1861a178023cd0cadd.png)
У нас есть один экземпляр из кластера, второй экземпляр из кластера. Один человек искал на майские отель №1, второй искал на майские отель №2. К поставщику отправили два запроса.
Шардирование поставщиков
Представим, что вместо парадигмы того, что делаем по микросервису на поставщика, мы используем библиотечный подход. У нас есть ноды, умеющие обрабатывать любые запросы, которые мы распределяем равномерно:
![](https://habrastorage.org/getpro/habr/upload_files/2da/4dd/233/2da4dd233a3efbcc3e08fc9122326a18.png)
Каждый раз при поступлении запроса мы вычисляем, в какую ноду его отправить, и отправляем.
Это выглядит так, как будто мы не решили проблему из предыдущих пунктов.
Но если выкинуть из алгоритма отель, то можно понять, что на первую ноду у нас попадут: один и тот же поставщик, одни и те же даты и два разных отеля, которые можно объединить.
Я говорю про консистентное хеширование. Но и здесь скрывается минус — у нас есть нода, которая вообще ничего не обрабатывает, а мы хотим, чтобы всё происходило более-менее равномерно.
Rendezvous hashing
Используем алгоритм Rendezvous hashing. Он улучшает консистентное хеширование:
![](https://habrastorage.org/getpro/habr/upload_files/f4a/ba3/16c/f4aba316cfae2af3be7c1ee20cbe936b.png)
У нас есть набор клиентов и набор серверов. Мы начинаем вычислять хэш не только от ключа, но и передаем сервер как параметр. Выбираем самый большой вес и отсылаем запросы туда.
Такая схема лучше:
Можно менять веса серверов. В зависимости от того, на какой сервер пришёл запрос, мы можем проставить ему вес. Это позволяет распределять поставщиков. У нас единый кластер, который с точки зрения operations менеджерится как один. С точки зрения разработки он тоже менеджерится как один. В нашей конфигурации поискового кластера находится десяток серверов. Если мы будем отправлять запросы в режиме round robin (каждый запрос на новый сервер по списку), то наш алгоритм объединения не будет оптимальным. Поэтому надо реализовать кастомный алгоритм балансировки запросов с клиентской стороны. Для этого используем алгоритм рандеву. Так запросы для одного и того же поставщика и дат будут приходить только на подмножество всех используемых серверов с автоматическим фоллбеком в случае недоступности.
Автоматическое перенаправление. Мы получаем фичу по автоматическому перенаправлению. А из-за того, что вычисляем хэши, если у нас выпадает какой-то сервер, то мы просто выбираем следующий, и всё снова хорошо.
Платим скоростью работы — O(N) / O(logN). Вычисляя хэш от каждого сервера, получаем сложность O(N). У нас есть возможность улучшить его до O(logN). Но если серверов меньше 50 и алгоритм выбора сервера — с консистентным хешированием, то скорость останется примерно на том же уровне.
Но запросов всё еще много
Пока мы только перенаправили два запроса к поставщику на два запроса к одному серверу. Поэтому добавляем буфер и начинаем отправлять поставщику один запрос. Поставщик доволен, потому что батч запроса всегда работает лучше, чем единичный.
![](https://habrastorage.org/getpro/habr/upload_files/4f6/67b/61d/4f667b61d6b457f045d37b4dd77bfcf8.png)
Кажется, всё хорошо.
![](https://habrastorage.org/getpro/habr/upload_files/f86/350/faa/f86350faafc98e9245f94e5f4d5c4fe5.png)
Мы уменьшили запросы, но сами поставщики тоже горят. И рано или поздно придётся автоматизировать ситуацию, когда надо отключать поставщиков.
Отключение поставщиков
![](https://habrastorage.org/getpro/habr/upload_files/dd5/1b3/fcc/dd51b3fcc17b9570b71ffb1c16b71bdd.png)
Есть график проблем, ошибок. И есть решение — поставить circuit breaker, чтобы отключал поставщиков. Тогда вроде бы всё будет хорошо…
Но что, если этот график выглядит так только в Grafana,а на самом деле так:
![](https://habrastorage.org/getpro/habr/upload_files/00c/984/e23/00c984e2377f80bc788fa0da8af6ba28.png)
На полном графике красные точки вообще не видно, но алерты срабатывают и говорят, что всё плохо.
Если пересчитать в процентах, получится три девятки, а значит алертить это не нужно.
![](https://habrastorage.org/getpro/habr/upload_files/2b2/c60/014/2b2c60014642768c5d00ae83f28cd211.png)
Но проценты наших 250 поставщиков выглядят как месиво, где непонятно, что хорошо, а что плохо. Поэтому нам пришлось придумать автоматический способ отслеживания.
Как следить
Пришлось вспоминать статистику. Я пробовал вбивать в Google «скользящее среднее, мониторинг», но выпадали только инвестиционные каналы. А хотелось увидеть, как это применяется в мониторинге.
Возьмём, к примеру, срез за 15 минут, усредним данные и нарисуем гладкую линию, на которой выброса нет.
![](https://habrastorage.org/getpro/habr/upload_files/a3f/eb8/284/a3feb828445dddc2efecd9590b4e97bd.png)
Дальше будем считать в каждой конкретной точке, что несколько отклонений — это плохо. А в этот же график добавим медианы от прошлых значений, чтобы видеть высокоуровневые тренды, когда поставщик деградирует.
![](https://habrastorage.org/getpro/habr/upload_files/f07/ab1/b45/f07ab1b45a20aa1aaa3a0cc27347dd35.png)
Чтобы всё работало автоматизировано, используем стек TICK (Telegraf, InfluxDB, Chronograf, Kapacitor).
В InfluxDB будут храниться метрики, в Chronograf писаться запросы для отключения, а Kapacitor будет их экзекьютить и дальше что-то с ними делать. Останется понять, за чем следить.
За чем хотим следить
Золотые сигналы
В SRЕ book есть так называемые золотые сигналы — это процент ошибок, availability, latency и так далее.
![](https://habrastorage.org/getpro/habr/upload_files/f8a/01e/6e8/f8a01e6e814c9f16ef49baa4b89bdf8d.png)
Например, процент ошибок вышел за критичное значение, и мы отключили этого поставщика. Но на графике видно, что мы экспоненциально его включаем обратно. То есть мы посылаем какое-то количество запросов. Если всё ещё плохо, то отключаем обратно, и от 5 минут до 2 часов число запросов растёт.
Но с точки зрения бизнес-логики есть гораздо более интересные кейсы.
Все отели недоступны.
Например, поставщики могут не упасть (504, в NGINX Gateway Timeout и все остальное), а включить режим, что все отели недоступны. А у нас кэш зафиксирует, что всё плохо.
Поэтому лучше сразу превентивно указать, что если поставщик отдаёт 100% недоступность — значит, всё у себя отключил и ничего не ищет. Нам такие запросы не нужны.
Никакой отель нельзя забронировать
А ещё может быть, что поставщик отдаёт доступность, но потом пользователь приходит, пытается забронировать, но отель не бронируется, потому что на самом деле этого номера нет, он уже забронирован. И с этим тоже надо что-то делать.
![](https://habrastorage.org/getpro/habr/upload_files/b4e/d1b/a00/b4ed1ba00eebf3b82886eb564770f552.png)
Эту проблему довольно сложно решить, неважно, делите ли вы систему с помощью функционального подхода (фронт, бек, база) или кросс-функционального (контент, поиск, бронирования). Системы должны друг на друга влиять.
Но с этим всё равно надо что-то делать. Поэтому мы автоматизируем отключение поставщиков и постим себе в Slack с помощью бота Kapacitor. Он пишет, что видит, что success rate одного из поставщиков упал ниже 65%, и его, наверное, надо отключить на 2 часа.
![](https://habrastorage.org/getpro/habr/upload_files/928/9c2/ef1/9289c2ef18ff62b59026dd2d834a5d5e.png)
«Разработчики шутят, что бот называется Kapacitor, потому что это Тор с конденсатором в руке.
![](https://habrastorage.org/getpro/habr/upload_files/df1/247/495/df1247495537b14a27d57e5b356b837b.png)
Таким образом, как будто бы мы решили все проблемы.
Выводы
Гораздо чаще стратегический прорыв происходит в результате измененного представления данных или таблиц. Здесь заключена сердцевина программы. Покажите мне блок-схемы, не показывая таблиц, и я останусь в заблуждении. Покажите мне ваши таблицы, и блок-схемы, скорее всего, не понадобятся: они будут очевидны.
© Брукс «Мифический человеко-месяц»
Напомню:
Мы спроектировали кэш;
Сделали буфер для того, чтобы батчить наши запросы;
Поставили circuit breaker.
Стратегические изменения достигаются за счет того, что вы изменили представление о данных. Вообще то, как вы у себя складываете данные, много говорит о том, над чем вы работаете и будет ли это эффективно. Мы эту проблему решали на каждом шаге. Выбирали решение, исходя из того, какие данные нам отдают, выбирали, как нам фильтровать их правильно с эффективной структурой. Решали, как распределять запросы в нашем кластере, а не просто делать Round Robin. Поэтому думайте о том, какие у вас бизнес-данные и как их можно распределить.