Свёртка, которая не лечит. Часть 3: живые бои на площадке
База данных - HighLoad оптимизация
Часть 3. Живые бои на площадке: галочка, воскресный призрак и документ, который ждал сам себя
Это третья часть цикла. В первой базу 2,5 ТБ собрались сворачивать, потому что «слишком большая», — а замеры показали, что ни одна причина нагрузки не связана с объёмом. Во второй мы прошли по музею накопленного за годы: зациклившийся обмен, ночное обслуживание, не доходившее до конца очереди, мёртвые планы обмена на 45 гигабайт и расширение, съедавшее десятки часов процессорного времени. Но самое болезненное для бизнеса происходило не в архитектуре, а на живой линии.
Архитектурные проблемы можно годами терпеть — они медленно едят ресурсы, а снаружи это выглядит как «база иногда подтормаживает». А вот то, что творилось на площадке, останавливало людей и оборудование здесь и сейчас.
1. Сервер, который падал по памяти
Сервер приложений падал прямо в смену. Не «тормозил» — падал: кластер убивал рабочий процесс целиком, а вместе с ним все сеансы пользователей и две тысячи фоновых заданий, которые жили в том же процессе.
Причина нашлась в журнале обмена с системой маркировки. Каждый запрос дописывался в один файл, а в конце этот файл целиком читался в память — по 64–144 гигабайта за вызов. Примерно как если бы бухгалтер после каждой операции перечитывал вслух всю книгу учёта с первой страницы — а книга с каждым днём становилась толще.
Логирование отключили — и падения по памяти прекратились. Без единого нового сервера.
Рядом жил дальний родственник той же беды. При печати и эмиссии кодов система отправляла на экран оповещение о ходе работы на каждый код: десять тысяч кодов — десять тысяч оповещений. Сервер это уже не роняло, но копило ожидания и блокировки. Предложение простое — сообщать о прогрессе раз в сто кодов.
2. Документ, который ждал сам себя
Оператор оформляет выпуск готовой продукции. Документ зависает на несколько минут, потом падает с ошибкой «в данной транзакции уже происходили ошибки». Оператор повторяет. Снова. Иногда — по кругу.
Разбор протоколов показал редкую по красоте картину. Проведение выпуска внутри своей же транзакции запускало перепроведение всей цепочки связанных документов — десятков штук, даже если в цепочке ничего не менялось. Та же транзакция держала блокировку очереди фоновых заданий производства. А фоновые задания, порождённые этим же проведением, пытались получить эту очередь — и не могли, потому что её держал их собственный родитель. Документ ждал сам себя. Через 20 секунд платформа сдавалась таймаутом, всё откатывалось, и оператор шёл на второй круг.
Из 258 секунд проведения 45 секунд документ считал, а 213 — ждал.
Решение спроектировали: штатный пакетный режим и вынос тяжёлого перепроведения цепочки в фон, а заодно не перепроводить то, что не изменилось. Пока оно ждёт внедрения — и это честная часть истории: найти причину и починить её — два разных этапа.
3. Галочка
Оператор загружал штрихкоды из файла. Одна неотмеченная галочка — и файл получил имя без нужного префикса, а обработка ушла в другую ветку: ту, которая ожидала, что штрихкод уже есть в базе. Штрихкода в базе не было.
Дальше — код в стиле «Пока НЕ Успех Цикл»: попробовать, если не вышло — попробовать ещё. Такая конструкция рассчитана на конфликт блокировок, который рассосётся сам через секунду-другую. Но при ошибке данных операция падала одинаково на каждой итерации, «Успех» не наступал никогда, и система решила ждать, пока штрихкод появится сам. Штрихкод не появился. Система ждала 26 минут.
Самое интересное: тот же паттерн «крутись, пока не получится» был размножен по всему контуру маркировки — печать этикеток, обработка терминалов сбора данных, эмиссия кодов. Восемнадцать таких циклов в шести модулях.
Загрузку штрихкодов исправили в тот же день: лимит попыток и проверка данных до входа в транзакцию. Обработку терминалов переписали. Остальные циклы ждут своей очереди — лечатся они одинаково.
4. Воскресный призрак
Отдельная история — про болезнь, которая возвращалась. Один из тяжёлых участков проведения документов казначейства (полтора миллиарда чтений на один документ, 17–27 минут на запись) вылечили точной статистикой: сервер увидел реальные размеры таблиц, выбрал правильный план и стал справляться за секунды. Победа, все довольны.
Через несколько дней — всё вернулось один в один. Будто ничего не чинили.
Разгадка нашлась не сразу. В системе тихо жило регламентное задание, которое каждое воскресенье в 07:00 перестраивало статистику по всей базе — но не подробно, а «на глаз», выборкой в 2,3% от объёма таблицы. По времени совпало до секунды: наша точная статистика — в четверг, задание — в воскресенье в 07:04, а к середине следующей недели казначейство снова проводило документы по полчаса. Как призрак замка: приходит в один и тот же час, всё ломает и уходит, а утром никто не понимает, почему опять холодно.
Таких заданий по статистике на одну базу оказалось три, и они мешали друг другу. Лишнее отключили, нужное сделали не «на глаз», а полным пересчётом с закреплением — чтобы никакой призрак её больше не затирал.
5. Кладбище протоколов на 300 гигабайт
Ну и на десерт: файлы никогда не удалялись.
Каждый документ ветеринарного госконтроля — это протокол обмена: тело запроса и тело ответа. Их копилось годами: больше 43 миллионов записей, около 180 гигабайт места в базе (по размерам самих файлов — около 600). Нужны они разве что для разборов с госорганом за прошлые периоды, но исправно нагружали сервер: по этой таблице четыре раза в сутки пересчитывалась статистика полным сканом, по 51 минуте за раз. Три с половиной часа в день на перечитывание протоколов, которые никто никогда не откроет.
Отдельный вопрос — где они лежали. В настройках хранения файлов стоял режим «в информационной базе». То есть файлы хранились прямо внутри SQL Server, в таблицах, среди остатков и проводок, — всего около 300 гигабайт вместе с прочими присоединёнными файлами. Хранить документы в СУБД можно, платформа позволяет. Так же, как можно хранить дрова в холодильнике: технически влезает, но холодильник не для этого, и открывать его становится всё тяжелее.
Перенос в тома на диске — штатная возможность платформы и минус 300 гигабайт из базы без вмешательства в учётные данные. Правда, на таком объёме штатная обработка переноса падала по памяти, и переносить пришлось бы порциями, почти две недели в один поток. Мы отложили это до решения по свёртке: переносить одни и те же файлы дважды смысла нет.
Для полноты картины — коды маркировки: около 220 миллионов записей в справочнике и примерно столько же в регистрах. В пуле кодов около 80% — мёртвый груз, который можно удалять.
Мы видели такое не раз: у каждого журнала есть настройка «хранить N дней», у каждого файла — «где хранить», и почти никто не знает, что они есть.
Вместо вывода
Все пять историй — об одном: сбой видно сразу, а причину — только после замеров. И найти причину и починить её — два разных этапа: часть исправлений сделана в тот же день, часть ещё ждёт своей очереди.
Финал — в части 4: как догрузка свёрнутой копии роняла сервер восемнадцать раз за воскресенье, почему повисли полторы тысячи кодов маркировки, как одно подчёркивание замедлило печать до нескольких кодов в минуту — и честная таблица «было / стало».
Случай обезличен, отдельные детали обобщены. Оценки — мнение автора по результатам замеров, а не оценка свёртки как метода или исполнителей в целом.
Технические подробности для коллег
Падения по памяти. Рабочий процесс кластера падал с превышением памяти; в нём одновременно жило около 2 000 фоновых заданий, поэтому каждое падение уносило всё сразу. Журнал запросов к системе маркировки накапливался в одном файле и в конце обработки читался целиком — 64–144 ГиБ за вызов. Отключение этого логирования сняло падения по памяти. Оповещения о ходе работы на каждый код при печати и эмиссии дают не падения, а ожидания: 10 000 кодов — 10 000 вызовов; предложено сообщать раз в 100.
Документ, который ждал сам себя. Профиль проведения выпуска по технологическому журналу: 258 с, из них CPU 45 с, остальное — ожидание. Проведение в своей транзакции перепроводит цепочку связанных документов и держит блокировку очереди фоновых заданий производства; порождённые задания упираются в неё и уходят в TTIMEOUT через 20 с, после чего транзакция откатывается («в данной транзакции уже происходили ошибки»). Целевое решение — штатный пакетный режим формирования и вынос перепроведения цепочки в фоновое задание вне транзакции проведения.
«Пока НЕ Успех Цикл». Конструкция повторяет операцию до успеха без различения ошибок: конфликт блокировки и ошибка данных обрабатываются одинаково. Правило: лимит попыток, повтор только для конфликта блокировок, проверка данных до начала транзакции. В контуре маркировки найдено 18 таких циклов в 6 модулях.
Воскресный призрак. Еженедельное задание sp_updatestats в воскресенье 07:00 пересчитывало статистику выборкой (на ключевом регистре — 2,3% строк) и затирало ранее сделанный FULLSCAN. Лечение: UPDATE STATISTICS … WITH FULLSCAN, PERSIST_SAMPLE_PERCENT = ON — закреплённая выборка сохраняется при последующих пересчётах; лишние задания статистики отключены, оставшееся обслуживание не понижает закреплённые 100%. Проверка: sys.dm_db_stats_properties — сравнить rows_sampled с rows и посмотреть persisted_sample_percent.
Файлы в базе. Режим хранения файлов «в информационной базе» складывает тела файлов в таблицы СУБД. Таблица протоколов госконтроля попадала в пересчёт статистики полным сканом четыре раза в сутки (по 51 мин). Перенос в тома — штатная обработка, но на сотнях гигабайт её стоит запускать порциями: в один заход она упиралась в память.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.