Отчёт сказал: 99,9 % записей справочника никем не используются. Справочник на 8,34 миллиона строк, свёртка базы с двух с лишним терабайт, окно простоя одно.
Реальных сирот в нём было 439.
Отчёт, в который я почти поверил
Свёртка шла в самописной конфигурации крупной сети ломбардов. Под ней стоял MS SQL Server (Microsoft SQL Server), дальше по тексту просто СУБД - система управления базами данных. Автор конфигурации давно не работает, документации нет, имена объектов говорят мало, крупных таблиц в схеме много.
Про то, как эту базу в итоге свернули и с каким результатом, я уже писал: Свертка базы 2,3 ТБ за одно ночное окно. Там порядок работ, ночное окно и итог. Здесь про то, что идёт до порядка работ и решает больше него: как по незнакомой таблице вообще выносится вердикт. В той статье классификация названа результатом аудита, а правил, по которым решение принимается, в ней нет.
В такой работе первым делом строишь карту ссылок: кто на кого ссылается, что можно удалять, что держится живыми связями. Скан прошёл по схеме и выдал результат: справочник чеков контрольно-кассовых машин (ККМ), 8,34 миллиона строк, сирот 99,9 %. То есть почти всё содержимое можно смело выносить.
Чеки в этой конфигурации хранятся справочником. Тип объекта тут выбирал не я, работать пришлось с тем, что есть, и на логику проверки это никак не влияет.
Цифра выглядела прекрасно. Гигантская таблица, почти целиком мусор, одно действие освобождает заметную часть базы. Ровно то, ради чего клиент и звал.
Спасла перекрёстная проверка: прямой полный подсчёт держателей по каждой ссылочной колонке плюс поиск имени объекта по коду конфигурации. Полный подсчёт дал другую картину. Держатель у чеков был, живой, на 8,3 миллиона записей. Сирот действительно набралось 439, это 0,005 % таблицы.
Разберём, откуда взялась разница. Скан брал по одной записи на каждую ссылочную колонку-кандидата: если запись ссылается на наш справочник, колонка считается держателем, если нет - колонка выбывает. По одной из колонок ему попалась запись с битой ссылкой. Колонка целиком признана невалидной и выброшена из рассмотрения. Та самая, что держала 8,3 миллиона живых чеков.
Завышение вышло в девятнадцать тысяч раз: инструмент предлагал удалить 8,33 миллиона строк там, где удалять можно было 439.
Тот же изъян чуть не завысил набор сирот по залогам на 17 миллионов строк. Обнаружился он там же, полным пересчётом.
Гипотеза, которую пришлось выбросить
Выборку в скане заложили осознанно. Нормальное инженерное решение, и на бумаге оно выглядит правильным.
Полный подсчёт держателей означает пройти по всем ссылочным колонкам схемы и по каждой посчитать реальное число ссылок на объект. На базе в 2,3 терабайта это тяжёлая операция: она грузит сервер и мешает работе. Выборка отвечает сразу, и в подавляющем большинстве случаев отвечает верно: если колонка ссылается на объект, почти любая её запись это покажет.
Гипотеза звучала так: держателя ссылок достаточно обнаружить, его не нужно пересчитывать. Связь между объектами задана схемой, значит подтвердить её можно дёшево, любой одной записью.
Ошибка внутри неё в том, что выборка проверяет состояние конкретной записи, а не наличие связи. Одна битая ссылка в проверяемой записи выносит вердикт по всей колонке. И чем старее база, тем выше шанс, что такая запись найдётся: базы, которые дожили до свёртки, живут не первый год, битые ссылки в них есть по определению.
Гипотеза отброшена, дешёвого способа определить держателей не осталось. Полный подсчёт вернулся в обязательную часть, время аудита выросло. Плата за то, чтобы не спутать 439 с восемью миллионами.
Косвенный признак того же дефекта стоит запомнить отдельно: доля сирот выше 90 % это красный флаг сам по себе. В работающей базе такого почти не бывает. Когда инструмент показывает, что вокруг почти всё мусор, проверять надо инструмент.
Асимметрия, из которой растёт всё остальное
Теперь про то, почему история со сканом не частный случай.
Две ошибки в этой работе стоят принципиально разного.
Оставить лишнее. Клиент потерял немного дискового места. Видно сразу, исправляется в любой момент, стоит денег ровно один раз и немного.
Удалить читаемое. Сломанное проведение документов, битые остатки, пропавшая дебиторка. Обнаруживается через недели, когда бухгалтер не сходится с контрагентом. Исправляется восстановлением из бэкапа, то есть потерей всей работы после свёртки. Часто не исправляется вообще.
Цена исходов несопоставима, а решение по каждой таблице принимается при неполной информации. Отсюда следует правило, которое я и выношу на обсуждение: все решения смещены в одну сторону, при сомнении выбирается консервативный вариант.
С этим спорят, и спорят по делу. Разбор возражения будет в конце, вместе со счётом.
Каталог из десяти решений
Дисциплину даёт закрытый список действий: по каждой таблице аналитик ставит одно значение из десяти. Формулировки «почистить» и «разобраться» в вердикт не проходят.
| # | Действие | Что означает | Класс риска |
|---|---|---|---|
| 1 | оставить_свежие |
оставить записи за актуальный период, остальное удалить | деструктивное |
| 2 | удалять_порциями |
удалять порциями по периоду, малыми пачками | деструктивное |
| 3 | порциями_тяжёлые |
то же для объектов с хранилищами значений, пачки мельче | деструктивное |
| 4 | пересоздать |
перенести выжившие записи в чистый объект | деструктивное |
| 5 | не_трогать_механизм |
не трогать: есть работающий штатный механизм очистки | безопасное |
| 6 | не_трогать_читатель |
не трогать: есть живой читатель | безопасное |
| 7 | уйдёт_за_регистратором |
удалится автоматически за регистратором при каскаде | безопасное |
| 8 | нужен_анализ_кода |
нужен анализ кода, вердикт невозможен | безопасное |
| 9 | свернуть_остатками |
сворачивать через ввод остатков | особое |
| 10 | только_после_каскада |
чистить можно только после каскада документов | условное |
Сами названия тут вторичны. Ценность в том, что аналитик уже не может написать «вроде можно почистить»: он обязан выбрать одно значение, и каждое тянет за собой свои требования к доказательствам. Прозу невозможно ни проверить, ни исполнить механически, а список из десяти значений можно и то и другое.
Вспомогательные таблицы итогов и срезов отдельного вердикта не получают, они наследуют решение головного объекта. Иначе список кандидатов раздувается в разы и в нём тонет смысл.
Правила, купленные инцидентами
Дальше пять правил. Каждое стоило своего инцидента, и ни одно не выведено из общих соображений.
«Не нашёл читателей» не равно «читателей нет»
Регистр сведений со статусами кассовых чеков выглядел классическим лог-мусором. Статусы, кому они нужны, чистим.
Его читало проведение приходных и расходных кассовых ордеров через срез последних с отбором по документу. Чистка на глаз сломала бы проведение кассовых документов по всей сети.
Отсюда требование: деструктивное решение выносится, только когда перечислены все классы читателей и по каждому есть доказательство отсутствия. Классов пять: код конфигурации, внешние обработки, ссылочные держатели в СУБД, обмены и подписки на события, штатные механизмы платформы. Не закрыт хотя бы один - решение понижается.
Про внешние обработки стоит сказать отдельно. В той базе их жило 204 штуки, и 35 из них упоминали разбираемые объекты. Обработка, годами лежащая в базе без владельца, имеет свойство читать ровно ту таблицу, которую вы посчитали мёртвой.
Включённый регламент очистки не означает работающий
Регламентное задание очистки устаревших версий объектов было включено и отрабатывало успешно примерно за две минуты. Каждый раз, годами.
При этом удаляло ноль строк. Срок хранения версий стоял «Бессрочно», функция расчёта границы удаления возвращала первое января первого года, удалять было нечего. Ошибок нет, задание отчитывалось успешно, лог чистый.
Версии объектов копились непрерывно с 2017 по 2026 год: 113,7 миллиона строк на 451 гигабайт.
Принцип, который стоит за этим случаем, шире свёртки. Смотреть надо на данные в базе, статус процесса про них ничего не говорит. «Задание успешно завершилось» и «задание сделало работу» это разные утверждения, и первое не влечёт второго.
Проверяется одним движением: сопоставить заявленный срок хранения с реальным возрастом самой старой строки по гистограмме распределения по годам. Возраст больше двух-трёх заявленных сроков означает сломанный механизм. Обратное тоже работает, и это единственный способ доказать вердикт «не трогать, есть механизм»: гистограмма показывает, что хвост реально обрезается.
Часть таблиц чистить не надо, и это надо доказать заранее
Регистр сведений с режимом записи «подчинён регистратору» уходит каскадом вместе со своим документом. Отдельного удаления он не требует, а попытка почистить его руками до каскада создаёт лишний деструктивный шаг там, где всё сделает платформа.
В этой базе через такой вердикт прошёл регистр на 31 гигабайт: в плане работ по нему нет ни одной операции, он просто исчезает следом за документами. Проверять режим записи стоит до того, как таблица попала в список кандидатов на удаление, иначе половина плана состоит из работы, которую делать не нужно.
Граница периода - самое опасное место работы
Отдельная ловушка, на которой легко потерять час, а можно потерять данные. Граница отсечения по периоду - самое опасное место всей работы: ошибка в ней не выдаёт себя ошибкой, она просто меняет множество строк.
Условие, промахнувшееся мимо диапазона, молча не отбирает ничего. Это ещё удачный исход. Неудачный - когда сравнение уходит в другую сторону и под удаление попадает всё. Поэтому любое условие по периоду сначала считается на количество и сверяется с гистограммой по годам, и только потом идёт в дело. Число строк, которые собираются исчезнуть, должно быть известно до того, как они исчезнут.
Верные решения в неверном порядке дают неверный результат
Восемь с лишним миллионов чеков держались записями регистра статусов. Сироты среди чеков появляются только после удаления старых статусов.
Правильный порядок: каскад документов, затем статусы, затем очистка осиротевших чеков. В обратном порядке получается удаление чеков, которые ещё читаются живыми статусами, а те читаются проведением касс.
Каждое решение по отдельности было верным. Убивал порядок.
Поэтому набор вердиктов оформляется направленным графом без циклов: у каждого условного решения явно записано, от чего оно зависит, исполнение идёт в топологическом порядке. Вопрос, который надо задать по каждому деструктивному решению: изменится ли множество удаляемых строк, если сначала выполнить другое. Если да, это ребро зависимости.
Лестница агрессивности
Правило «при сомнении консервативнее» останется декларацией, пока у него нет механики. Механика такая:
пересоздать / оставить_свежие / порциями_тяжёлые
↓
удалять_порциями
↓
только_после_каскада
↓
нужен_анализ_кода
↓
не_трогать_*
Любая неустранённая неопределённость двигает решение вниз по лестнице. Движение вверх разрешено только при появлении нового доказательства, закрывающего конкретное сомнение.
Отдельно перечислено, что доказательством не является: «скорее всего мусор», «похоже на лог», «в прошлый раз похожее удаляли». Это гипотезы, и гипотеза вверх по лестнице не двигает.
Перед фиксацией каждого деструктивного решения аналитик перечисляет открытые сомнения. Список пуст и все классы читателей закрыты - решение остаётся. Иначе понижение, без исключений и без «но объём огромный, жалко терять».
Где этот метод не работает
Граница у него есть, и она неприятная. Весь разбор строится на том, что поведение системы описано конфигурацией и данными в СУБД. Расширение с признаком изменения структуры данных ломает это допущение: оно может переопределить проведение документа, и тогда читатель таблицы живёт в расширении, а основная конфигурация о нём молчит.
Анализ, сделанный по основной конфигурации при наличии такого расширения, неполон. Слово выбрано точно: класс читателей «код конфигурации» остаётся незакрытым, и по правилу выше все деструктивные вердикты в базе понижаются. Лечится это только разбором самого расширения, а до тех пор список безопасных для удаления таблиц считать готовым нельзя.
Второе ограничение честнее назвать сразу: всё описанное касается баз, где потеря данных бьёт по учёту. Для базы, из которой ничего не проводится и ничем не отчитываются, протокол избыточен и его цена не окупится.
Счёт за осторожность
Теперь честно про цену, потому что она есть и её обычно не показывают.
Регистр накопления со связями залоговых билетов выглядел висячим фантомным хвостом на 16,7 миллиарда в валюте учёта. Классический след старого бага: сумма, которой в учёте быть не может.
При разборе выяснилось, что это живая структура перезалога, её читает бизнес-логика проведения, и 307 записей внутри держат реальные 4,34 миллиона в той же валюте на балансовом счёте.
Посчитаем долю. 4,34 миллиона от 16,7 миллиарда это 0,026 %. Агрессивная трактовка «весь этот хвост фантомный» была права на 99,974 %.
Вот это и есть счёт. Чтобы не потерять двадцать шесть тысячных процента, регистр остался нетронутым целиком, вместе с гигабайтами, которые почти наверняка были мусором. И такое решение принималось не один раз: каждое нужен_анализ_кода это таблица, которую не тронули из-за нехватки доказательств. Нужность её при этом никто не доказал.
Возражение из начала статьи звучит теперь предметнее. Оно не про трусость. Оно про то, что протокол стоит денег клиента: граф зависимостей, пять классов читателей, полный пересчёт держателей и гистограммы по каждой крупной таблице. Это самая долгая часть аудита, и растёт она по числу крупных таблиц; размер базы сам по себе на неё влияет слабо. На небольшой базе такой протокол выходит дороже самих гигабайтов, и разумнее удалить смелее, а потом чинить по факту.
У меня нет ответа, где именно проходит эта граница. На двух терабайтах протокол окупается очевидно. На паре сотен гигабайт я бы уже задумался.
Что получилось по цифрам
Итог я уже называл в первой статье, повторю коротко ради полноты. База уменьшилась с 2 341 гигабайта до 573, на 75 %. Расхождений остатков до и после ноль.
Полная проверка ссылочных колонок схемы после свёртки входит в приёмку наравне со сверкой остатков. Без неё результат принимать нельзя: остатки сходятся и при битой ссылочной части, они считаются по регистрам, ссылки в этот расчёт не входят.
Консервативный подход не помешал убрать три четверти базы.
Что забрать себе
- Держателей ссылок никогда не определять по выборке. Доля сирот выше 90 % означает сломанный метод обнаружения.
- Перед деструктивным решением закрывать все классы читателей, включая внешние обработки. «Не нашёл» не считается.
- Работоспособность регламентов очистки доказывать гистограммой распределения строк по годам. Зелёный статус задания доказательством не является.
- Строить граф зависимостей между решениями и исполнять в топологическом порядке.
- Сомнение всегда понижает агрессивность, вверх - только по новому доказательству.
Ни одно из этих правил не про 1С специально. Работает это везде, где удаление необратимо, а информация неполна.
Открытый вопрос
Мой тезис: при неполной информации аналитик обязан ошибаться в одну сторону, и лучше оставить лишнее, чем удалить читаемое.
Слабое место тезиса я показал выше сам. Протокол, который защищает от катастрофы, съедает основную часть бюджета аудита, и на базе средних размеров он может стоить дороже той катастрофы, от которой защищает.
Мне интересно, где по вашему опыту проходит порог. При каком объёме базы и при каком числе крупных таблиц полный протокол перестаёт окупаться и разумнее удалять смелее, с готовностью чинить?
Кто считал это на своих проектах - несите цифры, особенно если они противоречат написанному выше.
Другие наши инструменты диагностики 1С:
- Карта объёмов базы 1С - с чего начинается любая чистка: из чего на самом деле состоит база и куда ушли гигабайты, включая служебные таблицы платформы.
- Трансформатор SQL в запрос 1С - когда в плане запроса видно имя таблицы, а понять надо, какой объект конфигурации за ней стоит.
- Чек-ап СУБД под 1С - 40 с лишним проверок настроек SQL Server, чтобы после свёртки не упереться в них же.
- Проверка базы перед миграцией на PostgreSQL - предполётный чек-лист, если после чистки планируется смена СУБД.
Вступайте в нашу телеграмм-группу Инфостарт