Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Привіт, мене звуть Євген. Я працюю в інфраструктурі пошуку Яндекс.Маркету. Хочу розповісти спільноті Хабра про внутрішню кухню Маркета – а розповісти є що. Насамперед, як влаштований пошук Маркета, процеси та архітектура. Як ми впораємося із позаштатними ситуаціями: що станеться, якщо впаде один сервер? А якщо таких серверів буде 100?

А ще ви дізнаєтеся, як ми впроваджуємо нову функціональність на купі серверів відразу. І як тестуємо складні послуги прямо в production, не завдаючи користувачам ніяких незручностей. Загалом як влаштований пошук Маркета, щоб усім було добре.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Небагато про нас: яке завдання ми вирішуємо

Коли ви вводите текст, шукаєте товар за параметрами або порівнюєте ціни в різних магазинах, всі запити прилітають до пошуку. Пошук – це найбільший сервіс у Маркеті.

Ми опрацьовуємо всі пошукові запити: з сайтів market.yandex.ru, beru.ru, сервісу «Суперчек», Яндекс.Порадника, мобільних додатків. До нас відносяться пропозиції товарів у пошуковій видачі на yandex.ru.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Під пошуковим сервісом я маю на увазі не лише безпосередньо пошук, а й базу даних з усіма пропозиціями на Маркеті. Масштаб такий: за добу обробляється понад мільярд пошукових запитів. І все має працювати швидко, без перебоїв та завжди видавати потрібний результат.

Що є що: архітектура Маркету

Стисло опишу поточну архітектуру Маркета. Умовно можна описати її схемою нижче:
Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів
Допустимо, до нас приходить магазин-партнер. Каже, хочу продати іграшку: ось цього злісного кота з пищалкою. І ще злісного кота без пищалки. І просто кота. Тоді магазину потрібно підготувати пропозиції, якими Маркет здійснює пошук. Магазин формує спеціальний xml з пропозиціями та повідомляє шлях до цього xml через партнерський інтерфейс. Потім індексатор періодично завантажує цей xml, перевіряє наявність помилок і зберігає всю інформацію у величезну базу даних.

Таких збережених xml багато. Із цієї бази даних створюється пошуковий індекс. Індекс зберігається у внутрішньому форматі. Після створення індексу сервіс Розкладки викладає його пошукові сервери.

У результаті, в основі утворюється злий кіт з пищалкою, а на сервері - індекс кота.

Про те, як ми шукаємо кота, розповім у частині про архітектуру пошуку.

Архітектура пошуку маркету

Ми живемо у світі мікросервісів: кожен вхідний запит на market.yandex.ru викликає дуже багато підзапитів, і в їхній обробці беруть участь десятки сервісів. На схемі зображені лише деякі:

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів
Спрощена схема обробки запиту

Кожен сервіс має чудову штуку – свій балансер з унікальним ім'ям:

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Балансер дає нам більшу гнучкість в управлінні сервісом: можна, наприклад, вимикати сервери, що часто потрібні для оновлень. Балансер бачить, що сервер недоступний та автоматично перенаправляє запити на інші сервери чи дата-центри. Під час додавання або видалення сервера навантаження автоматично перерозподіляється між серверами.

Унікальна назва балансера не залежить від дата-центру. Коли сервіс А робить запит B, то за замовчуванням балансер B перенаправляє запит в поточний дата-центр. Якщо сервіс недоступний або відсутній у поточному дата-центрі, запит перенаправляється в інші дата-центри.

Єдиний FQDN для всіх дата-центрів дозволяє сервісу взагалі абстрагуватися від локацій. Його запит до сервісу B завжди буде оброблений. Винятком є ​​випадок, коли сервіс лежить у всіх дата-центрах.

Але не все так райдужно із цим балансером: у нас з'являється додаткова проміжна компонента. Балансер може працювати нестабільно, і ця проблема вирішується надмірними серверами. Також відбувається додаткова затримка між сервісами A та В. Але на практиці вона менша за 1 мс і для більшості сервісів це некритично.

Боротьба з несподіванками: балансування та стійкість до відмови сервісу пошуку

Уявіть, що стався колапс: треба знайти кота з пищалкою, але падає сервер. Або 100 серверів. Як викрутитись? Невже залишимо користувача без кота?

Ситуація страшна, але ми готові до неї. Розкажу по порядку.

Пошукова інфраструктура знаходиться у кількох дата-центрах:

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

При проектуванні ми закладаємо можливість вимкнення одного дата-центру. Життя сповнене несподіванок - наприклад, екскаватор може перерубати підземний кабель (так, було й таке). Потужностей в дата-центрах, що залишилися, повинно бути достатньо, щоб витримати пікове навантаження.

