MAATRIX / Блог / Что происходит с данными, когда вы удаляете файл на сервере

Что происходит с данными, когда вы удаляете файл на сервере

MAATRIX

Вы набираете rm important.sql, нажимаете Enter — и файл исчезает из ls за долю секунды. Кажется логичным, что данные тоже мгновенно стёрлись с диска. На деле почти всегда это не так: rm убирает лишь запись в каталоге, а сами блоки данных могут пролежать на диске нетронутыми ещё долго. Разберёмся, что происходит на самом деле, почему место иногда «не освобождается» после удаления и какие у вас реальные шансы вернуть файл, если удаление было ошибкой.

Что физически происходит на диске при rm

В файловых системах семейства ext (ext4 на большинстве Linux-серверов) у каждого файла есть две независимые сущности. Первая — inode, структура метаданных: права, владелец, размер, время изменения, количество ссылок и указатели на блоки данных, где реально лежит содержимое. Вторая — запись в каталоге (directory entry), которая просто сопоставляет имя файла номеру inode. Само имя нигде внутри inode не хранится — оно живёт только в каталоге.

Когда вы выполняете rm important.sql, происходит ровно это: из каталога убирается запись с именем important.sql, а у inode, на которую она указывала, уменьшается счётчик ссылок (link count) на единицу. Блоки данных на диске при этом не трогаются вообще — ни один байт содержимого файла не перезаписывается и не обнуляется в момент удаления. Именно поэтому rm — операция почти мгновенная независимо от размера файла: удаление гигабайтного дампа базы и удаление пустого текстового файла занимают одинаковое время, потому что физически переписывается только метаданные каталога, а не содержимое.

Проверить это можно на живом примере:

touch /tmp/test.txt
stat /tmp/test.txt | grep Inode
ls -i /tmp/test.txt

Оба вывода покажут один и тот же номер inode. После rm /tmp/test.txt эта inode либо освобождается для повторного использования, либо продолжает существовать — в зависимости от одного условия, о котором ниже.

inode, link count и directory entry: вся механика в одной картинке

Ключевая деталь, которую многие упускают: у одной inode может быть несколько имён одновременно. Это механизм жёстких ссылок:

echo "data" > original.txt
ln original.txt hardlink.txt
ls -i original.txt hardlink.txt

Оба имени укажут на один и тот же номер inode, а stat покажет Links: 2. Удаление любого из двух имён (rm original.txt) не тронет данные — просто уменьшит счётчик ссылок до 1, а второе имя (hardlink.txt) продолжит указывать на те же данные как ни в чём не бывало.

Это подводит к главному правилу: данные освобождаются не когда исчезает имя файла, а когда link count падает до нуля и одновременно нет ни одного процесса, держащего эту inode открытой через файловый дескриптор. Оба условия обязательны. Если хотя бы одно не выполнено — блоки данных на диске остаются как есть, просто становятся невидимыми через обычные ls и cat, потому что путь к ним из каталога исчез.

Именно это отличие — между «файл удалён из каталога» и «данные физически недоступны» — и объясняет всё остальное поведение, описанное ниже: почему существуют инструменты восстановления удалённых файлов, почему место после rm иногда не освобождается сразу, и почему на SSD всё работает не так предсказуемо, как на классическом HDD.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Когда данные всё же освобождаются: link count 0 и закрытые дескрипторы

Ядро Linux хранит для каждой inode два счётчика, которые вместе решают её судьбу: количество жёстких ссылок (link count) и количество открытых файловых дескрипторов на неё среди всех работающих процессов. Пока ненулевым остаётся хотя бы один из этих счётчиков, ядро не трогает inode и не помечает её блоки данных как свободные для переиспользования.

Типичный порядок событий при обычном rm:

  1. Link count уменьшается до 0 (запись в каталоге убрана, других жёстких ссылок нет).
  2. Ядро проверяет: есть ли открытые файловые дескрипторы у этой inode среди работающих процессов.
  3. Если дескрипторов нет — inode и её блоки данных немедленно помечаются свободными, доступными для переиспользования файловой системой.
  4. Если дескриптор есть (какой-то процесс уже открыл файл на чтение или запись до удаления) — inode остаётся «живой», данные физически лежат на диске, место в df -h не освобождается, и это состояние сохраняется до тех пор, пока процесс не закроет дескриптор (или не завершится).

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

Место не освобождается после удаления: держит открытый процесс

Это одна из самых частых практических ловушек на сервере: вы удаляете разросшийся лог-файл, а df -h продолжает показывать тот же процент заполнения диска. Причина почти всегда одна — процесс, который пишет в этот файл, открыл его дескриптор раньше, чем вы выполнили rm, и продолжает держать его открытым, ничего не зная об удалении из каталога.

Классический сценарий: nginx или ваше приложение открыло лог-файл на запись при старте, вы удалили файл руками вместо настройки ротации — процесс как ни в чём не бывало продолжает писать в удалённый файл, который больше не виден через ls, но место расходует так же, как и до удаления.

