Ночная загрузка данных упала без видимой ошибки: как узнать об этом в первую же ночь
Иван Недомолков · Chop etilgan:
В журнале СУБД у такой аварии есть строка, только её никто не читает. В PostgreSQL она выглядит так:
ERROR: numeric field overflow
DETAIL: A field with precision 5, scale 2 must round to an absolute value less than 10^3.
В MS SQL та же ситуация называется Arithmetic overflow error converting numeric to data type numeric. Смысл один: пришло число, которое не помещается в объявленный тип колонки.
Если после этого в таблице ноль новых строк, а отчёты спокойно открываются со вчерашними цифрами, делайте три вещи. Сегодня: найдите во входных данных значения за пределом типа и решите, расширять колонку или прижимать значение. Завтра: поставьте сравнение числа загруженных строк с прошлой ночью, чтобы следующий обвал будил вас в первую ночь. Потом: проверьте, не пишет ли загрузка всю пачку одной транзакцией, потому что именно это превращает одну плохую строку в пустую ночь.
Числа ниже из одного разбора. Витрина аналитики крупной розничной сети каждую ночь пополнялась данными внешней торговой площадки. Шесть ночей подряд загрузка не привезла ничего, и заметили это на шестые сутки, когда пошли выяснять, почему недельные цифры выглядят странно.
Почему падает вся пачка из-за одной строки
Колонка была объявлена как число из пяти знаков, два из них после запятой. До запятой остаётся три знака, потолок 999,99. Площадка изредка присылала значения за тысячу, например 1250,56.
Вставка шла одним запросом на много строк внутри одной транзакции. Дойдя до 1250,56, СУБД бросала исключение и откатывала всё, что успела вставить до этого. У такой ночи два исхода: приехало всё или не приехало ничего.
Отсюда и “молчание”. Кривых значений в таблице нет, частичных данных тоже. Таблица просто не меняется, а неизменная таблица выглядит так же, как в ночь, когда грузить было нечего. Если эти два состояния у вас ничем не различаются, авария будет висеть до первого человека, который удивится цифрам.
Про то же свойство, только внутри 1С, у нас есть разбор длинных транзакций: приём обмена целиком одной транзакцией останавливается от единственного конфликта.
Как найти бракованные строки
Раз пачка откатилась, в приёмнике брака нет. Искать его надо во входных данных: в файле, в промежуточной таблице, в ответе внешней системы. Критерий берётся из типа колонки. Для числа 5,2 подозрительно всё, что по модулю от тысячи и выше.
Когда строки найдены, первым делом посчитайте, насколько они вылезли за предел. От этого зависит, кому писать.
| Что видите | Что это обычно значит | Куда идти |
|---|---|---|
| значение больше предела в разы, ноль там, где его не бывает, отрицательное, пустое | источник сломался или сменил формат | к поставщику данных |
| значение чуть выше предела | источник работает нормально, граница в вашей схеме придумана | к своей схеме |
В разобранном случае 1250,56 против 999,99 это превышение на четверть. Товар стоял глубоко в выдаче, площадка честно вернула его позицию. Трёхзначную позицию никто не обещал, её допустили при проектировании таблицы и нигде не записали, кроме самой схемы.
Вторым делом посчитайте долю. При дозаливке за дни простоя приехало 12 237 строк, прижимать пришлось 16. Это 0,13 %, одна строка из 765. Такая доля остановила 100 % загрузки на шесть суток. Пока звучит “шестнадцать плохих строк”, кажется, что проблема в данных. Соотношение 0,13 к 100 показывает, что масштаб аварии сделала вставка без изоляции ошибок, а колонка только нажала на спуск. Пиши загрузка сбойные строки в сторону, потерялось бы шестнадцать записей, и утром кто-нибудь на них посмотрел бы.
Обрезать значение или расширять колонку: чем платите
Вариантов четыре, и у каждого своя цена.
| Решение | Что закрывает | Чем платите |
|---|---|---|
| расширить тип с 5 до 7 знаков | этот конкретный выход за предел: потолок растёт с 999,99 до 99 999,99, в сто раз | ничем не защищает от пустой даты, длинной строки или отрицательного числа завтра. Механизм отказа остаётся |
| прижать значение к пределу перед записью | строка сохраняется, ночь проходит | исходное значение пропадает, узнать частоту выхода за диапазон уже нельзя |
| изолировать сбойные строки: построчная вставка или пропуск с записью в лог | весь класс аварий, не только переполнение | теряются только плохие строки, их надо разбирать |
| писать отвергнутое значение в отдельный лог | вопрос “как часто это бывает” | пара полей: когда, какой объект, что пришло |
В разобранном случае выбрали обрезку. Для позиции в выдаче это допустимо: тысяча первое место и тысяча триста двадцатое одинаково означают “карточку не видно”, и ни одно решение по рекламе между ними не различает. После исправления отказов не было ни одного, но проверено это на тех же данных, где нашли дефект. Эта причина закрыта, другие нет.
С суммой, количеством или курсом валюты тот же приём превращается в тихое искажение учёта. Прежде чем прижимать значение, спросите себя, смотрит ли кто-то на то, что вы отрезаете.
Два долга в этой истории я бы у себя не оставил. Лог отвергнутых значений так и не завели, поэтому сколько раз площадка выходит за тысячу в обычную неделю, не знает никто. Расширение типа отложили со словами “потребует простоя”, при этом размер таблицы никто не назвал. На небольшой таблице смена типа занимает секунды, и решение без замера я бы оспорил.
Какая проверка поймает аварию в первую ночь
Минимальный набор: на каждую ночь две записи в журнал загрузки, сколько строк приехало и сколько отвергнуто, и один алерт на резкое падение первой цифры. Правило из разбора: строк стало меньше чем на 80 % против прошлой ночи, значит шлём сигнал.
В разобранном случае падение было стопроцентным, строк ноль. Такой алерт сработал бы в первую ночь. Пять ночей из шести стоили ровно отсутствия этой проверки.
У заданий, которые не загружают, а разбирают очередь, аналог этой цифры другой: сколько записей осталось по отбору после прогона. Без него обход порциями, потерявший половину набора, выглядит успешным.
Запрос на журнале загрузок, синтаксис PostgreSQL:
-- zhurnal_zagruzki: noch date, zagruzka text,
-- strok_prishlo int, strok_otvergnuto int
SELECT p.zagruzka,
p.strok_prishlo AS vchera,
COALESCE(t.strok_prishlo, 0) AS segodnya
FROM zhurnal_zagruzki p
LEFT JOIN zhurnal_zagruzki t
ON t.zagruzka = p.zagruzka
AND t.noch = p.noch + 1
WHERE p.noch = CURRENT_DATE - 1
AND COALESCE(t.strok_prishlo, 0) < p.strok_prishlo * 0.2;
Соединение идёт от вчерашней записи к сегодняшней, и это сделано нарочно. Упавшая загрузка часто не пишет в журнал вообще ничего. Если искать от сегодняшней записи, пропущенная ночь в выборку не попадёт, и проверка промолчит ровно тогда, когда должна кричать. Отсутствие записи здесь считается нулём.
Порог 80 % грубый. Он ловит обвал и пропускает деградацию: из тысячи строк приехало семьсот, и никто не узнает. Это первая ступень, которую ставят за полчаса. Вторая, когда дойдут руки: база сравнения меняется с прошлой ночи на среднее по тому же дню недели за последний месяц, чтобы понедельник не мерился субботней линейкой.
Если загрузки у вас идут регламентными заданиями и зелёный статус уже не раз обманывал, в разборе выгрузки по расписанию, которая не работает есть соседняя метрика, возраст данных в приёмнике. Счётчик строк и возраст данных вместе закрывают оба варианта тишины. Как выстроить такой контроль постоянно, с порогами и оповещениями, мы делаем в рамках мониторинга 1С и СУБД.
Как найти у себя узкие числовые реквизиты, которые заполняются извне
В 1С тот же риск сидит в квалификаторах числа. Реквизит Число(5,2) даёт те же три знака до запятой и потолок 999,99. Если его заполняет обмен, загрузка из файла или веб-сервис, мина та же.
Глазами по конфигурации такие реквизиты не найти, их тысячи. Обход метаданных находит их за минуту и не читает ни одной строки данных:
Для Каждого ОписаниеДокумента Из Метаданные.Документы Цикл
Для Каждого Реквизит Из ОписаниеДокумента.Реквизиты Цикл
Для Каждого ТипРеквизита Из Реквизит.Тип.Типы() Цикл
Если ТипРеквизита <> Тип("Число") Тогда
Продолжить;
КонецЕсли;
Квалификаторы = Реквизит.Тип.КвалификаторыЧисла;
ЦелаяЧасть = Квалификаторы.Разрядность - Квалификаторы.РазрядностьДробнойЧасти;
Если ЦелаяЧасть <= 3 Тогда
Сообщить(ОписаниеДокумента.Имя + "." + Реквизит.Имя
+ " - Число(" + Квалификаторы.Разрядность + ","
+ Квалификаторы.РазрядностьДробнойЧасти
+ "), до запятой знаков: " + ЦелаяЧасть);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Код проверялся на 8.3.24, он работает только с метаданными и к СУБД не обращается. Порог в три знака поменяйте под свой случай. Тем же способом допишите обход регистров сведений и накопления: коэффициенты, проценты и курсы там объявляют узкими чаще, чем в документах.
Список выйдет длинным. Дальше его сужают руками по одному признаку: откуда приходит значение. Реквизит, который считает сама конфигурация, ограничен вашей же логикой, и эту аварию он не устроит. Опасны те, что заполняются снаружи. Обход стоит повторять после каждой заметной доработки обменов, он бесплатный.
Два наших инструмента помогают в соседних шагах. Карта объёмов базы показывает, какие таблицы перестали расти, и загрузка, вставшая неделю назад, видна там сразу. Если ошибка СУБД пришла с именами вида _Document123, трансформатор SQL в термины 1С переведёт их в документы и реквизиты конфигурации.
Какая загрузка у вас сейчас пишет пачку одной транзакцией, и чем вы отличите её пустую ночь от ночи, когда грузить было нечего?