Судний день: До чого призводять приховані помилки асинхронної обробки даних при зростанні навантаження



У нашому блозі ми розповідаємо не лише про розвиток свого продукту — білінгу для операторів зв'язку «Гідра», але і описуємо складності і проблеми, з якими стикаємося на цьому шляху. Раніше ми вже описували ситуацію, в якій безконтрольне зростання таблиць у базі даних однієї компанії-користувача нашої системи призвів до справжнього DoS.

Сьогодні мова піде про ще один цікавий випадок раптового збою, який зробив «день сміху» 1 квітня цього року зовсім не смішним для служби підтримки «Латеры».

Все пропало

Один з операторів зв'язку і, за сумісництвом, клієнтів нашої компанії, користувався білінгом «Гідра» протягом декількох років. Спочатку все було добре, проте з часом стали виникати проблеми — наприклад, різні елементи системи могли працювати повільніше, ніж треба.

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

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

Гарячий день

Зайшовши на білінговий сервер, інженери техпідтримки першим ділом виявили, що півтори години тому закінчилася місце в основному табличному просторі робочої БД. В результаті цього зупинилося кілька системних процесів білінгу — зокрема, оновлення профілів в модулі технічного відповідає за управління доступів абонентів до послуг). Техпідтримка видихнула і, передчуваючи швидкий обід, швидко збільшила табличне простір і запустила процеси.

Це не допомогло — абонентам легше не стало. Клієнт тим часом почав панікувати: потік обурених дзвінків «я заплатив, але нічого не працює» почав завалювати його колл-центр. Розгляд продовжилося.

На сервері RADIUS-авторизації, який за кілька днів до цього переїхав на нове «залізо», інженери виявили сильне уповільнення роботи. Швидко з'ясувалося, що воно призводило до того, що в БД сервера авторизації містилися неактуальні дані про користувача профілях — саме тому біллінг помилково відкривав доступ в інтернет одним абонентам і закривав його тим, хто оплачував послуги вчасно. Однак причина такого драматичного падіння продуктивності залишалася неясною.

Для позбавлення від неактуальних даних було вирішено повторно надіслати до RADIUS-сервер з провіженінга правильні дані, по суті справи відтворити всю інформацію заново. Ця операція спочатку замислювалася як засіб, який застосовується у відчайдушному положенні при руйнуванні БД.

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

Єдине, що можна було зробити додатково — попередньо очистити дані в кеші перед реплікацією, але це призвело б до відмов в обслуговуванні для абонентів, у яких все було добре (а їх була більшість), і на це піти ми не могли.

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

Ліричний відступ: Коли місто засинає

Отже, в ніч на 1 квітня біллінг почав генерувати нові дані для абонентських профілів на заміну старим — якщо абонент не оплатив послугу, то потрібно було відібрати у нього доступ до неї. На цьому кроці модуль provisioning згенерував велику кількість нових профілів і асинхронно відправив їх у внутрішню чергу повідомлень Oracle. Оскільки безпосередньо з чергами Oracle працювати ззовні з нашим стеком технологій незручно, для їх подальшої передачі використовується «прошарок» у вигляді брокера повідомлень Apache ActiveMQ, до якого підключається RADIUS-сервер, записує дані в MongoDB.

Біллінг повинен відправляти дані про змінені абонентських профілях в строгому порядку. Щоб порядок дотримувався і не відбувалося «плутанини» профілів на відключення та підключення, у системі реалізований спеціальний процес, що вибирає дані з черги ActiveMQ і записує їх в MongoDB.

Приходить розуміння

Система була розгорнута таким чином, що ActiveMQ і MongoDB перебували на різних серверах, при цьому в використовуваної конфігурації ActiveMQ не вимагалося підтвердження отримання повідомлень — всі відправлене з черги вважалося прийнятим за замовчуванням. Потрібно було лише вхідне з'єднання, потім дані вирушали до вичерпання лімітів буферів ОС приймаючої сторони.

Раніше іноді траплялися випадки втрати зв'язку RADIUS-сервера з ActiveMQ — на такий випадок ми передбачили функціональність відновлення сполук. Для детектування випадків втрати зв'язку був реалізований спеціальний механізм, що використовує протокол STOMP — в ньому є налаштування передачі heartbeat-пакетів. Якщо такий сигнал не отримано, то з'єднання вважається загубленим і примусово переустановлюється.

Коли ми всі це усвідомили, причина проблеми стала відразу зрозуміла.

Повідомлення з ActiveMQ завжди витягуються в тому ж потоці виконання, який записує дані в профілі абонентів. Витяг і запис — те, що він робить по порядку, і якщо запис затрималася, то потік не може «дістати» наступний пакет. І якщо в ньому містився heartbeat, то він не буде отримано — і інший потік, який перевіряє наявність таких пакетів, буде вважати з'єднання втраченим і спробує його повторно.

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

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

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

Криза минула, можна було видихнути, однак тепер потрібно зрозуміти, що послужило причиною такої некоректної роботи, і запобігти можливості повторення подібних збоїв.

Пошук причин