Найти такие «удалённые, но открытые» файлы и понять, кто их держит, можно так:

lsof +L1 /var/log

Флаг +L1 у lsof показывает файлы, у которых количество ссылок (link count) меньше единицы — то есть ровно те inode, что уже удалены из каталога, но ещё открыты каким-то процессом. Вывод покажет PID, имя процесса, дескриптор и размер, который эта inode реально занимает на диске.

Более широкий, но не всегда доступный по правам вариант — искать по всей системе:

lsof / 2>/dev/null | grep deleted

В колонке пометки будет видно (deleted) рядом с именем файла — это признак ровно той же ситуации.

Дальше два варианта действий:

  • Корректно закрыть дескриптор — перезапустить или мягко перечитать конфигурацию у процесса-владельца. Для сервисов с ротацией логов это штатная операция: systemctl reload nginx или сигнал SIGHUP заставляет процесс переоткрыть файлы логов по тем же путям, после чего старый (удалённый) дескриптор закрывается, и место освобождается — новый файл создаётся заново с нуля.
  • Спасти содержимое перед закрытием, если оно ещё нужно. Пока дескриптор открыт, данные доступны через /proc/<PID>/<FD> — можно скопировать их в новый файл прямо оттуда:
lsof +L1 /var/log
# смотрим PID и номер дескриптора (FD) в выводе, например 12345 и 4
cp /proc/12345/fd/4 /var/log/app-recovered.log

Это рабочий приём, которым стоит пользоваться до перезапуска процесса, а не после — как только дескриптор закроется, доступ к содержимому через /proc пропадёт вместе с ним.

Профилактика проще лечения: правильная настройка logrotate с директивой copytruncate (или штатной пересылкой сигнала процессу после ротации) исключает саму возможность этой ситуации — файл не удаляется целиком, а обрезается на месте, и процесс продолжает писать в тот же дескриптор без потери места. Если на сервере регулярно кончается место на диске по похожим причинам, стоит посмотреть более широкий разбор диагностики диска — там описан общий порядок поиска, куда уходит место, включая эту же категорию проблем: закончилось место на диске VPS.

SSD и TRIM: почему восстановление там куда менее надёжно

Всё описанное выше — про классическую механику файловых систем, которая изначально проектировалась под жёсткие диски (HDD), где «пометить блок свободным» и «физически стереть данные» — две совершенно разные, разнесённые во времени операции. На SSD и NVMe-накопителях в игру вступает дополнительный механизм — TRIM, и он меняет картину заметно.

Проблема, которую решает TRIM: SSD не умеет перезаписывать отдельную ячейку напрямую, как HDD, — сначала он обязан стереть целый блок (обычно существенно крупнее одного файлового блока), и только потом записать в него новые данные. Если контроллер накопителя не знает, какие логические блоки файловая система считает «свободными» (а с точки зрения самого диска все когда-либо записанные секторы выглядят одинаково занятыми — он не видит файловую систему изнутри), он вынужден при каждой записи выполнять более медленный цикл «стереть весь блок → записать заново», что со временем ощутимо бьёт по скорости записи и по ресурсу самих ячеек.

TRIM — это команда, которой файловая система заранее сообщает контроллеру SSD: «эти логические блоки больше не используются, можешь их стереть заранее, в фоне, когда будет время». Получив эту команду, контроллер нередко стирает содержимое ячеек проактивно, в рамках сборки мусора (garbage collection) и wear leveling — задолго до того, как туда реально запишут что-то новое. Из этого следует практический вывод: на SSD с активным TRIM данные удалённого файла могут физически исчезнуть с накопителя намного раньше и полнее, чем на HDD, где до фактической перезаписи блока прежнее содержимое обычно остаётся читаемым сколь угодно долго.

Проверить, включён ли TRIM на вашей системе, можно так:

systemctl status fstrim.timer
lsblk --discard

Первая команда покажет, активен ли периодический systemd-таймер, который штатно вызывает fstrim по расписанию (в большинстве современных дистрибутивов он включён по умолчанию). Вторая покажет для каждого блочного устройства колонки DISC-GRAN и DISC-MAX — ненулевые значения означают, что устройство поддерживает TRIM. Отдельно стоит проверить, не примонтирован ли раздел с опцией discard в /etc/fstab или /etc/crypttab — в этом случае TRIM выполняется не по расписанию, а немедленно при каждом удалении, что ещё сильнее сокращает окно для восстановления.

Важная оговорка для арендованного VPS: если диск виртуальной машины — это сетевое хранилище или тонкий том поверх образа, а не физический NVMe напрямую, реальное поведение TRIM зависит от инфраструктуры хостинг-провайдера на уровне гипервизора и снаружи гостевой системы обычно не проверить — гостевая ОС отправляет TRIM, а что физически происходит на стороне хранилища, решает хост. Если вопрос критичен, у провайдера стоит уточнить, как реализован discard для конкретного тарифа. Про деградацию накопителей во времени подробнее здесь: как SSD умирает медленно: wear leveling.