Розглянемо окремий дата-центр. У кожному дата-центрі однакова схема роботи балансерів:

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів
Один балансер – це як мінімум три фізичні сервери. Така надмірність зроблена для надійності. Балансери працюють на HAProx.

HAProxу ми вибрали через високу продуктивність, невеликі вимоги до ресурсів і широку функціональність. Усередині кожного сервера працює пошуковий софт.

Імовірність виходу з експлуатації одного сервера невелика. Але якщо у вас багато серверів, ймовірність того, що впаде хоча б один, збільшується.

Так і відбувається насправді: сервери падають. Тому треба постійно відстежувати стан усіх серверів. Якщо сервер перестає відповідати, його автоматично відключають від трафіку. Для цього у HAProxy є вбудований health check. Він раз на секунду ходить на всі сервери з HTTP запитом "/ping".

Інша особливість HAProxy: agent-check дозволяє рівномірно завантажувати всі сервери. Для цього HAProxy підключається до всіх серверів, а вони повертають свою вагу в залежності від поточного навантаження від 1 до 100. Вага обчислюється на підставі кількості запитів у черзі на обробку та навантаження на процесор.

Тепер про пошук кота. На пошук прилітають запити виду /search?text=angry+cat. Щоб пошук був швидким, весь індекс кота має бути розміщений в оперативній пам'яті. Навіть читання із SSD недостатньо швидке.

Давним-давно база пропозицій була невелика, і їй вистачало оперативної пам'яті одного сервера. У міру зростання бази пропозицій все перестало поміщатися в цю оперативну пам'ять, і дані розділили на дві частини: shard 1 і shard 2.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів
Але так завжди буває: будь-яке рішення, навіть добре, породжує інші проблеми.

Балансер, як і раніше, ходив на будь-який сервер. Але на тій машині, куди прийшов запит, була лише половина індексу. Решта була на інших серверах. Тому серверу треба було сходити на якусь сусідню машину. Після отримання даних від обох серверів результати об'єднувалися і переранжувалися.

Оскільки балансер розподіляє запити рівномірно, всі сервери займалися переранжуванням, а чи не лише віддавали дані.

Проблема виникала, якщо сусідній сервер був недоступний. Рішенням було вказати як «сусідний» сервер кілька серверів з різними пріоритетами. Спочатку запит відправлявся на сервери у поточну стійку. Якщо не надходила відповідь, запит надсилався на всі сервери цього дата-центру. І вже в останню чергу запит йшов до інших дата-центрів.
У міру зростання кількості пропозицій дані поділили на чотири частини. Але і це була не межа.

Зараз використовується конфігурація із восьми шардів. Крім того, для ще більшої економії пам'яті індекс розділили на пошукову частину (по якій відбувається пошук) і на сниппетную частину (яка не бере участі в пошуку).

Один сервер містить інформацію лише по одному шарду. Тому, щоб здійснити пошук за повним індексом, треба шукати на восьми серверах, що містять різні шарди.

Сервери згруповані у кластери. Кожен кластер містить вісім пошукових серверів та один сніпетний.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів
На сніпетному сервері працює key-value база даних зі статичними даними. Вони потрібні для видачі документів, наприклад, описи кота з пищалкою. Дані спеціально винесені на окремий сервер, щоб завантажувати пам'ять пошукових серверів.

Оскільки ID документів унікальні лише в рамках одного індексу, могла виникнути ситуація, що у «сніппетах» відсутні документи. Ну або що по одному ID буде інший контент. Тому, щоб пошук працював і відбувалася видача, виникла потреба у консистентності всього кластера. Про те, як ми стежимо за консистентністю, розповім трохи нижче.

Сам пошук влаштований так: пошуковий запит може прийти на будь-який з восьми серверів. Припустимо, він прийшов на сервер 1. Цей сервер опрацьовує всі аргументи і розуміє, що і як треба шукати. Залежно від вхідного запиту сервер може робити додаткові запити зовнішні сервіси за необхідною інформацією. За одним запитом може бути до десяти запитів у зовнішні сервіси.

Після збору необхідної інформації починається пошук за основою пропозицій. Для цього робляться запити на всі вісім серверів у кластері.

Після отримання відповіді результати об'єднуються. В кінці для формування видачі можуть знадобитися ще кілька підзапитів на сервер.

Пошукові запити всередині кластера мають вигляд: /shard1?text=angry+cat. Крім того, між усіма серверами всередині кластера раз на секунду постійно робляться запити виду: /статус.

Запит /статус виявляє ситуацію, коли сервер недоступний.

