Удалили данные из базы 1С, а файл на диске весит столько же: проверка и план ночи сжатия
Иван Недомолков · Dərc edilib:
Типичная жалоба после свертки, чистки служебных таблиц или удаления старых данных: “удалили почти всё, а свободного места на сервере не прибавилось”. Файл базы на диске весит те же терабайты, мониторинг показывает тот же красный диск.
Это нормальное поведение MS SQL Server. Удаление освобождает страницы внутри файла данных, но сам файл остаётся того размера, до которого когда-то вырос. Вернуть место операционной системе можно только сжатием файла, и у этого сжатия на больших объёмах есть свои ловушки.
Ниже порядок, которым мы проходим такую работу: проверить, проверить окружение, сжать, привести индексы в порядок. Цифры везде из одного проекта: база 1С розничной сети fashion-сегмента, три страны, около двухсот касс. Там после очистки служебного кэша файл в 4 197 ГБ держал внутри всего около сотни гигабайт данных, и за две ночи его ужали до 105 ГБ. Сама история очистки рассказана в разборе про 97,5 % базы, здесь только то, что было с файлом потом.
Шаг 1. Посмотреть, что похудело: данные или файл
Одна выборка из sys.database_files отвечает на вопрос за секунду. Размеры в ней хранятся в страницах по 8 КБ, поэтому деление на 128 даёт мегабайты, а ещё на 1 024 гигабайты.
-- Выполнить в контексте базы 1С: USE YourDb;
SELECT
name AS file_name,
type_desc AS file_type,
CAST(size / 128.0 / 1024 AS decimal(12, 1)) AS file_gb,
CAST(FILEPROPERTY(name, 'SpaceUsed') / 128.0 / 1024
AS decimal(12, 1)) AS used_gb,
CAST((size - FILEPROPERTY(name, 'SpaceUsed')) / 128.0 / 1024
AS decimal(12, 1)) AS free_inside_gb
FROM sys.database_files;
Если free_inside_gb большое, а file_gb прежнее, перед вами ровно наш случай. На тестовой копии той базы картина после двух раундов очистки была такая:
| Замер | До очистки | Первый раунд | Второй раунд |
|---|---|---|---|
| used_gb (данные) | 4 099 | 386 | 160 |
| free_inside_gb | меньше 1 | 3 713 | 3 940 |
| file_gb (на диске) | 4 099 | 4 099 | 4 099 |
Внутри освободилось 96 %, файл не изменился ни на гигабайт. Мы сами однажды, готовя отчёт, перепутали эти колонки и подняли тревогу на пустом месте, так что сначала смотрите, какое число вы вообще сравниваете.
Полный бэкап с компрессией берёт только занятые страницы. Поэтому он худеет сразу после очистки, без всякого сжатия файла: у нас уже после первой ночи он весил 21 ГБ при файле почти в три терабайта. Сжатие нужно диску сервера, бэкапу оно ничего не добавит.
Шаг 2. Быстрый режим попробовать можно, рассчитывать на него нельзя
У DBCC SHRINKFILE есть режим, который ничего не двигает:
-- Отрезать только пустой хвост файла, без перемещения страниц
DBCC SHRINKFILE (N'YourDb_Data', TRUNCATEONLY);
Он отдаёт диску свободное место только в самом конце файла, после последней занятой страницы. Если база росла годами и данные удалялись из середины, в конце почти наверняка стоят живые страницы, и хвост отрезать нечем.
У нас так и вышло. После первого раунда очистки команда вернулась мгновенно и файл не уменьшила. После второго шла шесть минут и снова не вернула ни гигабайта: сервер перебирал свободные участки посреди файла, а в конце пусто не было. Вывод практический: если TRUNCATEONLY вернул ноль, нужно полное сжатие с переносом страниц, и под него надо планировать окно.
Шаг 3. Проверить окружение до ночи работ
Почти всё, что останавливало наше сжатие, к самому сжатию отношения не имело. Это расписание, модель восстановления и соседи по серверу. Все три вещи проверяются днём, заранее.
Задания агента SQL, которые трогают базу
Плановый полный бэкап, стартовавший посреди сжатия, уронил операцию с ошибкой 3140: бэкап и сжатие одного файла одновременно не идут. Переключение модели восстановления по расписанию тоже живёт в заданиях. Список всех включённых заданий, где упоминается база:
SELECT j.name AS job_name, s.step_id, s.step_name, s.database_name, s.command
FROM msdb.dbo.sysjobs AS j
JOIN msdb.dbo.sysjobsteps AS s ON s.job_id = j.job_id
WHERE j.enabled = 1
AND (s.database_name = N'YourDb' OR s.command LIKE N'%YourDb%')
ORDER BY j.name, s.step_id;
На ночь работ такие задания отключаются, а утром включаются обратно. Отдельно проверьте, что отключение действительно применилось.
Модель восстановления
Сжатие пишет в журнал транзакций каждое перемещение страницы. В модели SIMPLE журнал сам очищается на контрольных точках. В FULL он копит всё до ближайшего бэкапа журнала, и на терабайтном переносе упирается в потолок: ошибка 9002, журнал заполнен, операция стоит.
У нашей базы модель переключалась заданиями: в 22:30 ставилось SIMPLE, в 00:02 возвращалось FULL. Окно сжатия этим расписанием резалось посередине. Правило, к которому пришли: на всё время работ база стоит в SIMPLE, переключающие задания отключены.
SELECT name, recovery_model_desc, log_reuse_wait_desc
FROM sys.databases
WHERE name = N'YourDb';
ALTER DATABASE [YourDb] SET RECOVERY SIMPLE;
Возврат в FULL после работ обязателен и сразу с полным бэкапом, иначе цепочка журналов порвана и восстановление на точку во времени молча пропало. Подробнее про журнал и 9002 есть в статье про разросшийся журнал транзакций.
Соседние базы на том же экземпляре
На нашем сервере жила вторая база той же сети. В ночь работ на ней завис отчёт, занял 24 потока, проработал 4,3 часа и душил весь экземпляр. Сжатие нашей базы почти встало и пошло дальше только после того, как сеанс отчёта сняли. Поэтому перед стартом и по ходу работ смотрим на все тяжёлые запросы экземпляра, а не только на свою базу:
SELECT
r.session_id,
DB_NAME(r.database_id) AS db,
r.command,
r.status,
r.wait_type,
r.total_elapsed_time / 60000 AS minutes,
r.cpu_time / 1000 AS cpu_sec,
(SELECT COUNT(*) FROM sys.dm_os_tasks AS t
WHERE t.session_id = r.session_id) AS tasks
FROM sys.dm_exec_requests AS r
WHERE r.session_id <> @@SPID
AND r.session_id > 50
ORDER BY r.cpu_time DESC;
Колонка tasks показывает, сколько потоков держит запрос. Отчёт с двумя десятками потоков, который идёт несколько часов, ночью сжатия снимаем или договариваемся о нём заранее.
Шаг 4. Порции или один проход
Привычный совет сжимать порциями выглядит разумно: каждая укладывается в окно, прерывание ничего не ломает, перенесённые страницы остаются на новых местах. Наш регламент до прода был написан именно так: несколько ночей, каждую отрезать по куску. Прожил он одну ночь.
| Ночь 1, порции по 500 ГБ | Ночь 2, одна команда | |
|---|---|---|
| Файл до | 4 197 ГБ | 2 999,6 ГБ |
| Файл после | 2 999,6 ГБ | 300 ГБ |
| Вернули диску | около 1,2 ТБ | около 2,7 ТБ |
| Сколько шло | вся ночь, с зависаниями | 89 минут |
Порции застревали на хвосте. Основной объём переносился, а на последних гигабайтах сеанс висел в ожидании SLEEP_TASK, то есть ждал, не делая работы. Сделанное фиксировали так: база в OFFLINE, обратно в ONLINE, затем TRUNCATEONLY. Перевод снимал зависшую операцию, перенесённые страницы оставались на новых местах, и освободившийся за ними хвост теперь отрезался. Почему зависание снимается именно так, мы не разбирали, это наша версия по результату.
-- Снять зависшую операцию вместе со всеми сеансами базы
ALTER DATABASE [YourDb] SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [YourDb] SET ONLINE;
-- Забрать хвост, который освободился за перенесёнными страницами
DBCC SHRINKFILE (N'YourDb_Data', TRUNCATEONLY);
Тот же перевод снимал и залипший признак сжатия: после прерванных попыток база считала, что сжатие ещё идёт, и новую операцию не принимала.
На вторую ночь запустили одну команду на весь остаток:
-- Целевой размер в мегабайтах: 300 ГБ
DBCC SHRINKFILE (N'YourDb_Data', 307200);
Внутри 89 минут было две фазы. Примерно 45 минут работа упиралась в процессор: по отчёту это была фоновая доработка освобождения страниц, оставшаяся от очистки. SQL Server очень большие объёмы освобождает не сразу, сначала помечает, потом разбирает в фоне. Наша трактовка такая: почти 4 ТБ освободились “за две минуты” только на уровне пометки, а разборка тянулась до второй ночи, и каждая порция первой ночи натыкалась на неё заново. Отдельного замера под эту версию нет, с фазами большого прохода она сходится.
Оставшиеся примерно 44 минуты файл уменьшался на 50-60 ГБ в минуту. В отчёте стоит около 53, наш пересчёт 2 700 ГБ на 44 минуты даёт 61. Обе исходные цифры приблизительные, читайте их как порядок.
Мы не утверждаем, что один проход быстрее всегда. После массовой очистки, где освободилось почти всё, у нас было так, и мерить это стоит на копии своей базы.
Шаг 5. Следить за сжатием, пока оно идёт
DBCC SHRINKFILE отдаёт процент выполнения в sys.dm_exec_requests. Тем же запросом видно, ждёт ли операция и чего именно:
SELECT
session_id,
command,
percent_complete,
wait_type,
wait_time / 1000 AS wait_sec,
total_elapsed_time / 60000 AS minutes
FROM sys.dm_exec_requests
WHERE command LIKE N'Dbcc%';
Если percent_complete долго не двигается, а в wait_type стоит SLEEP_TASK, операция стоит. Если там ожидание блокировки, ищите, кто держит. Если процент растёт медленно при свободном процессоре, проверьте запрос из шага 3 про соседей.
И размер файла в процессе удобно снимать тем же запросом из шага 1: file_gb уменьшается по ходу.
Шаг 6. После сжатия: индексы, статистика, бэкап
Сжатие переносит страницы, не глядя на их порядок, поэтому индексы после него фрагментированы почти гарантированно. Перестраивать их до сжатия бессмысленно, сжатие всё перемешает снова. Порядок один: сначала сжатие, потом перестроение.
У нас перестроение 200 индексов заняло 44 минуты без единой ошибки, вместе с ним обновили статистику. Занятое место внутри файла при этом ещё сократилось, со 104 до 90,7 ГБ, перестроенные индексы плотнее. Скрипт отбора индексов и параметры перестроения разобраны в SQL-версии этого проекта, повторять его здесь не будем.
Последним шагом:
ALTER DATABASE [YourDb] SET RECOVERY FULL;
-- Новый полный бэкап сразу, от него теперь отсчитывается цепочка журналов
BACKUP DATABASE [YourDb]
TO DISK = N'D:\Backup\YourDb_after_shrink.bak'
WITH COMPRESSION, CHECKSUM;
Этот бэкап весил 18,34 ГБ. До проекта ночной бэкап базы занимал 556 ГБ и шёл два часа. Худеет он от очистки данных, сжатие файла тут ни при чём.
Сколько оставить запаса
Сжимать файл вплотную к данным не обязательно. База растёт, и автоприрост файла посреди рабочего дня может дать паузу пользователям. Мы дожали файл с 300 до 105 ГБ при данных около 104 ГБ, а в отчёте записали опцию: если нужен запас, файл разово выставляется на 130-150 ГБ, хвост уже чистый, это минуты.
-- Разово задать файлу запас под рост, в мегабайтах: 140 ГБ
ALTER DATABASE [YourDb]
MODIFY FILE (NAME = N'YourDb_Data', SIZE = 143360MB);
Регулярное сжатие по расписанию при этом вредно. Файл сжимается, снова растёт, индексы каждый раз перемешиваются, сервер тратит ночи на работу без результата. Разовое сжатие после большой очистки другое дело: 97 % объёма ушло и обратно не вернётся, и это самый короткий путь вернуть диск. Есть штатный вариант с выгрузкой и загрузкой через DT, но замера на терабайтах по нему у нас нет.
Чек-лист ночи сжатия
- Запрос из шага 1: сколько занято и сколько свободно внутри файла.
TRUNCATEONLYна копии. Вернул ноль, значит нужно полное сжатие и окно.- Задания агента SQL, которые трогают базу: бэкапы, переключение модели, обслуживание. Отключить на ночь.
- Модель
SIMPLEна всё время работ. - Тяжёлые запросы на всём экземпляре, включая соседние базы.
- Сжатие с наблюдением за
percent_completeиwait_type. - Перестроение индексов и статистика после сжатия, не до.
FULLи полный бэкап в ту же ночь.- Включить отключённые задания обратно.
С чего начинать, если база выросла
Всё это начинается после того, как понятно, что именно занимает место. Две ночи сжатия ничего не стоят, если удалили не то. Первый шаг у нас всегда карта: какие объекты сколько весят и где объём не совпадает с ожиданием. Её строит обработка Карта объёмов базы 1С: обходит объекты метаданных, отдельно показывает служебные таблицы без объекта за ними и считает долю каждой. На этой базе карта и показала, что 92 % объёма занимает мёртвый кэш разграничения доступа. Если же вопрос стоит про свёртку, сколько места она реально вернёт, заранее считает Калькулятор свёртки базы 1С: так понятно, стоит ли вообще затевать удаление, которое потом придётся сжимать.
Свертку ещё только выбираете? Сначала сравните готовую обработку свертки и проект подрядчика: коробке хватает истории, которая режется по дате, и сервера 1С на Windows. Если хотите пройти весь путь от карты до сжатого файла с подрядчиком, который это уже делал на терабайтах, посмотрите свертку и обрезку базы 1С и кейс проекта целиком.