Як завжди буває в моменти масштабних аварій, один на одного наклалися відразу кілька факторів — кожен окремо не призвів би до таких плачевних наслідків, але спрацював ефект «сніжної грудки».

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

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

Оскільки такі перевірки балансу у цього клієнта відбуваються одномоментно на початку нового місяця, першого числа кожного місяця навантаження на систему завжди зростає. Цьому сприяє не тільки необхідність проаналізувати всіх клієнтів і відключити неплатників — тих з них, хто одразу ж вирішить оплатити послуги, потрібно швидко «допустити» до послуг знову і коректно цей факт врахувати.

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

Другий процес — запобігання встановлення нової сесії відключеним абонентом. Для цього в незалежну базу даних авторизаційного сервера з користувацькими конфігураціями потрібно копіювати дані з provisioning-модуля «Гідри». Це дозволяє всім частинам системи знати про те, що певних абонентів обслуговувати зараз не потрібно.

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

Друга причина — бажання заощадити на інфраструктурі та відмова від розумного рознесення сервісів. Для білінгу був закуплений менш потужний сервер, ніж було необхідно. Чотири роки він відпрацював без проблем, однак обсяг даних в БД ріс, крім того, на сервер поступово «навішувалися» вимогливі до ресурсів додаткові сервіси (зовнішня звітна система на Java-машині, новий модуль provisioning також з використанням Java-машини тощо), а рекомендації інженерів, які говорили про необхідність рознесення сервісів, відкладали в довгий ящик з аргументом «працює».

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

Незадовго до 1 квітня нарешті вдалося отримати окремий сервер для авторизаційної БД з профілями абонентів. Як з'ясувалося пізніше, RAID-масив на цьому сервері виявився дефектним, і на ньому авторизація при певному рівні навантаження гальмувала ще сильніше, ніж на старому «залозі». Ні на одній з більш ніж 100 інших інсталяцій «Гідри» ця помилка не проявлялася з-за того, що в нормальних умовах сервіс авторизації працює швидко і ресурсів вистачає з великим запасом.

Безпосереднім тригером аварії, ймовірно, слід вважати закінчилося місце в табличному просторі, яке призвело до того, що почали накопичуватися зміни профілів. Як тільки вільне місце знову з'явилося, всі ці зміни були оброблені і в дефектний RADIUS-сервер через системні черзі спрямувався потік репликационных повідомлень, які не змогли застосовуватися.

Певні підозри про можливе ба виникали і до описуваної ситуації — періодично в деяких одиничних абонентів в базі зберігалися неправильні профілі. Однак обчислити проблему до першого числа нового місяця не вдалося — подія було дуже рідкісним і розширене логування не допомогло вчасно «відловити» помилку (у тому числі по причині переїзду на новий сервер).

Запобігання проблем в майбутньому

Усунувши збій і розібравшись у його причини, вже в спокійній обстановці ми внесли корективи, покликані виключити повторення ситуації в майбутньому. Ось, що ми зробили:
  • Насамперед, у ActiveMQ була додана функціональність вимоги підтвердження доставки відправлених даних. При цьому подібне підтвердження працює в кумулятивному режимі — сервер підтверджує отримання не кожного повідомлення, а певною їх пачки (раз на п'ять секунд). Логіка обробки повідомлень підтримує повторну обробку черзі, починаючи з певного моменту, навіть якщо якісь з даних вже потрапили в БД.
  • Крім того, була збільшена частота відправки heartbeat-пакетів — замість п'яти секунд час збільшилася до декількох хвилин. В доповнення до механізму heartbeat з'єднання до брокера повідомлень стало встановлюватися з опцією keepalive з невеликими інтервалами перевірки активності сполуки (декілька десятків секунд проти пари годин, встановленою операційною системою за замовчуванням).
  • Також проводилися тести, в ході яких при відправленні повідомлень випадковим чином перезапускались різні модулі системи. В ході одного з таких тестів якась частина даних все одно виявлялася втраченої. Тоді був замінений сам «движок» бази даних MongoDB — перейшли на використання WiredTiger. Ми планували зробити це раніше, але з нагоди тестів вирішили поєднати переїзд.
Внесені зміни дозволили звести число загублених пакетів до нуля і запобігти виникненню подібних проблем у майбутньому навіть в умовах дуже «агресивного середовища».

Крім того, за рекомендаціями інженерів техпідтримки «Латеры» сервіси системи були рознесені на різні сервери (гроші на них швидко знайшлися). Це вдалося встигнути зробити до 1 травня — наступного дня масових білінгових операцій.

Головний урок

Тривожні «дзвіночки», які сигналізують про можливі проблеми, що звучали і в березні, але виявити їх до настання планового сплеску активності не вдалося. Якщо б цього сплеску не було, або він стався б в іншому місці — не «гальмує» RADIUS-сервер з максимальною швидкістю послідовного запису 5 МБ/сек, то з високою часткою ймовірності інженерам вдалося локалізувати проблему в квітні. Їм не вистачило буквально кількох днів.

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

Інші технічні статті від «Латеры»:



Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.