Також він контролює, щоб на всіх серверах версія пошукача та версія індексу були однакові, інакше всередині кластера будуть неконсистентні дані.

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

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Щоб перенести дані, ми запровадили універсальні ключі для документів. Тепер неможлива ситуація, коли за одним ключем повертається контент від іншого документа.

Але перехід в іншу архітектуру ще завершено. Зараз ми хочемо позбавитися виділеного сніппетного сервера. А потім узагалі відійти від кластерної структури. Це дозволить нам продовжувати легко масштабуватись. Додатковий бонус – значна економія заліза.

А тепер до страшних історій із щасливим кінцем. Розглянемо кілька випадків відсутності серверів.

Сталося жахливе: недоступний один сервер

Допустимо, один сервер недоступний. Тоді решта серверів у кластері може продовжити відповідати, але пошукова видача буде неповною.

Через перевірку статусу /статус сусідні сервери розуміють, що один недоступний. Тому, щоб зберегти повноту, всі сервери в кластері на запит /ping починають відповідати балансера, що вони теж недоступні. Виходить, померли всі сервери у кластері (що не так). Це основний недолік нашої схеми із кластерами – тому ми хочемо від неї піти.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Запити, які завершилися з помилкою, балансер перепитує інших серверах.
Також балансер перестає відправляти на мертві сервери трафік, але продовжує перевіряти їх статус.

Коли сервер стає доступним, він починає відповідати на /ping. Як тільки починають приходити нормальні відповіді на пінги від мертвих серверів, балансери починають відправляти туди трафік користувача. Робота кластера відновлюється, ура.

Ще гірше: недоступно багато серверів

Вирубується значна частина серверів у дата-центрі. Що робити, куди тікати? На допомогу знову приходить балансер. Кожен балансер завжди містить у пам'яті поточну кількість живих серверів. Він постійно вважає максимальну кількість трафіку, яка може обробити поточний дата-центр.

Коли падає багато серверів у дата центрі, балансер розуміє, що цей дата центр не може обробити весь трафік.

Тоді надлишковий трафік починає випадково розподілятися в інші дата-центри. Все працює, всі щасливі.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Як ми це робимо: публікація релізів

Тепер про те, як ми публікуємо внесені до сервісу зміни. Тут ми пішли шляхом спрощення процесів: викочування нового релізу майже повністю автоматизовано.
Коли набирається певна кількість змін у проекті, автоматично створюється новий реліз та запускається його складання.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Потім сервіс викочується у testing, де перевіряється стабільність роботи.

Одночасно із цим запускається автоматичне тестування продуктивності. Ним займається спеціальний сервіс. Не буду про нього зараз розповідати – його опис гідний окремої статті.

Якщо публікація у testing пройшла успішно, автоматично запускається публікація релізу у prestable. Prestable – це спеціальний кластер, куди прямує нормальний користувальницький трафік. Якщо він повертає помилку, балансер робить перезапит у production.

У prestable вимірюється час відповідей та порівнюється з попереднім релізом у production. Якщо все нормально, то підключається людина: перевіряє графіки та результати тесту навантаження і потім запускає викочування в production.

Все найкраще – користувачу: A/B-тестування

Не завжди очевидно, чи принесуть зміни у сервісі реальну користь. Щоб вимірювати корисність змін, люди вигадали A/B-тестування. Я трохи розповім, як це працює у пошуку Яндекс.Маркета.

Все починається з додавання нового CGI-параметра, що включає нову функціональність. Нехай нашим параметром буде: market_new_functionality=1. Потім код містить цю функціональність за наявності прапора:

If (cgi.experiments.market_new_functionality) {
// enable new functionality
}

Нова функціональність викочується у production.

Для автоматизації A/B тестування є виділений сервіс, який докладно описаний тут. У сервісі створюється експеримент. Задається частка трафіку, наприклад, 15%. Відсотки задаються не для запитів, а користувачів. Також вказується час експерименту, наприклад, тиждень.

Одночасно можна запустити кілька експериментів. У налаштуваннях можна вказати, чи можливо перетин з іншими експериментами.

В результаті сервіс автоматично додає аргумент market_new_functionality=1 до 15% користувачів. Також він автоматично вважає вибрані метрики. Після закінчення експерименту аналітики дивляться на результати та роблять висновки. З висновків приймається рішення про викочуванні в production чи доопрацюванні.

Спритна рука Маркета: тестування в production

Часто трапляється, що треба перевірити роботу нової функціональності в production, але при цьому немає впевненості, як вона поведеться в «бойових» умовах під великим навантаженням.

Є рішення: прапори в параметрах CGI можна використовувати не тільки для A/B-тестування, але і для перевірки нової функціональності.