Экстренное восстановление случайно удалённого файла

Если файл удалили по ошибке и он не открыт ни одним процессом (то есть lsof +L1 его не показывает), шансы на восстановление есть, но они убывают с каждой секундой работы диска — потому что любая новая запись на том же разделе может занять освободившиеся блоки данных удалённого файла. Действовать нужно быстро и в правильном порядке.

Шаг 1. Прекратите любую запись на этот раздел немедленно. Это самое важное действие, и оно важнее самого восстановления. Если раздел не критичен для работы прямо сейчас — remount его в режим только для чтения:

mount -o remount,ro /path/to/mountpoint

Если это корневой раздел системы и remount невозможен без остановки сервисов — минимизируйте любую активность: остановите приложения, которые пишут на диск, отложите логи, не запускайте apt update и что угодно ещё, что порождает новые файлы.

Шаг 2. Не пишите результаты восстановления на тот же раздел. Это частая ошибка: человек ставит утилиту восстановления через пакетный менеджер (что само по себе уже пишет новые файлы на диск) и сохраняет найденные файлы туда же, откуда их восстанавливает — рискуя затереть как раз те блоки, которые ищет. Правильно — смонтировать отдельный диск или сетевое хранилище и писать результат туда.

Шаг 3. Используйте инструмент, подходящий под ситуацию.

  • Для ext4 существует extundelete, но у современных версий e2fsprogs и журналируемого ext4 он работает не всегда надёжно — журналирование файловой системы усложняет однозначное восстановление структуры удалённого файла, и результат стоит рассматривать как «может получиться», а не как гарантию.
  • testdisk и photorec (part of одного пакета) работают на уровне сырых секторов диска, не полагаясь на структуры конкретной файловой системы, и поэтому более универсальны — способны находить файлы по сигнатурам содержимого (заголовкам форматов) даже когда метаданные файловой системы уже повреждены или недоступны. Обратная сторона — photorec не восстанавливает исходные имена файлов и путь, только содержимое и определённый по сигнатуре тип.
  • Если на сервере настроены снапшоты — LVM snapshot, снапшот файловой системы (btrfs/ZFS) или снапшот на уровне хостинг-провайдера, — почти всегда быстрее и надёжнее восстановить файл именно оттуда, а не гоняться за ним по сырым секторам. Первым делом стоит проверить именно этот путь:
lvs -o lv_name,origin

Если в выводе есть снапшоты нужного тома — файл, скорее всего, проще достать примонтировав снапшот отдельно, чем восстанавливать посекторно.

Дать точную оценку шансов на успех в процентах или во времени было бы нечестно — она зависит от объёма записи на диск с момента удаления, типа накопителя (на SSD с активным TRIM шансы падают резко, как описано выше), размера и фрагментации файла. Уверенно можно сказать одно: чем раньше остановлена запись на раздел после обнаружения ошибки, тем выше шансы, и это единственный фактор полностью в ваших руках.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Стирает ли rm данные сразу?

Нет. rm убирает имя файла из каталога и уменьшает счётчик ссылок на inode. Сами блоки данных на диске остаются нетронутыми до тех пор, пока link count не упадёт до нуля и не закроются все открытые файловые дескрипторы на эту inode — только после этого блоки помечаются свободными для переиспользования.

Почему df -h не показывает освобождённое место после удаления файла?

Почти всегда — потому что какой-то процесс держит файл открытым через файловый дескриптор, хотя имя файла уже удалено из каталога. Найдите его командой lsof +L1 <точка монтирования> и либо перезапустите/перечитайте конфигурацию процесса, либо (если нужно сохранить содержимое) скопируйте данные из /proc/<PID>/<FD> перед этим.

Есть ли разница между rm и shred в этом контексте?

Да, принципиальная. shred перед удалением явно перезаписывает содержимое файла случайными данными несколько раз — то есть решает ровно ту проблему, которую обычный rm не решает. На SSD, впрочем, эффективность многократной перезаписи shred спорна: из-за wear leveling контроллер может физически писать данные не в те же самые ячейки, что и раньше, так что гарантированного полного стирания через shred на SSD ожидать не стоит.

Можно ли восстановить файл, если диск — SSD с включённым TRIM?

Шансы существенно ниже, чем на HDD, и падают быстро — TRIM может привести к физическому стиранию данных контроллером в фоне вскоре после удаления, независимо от того, записали вы что-то новое поверх или нет. Если файл критичен, первым делом проверяйте наличие снапшотов или бэкапов, а не рассчитывайте на восстановление с самого накопителя.

Что произойдёт с содержимым файла, если процесс, который его держит открытым, аварийно упадёт (crash), а не завершится штатно?

Ядро всё равно закрывает все файловые дескрипторы упавшего процесса как часть его завершения, включая дескриптор на удалённый файл. Если это была последняя ссылка на inode — данные освобождаются в этот же момент, точно так же, как при штатном завершении процесса.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →