파일을 지웠는데 디스크 공간이 그대로인 이유: unlink와 열린 파일

rm으로 큰 파일을 지워도 df의 여유 공간이 늘지 않을 때가 있습니다. POSIX가 정한 링크 수와 열린 파일 규칙, du와 df가 다르게 보이는 까닭, Linux에서 원인을 찾고 공간을 되찾는 순서를 정리했습니다.

개발운영체제리눅스

디스크가 가득 차서 커다란 로그 파일을 rm으로 지웠는데 df에 보이는 여유 공간이 그대로일 때가 있습니다. 같은 디렉터리를 du로 재 보면 그 파일만큼 줄어 있습니다. 두 도구 중 하나가 틀린 것은 아닙니다. 파일의 이름은 사라졌지만 파일 자체는 아직 남아 있는 상태입니다.

df -k /var                    # 파일 시스템의 여유 공간(KB 단위)
rm /var/log/app/huge.log
df -k /var                    # 여유 공간이 그대로일 수 있다
du -sk /var/log/app           # 디렉터리 합계는 줄어든다

이름을 지우는 것과 파일을 지우는 것

Unix 계열 파일 시스템에서 디렉터리 항목, 즉 파일 이름은 파일을 가리키는 링크입니다. 한 파일에 하드 링크로 이름이 여러 개 붙을 수 있고, POSIX는 stat 구조체의 st_nlink를 그 파일의 하드 링크 수라고 정합니다.

POSIX에서 rm은 각 파일에 대해 remove()와 같은 동작을 하고, remove()는 경로가 디렉터리가 아니면 unlink()와 같습니다. unlink()는 이름 하나를 없애고 링크 수를 줄입니다.

공간이 언제 풀리는지는 POSIX.1-2024의 unlink() 설명에 나와 있습니다. 링크 수가 0이 되고, 열린 파일 디스크립터나 메모리 매핑으로 그 파일을 참조하는 프로세스가 하나도 없어야 공간이 해제됩니다. 마지막 링크를 지울 때 참조하는 프로세스가 있으면 링크는 unlink()가 반환되기 전에 제거되지만, 파일 내용의 제거는 그런 참조가 모두 사라질 때까지 미뤄집니다.

close() 설명에는 디스크립터 쪽 규칙이 있습니다. 링크 수가 0인 파일은 그 파일의 디스크립터가 모두 닫힐 때 공간이 해제된다는 내용입니다. 다만 unlink() 설명은 메모리 매핑도 참조로 세므로, 디스크립터가 다 닫혀도 파일 기반 매핑이 남아 있으면 공간은 아직 돌아오지 않습니다. 프로세스가 종료되면 열려 있던 디스크립터는 닫히고, 그 프로세스가 만든 매핑은 해제됩니다.

결국 rm은 이름을 지우는 명령입니다. 다른 하드 링크가 남아 있거나, 어떤 프로세스가 파일을 열어 두었거나 매핑해 두었다면 데이터는 디스크에 남습니다.

du와 df가 다르게 보이는 이유

POSIX는 du를 지정한 파일에서 시작하는 파일 계층에 할당된 공간을 보고하는 도구로, df를 파일 시스템의 사용 가능한 공간을 보고하는 도구로 정의합니다.

이름이 지워진 파일은 디렉터리를 따라가도 닿을 수 없으니 du의 합계에서 빠집니다. 하지만 공간이 아직 해제되지 않았으니 df의 여유 공간은 늘지 않습니다. 이 차이는 “지웠지만 아직 참조가 남은 파일”을 의심할 단서가 됩니다. 다만 두 숫자가 어긋나는 이유가 이것 하나뿐이라고 단정할 수는 없습니다.

Linux에서 찾아보기

여기서부터는 POSIX가 아니라 Linux와 개별 도구의 기능입니다. /proc에 대한 내용은 Linux man-pages 6.19에 문서화된 내용을 기준으로 했습니다.

  • lsof +L1: lsof 매뉴얼 4.99.0은 +L1이 unlink된 열린 파일을 고른다고 설명합니다. 링크 수가 1보다 작은 열린 파일만 나열하는 방식입니다. 특정 프로세스로 좁히려면 선택 조건을 AND로 묶는 -a와 -p <PID>를 함께 씁니다.
  • /proc/<PID>/fd/: 프로세스의 열린 파일 디스크립터마다 하나씩, 디스크립터 번호를 이름으로 한 심볼릭 링크가 들어 있습니다.
  • /proc/<PID>/maps: 파일에 기반한 매핑에서 그 파일이 삭제되었다면 경로 뒤에 (deleted)가 붙습니다. 매뉴얼은 이 표시가 모호하다고도 적습니다.
  • /proc/<PID>/exe: 실행 중인 프로그램의 실행 파일이 unlink되면 이 링크에도 (deleted)가 붙습니다.

공간을 되찾으려면

그 파일을 가리키는 참조가 모두 사라져야 합니다. 열린 디스크립터는 모두 닫히고, 파일 기반 매핑은 모두 해제되어야 합니다. 링크 수는 이미 0이므로 마지막 참조가 사라지는 시점에 공간이 해제됩니다. 보통은 프로세스가 종료될 때 이 두 가지가 함께 일어납니다.

운영 영향을 줄이려면 다음 순서로 확인합니다.

  1. 파일을 잡고 있는 프로세스를 모두 찾습니다. 한 파일을 여러 프로세스가 참조할 수 있고, unlink() 설명은 디스크립터와 매핑을 따로 세므로 한쪽만 봐서는 부족할 수 있습니다. lsof +L1의 결과와 관련 프로세스의 /proc/<PID>/maps를 함께 확인합니다.
  2. 프로그램이 문서로 제공하는 로그 다시 열기나 graceful reload 기능이 있으면 그것을 먼저 씁니다. 방법은 프로그램마다 다르니 해당 문서를 따릅니다.
  3. 그런 기능이 없을 때에만 서비스별 영향을 검토한 뒤 재시작을 마지막 수단으로 고려합니다.
  4. 처리가 끝나면 df -k로 여유 공간이 실제로 늘었는지 확인합니다. 그대로라면 아직 참조하는 프로세스나 다른 하드 링크가 남아 있는지 다시 봅니다.

rm을 다시 실행해도 소용이 없습니다. 지울 이름이 이미 없기 때문입니다.

공간이 해제된 뒤에는 POSIX에 따라 그 파일의 일련 번호인 st_ino가 다시 쓰일 수 있습니다.

이 글은 POSIX 규칙과 Linux에서의 확인 방법만 다룹니다. Windows, 네트워크 파일 시스템, 스냅샷 같은 파일 시스템별 기능은 동작이 다를 수 있어 범위에서 제외했습니다.

블로그로