Ми зробили інструмент, який дозволяє миттєво змінювати конфігурацію на тисячах серверів, не наражаючи на ризик. Він називається "Стоп-кран". Початкова ідея полягала у можливості швидко відключати якусь функціональність без розкладки. Потім інструмент розширився та ускладнився.

Схема роботи сервісу представлена ​​нижче:

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Через API задаються значення прапорів. Сервіс керування зберігає ці значення бази даних. Усі сервери ходять в базу разів на десять секунд, викачують значення прапорів і застосовують ці значення на кожен запит.

У Стоп-крані можна виставляти два види значень:

1) Умовні висловлювання. Застосовуються, коли виконується одне із значень. Наприклад:

{
	"condition":"IS_DC1",
	"value":"3",
}, 
{
	"condition": "CLUSTER==2 and IS_BERU", 
	"value": "4!" 
}

Значення "3" буде застосовуватися, коли запит оброблятиметься в локації DC1. А значення "4", коли запит обробляється на другому кластері для сайту beru.ru.

2) Безумовні значення. Застосовуються за умовчанням, якщо не виконано жодної з умов. Наприклад:

value, value!

Якщо значення закінчується на знак оклику, йому присвоюється підвищений пріоритет.

Парсер CGI-параметрів розбирає URL-адресу. Потім застосовує значення зі Стоп-крана.

Застосовуються значення з такими пріоритетами:

  1. З підвищеним пріоритетом зі Стоп-крана (знак оклику).
  2. Значення із запиту.
  3. Значення за замовчуванням із Cтоп-крана.
  4. Значення за промовчанням у коді.

Прапорів, які вказуються в умовних значеннях, багато – їх вистачає на всі відомі сценарії:

  • Дата центр.
  • Оточення: production, testing, shadow.
  • Майданчик: market, beru.
  • Номер кластеру.

Цим інструментом можна увімкнути нову функціональність на якійсь групі серверів (наприклад, лише в одному дата-центрі) та перевірити роботу цієї функціональності без особливого ризику для всього сервісу. Навіть якщо ви десь серйозно помилилися, все почало падати і ліг весь дата-центр, балансери перенаправлять запити до інших дата-центрів. Кінцеві користувачі нічого не помітять.

Якщо ви помітили проблему, можна відразу повернути старе значення прапора, і зміни відкотяться назад.

Цей сервіс має і мінуси: розробники його дуже люблять і часто намагаються всі зміни запхати в Стоп-кран. Ми намагаємося боротися із неправильним використанням.

Підхід зі Стоп-краном добре працює, коли у вас є стабільний код, готовий до викочування в production. При цьому у вас залишаються сумніви, і ви хочете перевірити код у «бойових» умовах.

Однак, Стоп-кран не підходить для тестування в процесі розробки. Для розробників є окремий кластер, який має назву «тіньовий кластер».

Таємне тестування: тіньовий кластер

Запити з одного із кластерів дублюються на тіньовий кластер. Але балансер повністю ігнорує відповіді кластера. Схема його роботи представлена ​​нижче.

Як влаштований пошук Яндекс.Маркета і що буде, якщо впаде один із серверів

Ми отримуємо тестовий кластер, який знаходиться у справжніх бойових умовах. Туди летить нормальний користувальницький трафік. Залізо в обох кластерах однакове, тому можна порівнювати продуктивність та помилки.

А оскільки балансер повністю ігнорує відповіді, кінцеві користувачі відповідей від тіньового кластера не побачать. Тому не страшно зробити помилку.

Висновки

Отже, як ми збудували пошук Маркета?

Щоб все йшло гладко, ми виокремлюємо функціональності в окремі послуги. Так можна й масштабувати лише ті компоненти, які нам потрібні, і робити компоненти простішими. Окремий компонент легко віддати в іншу команду та розділити обов'язки щодо роботи над ним. І значна економія заліза за такого підходу – очевидний плюс.

Нам також допомагає тіньовий кластер: можна розробляти сервіси, тестувати їх у процесі і не турбувати користувача.

Ну і перевірка у production, звичайно. Чи потрібно змінити конфігурацію на тисячі серверів? Легко використовуємо Стоп-кран. Так можна одразу викотити готове складне рішення та зробити відкат на стабільну версію, якщо виникнуть проблеми.

Сподіваюся, я зумів показати, як ми робимо Маркет швидким і стабільним при базі пропозицій, що постійно зростає. Як вирішуємо серверні проблеми, знаємося з величезною кількістю запитів, покращуємо гнучкість сервісу і робимо це, не перериваючи робочих процесів.

Джерело: habr.com

Додати коментар або відгук