본문 바로가기
빌드노트 검색

빌드노트

로그를 지워도 df 사용량이 줄지 않는 이유: 열린 파일 확인하기

로그를 삭제해도 열린 파일 디스크립터가 남아 있으면 공간은 유지된다. 격리 Linux에서 1MiB 파일의 삭제 전후와 마지막 FD 해제 후를 비교하고, lsof와 /proc로 보유 프로세스를 확인해 서비스의 로그 재열기 절차를 고르는 방법을 설명한다.

Linux에서 큰 로그를 지웠는데 du로 본 로그 폴더만 작아지고 df의 여유 공간은 그대로라면, 삭제된 파일을 아직 열고 있는 프로세스를 확인할 차례다. 이름이 사라진 파일도 프로세스가 붙잡고 있으면 공간을 차지한다. 보유 프로세스를 찾은 다음에는 그 서비스가 지원하는 로그 재열기 절차를 골라야 한다.

Linux의 unlink()는 디렉터리에서 파일의 이름을 제거한다. 마지막 이름을 지웠어도 파일을 연 프로세스가 남아 있으면 파일은 유지되고, 마지막 파일 디스크립터가 닫힐 때 해제된다. 파일 디스크립터는 프로세스가 열린 파일을 다루는 번호다. 삭제 뒤에도 같은 번호로 파일에 접근할 수 있는 이유다. Linux unlink(2) 문서 (새 탭에서 열림)

다만 df와 du의 차이만으로 이 원인을 확정할 수는 없다. df는 지정 경로가 속한 파일시스템 전체를 보고, du는 지정한 파일과 디렉터리를 따라가며 사용량을 합친다. 로그 폴더 하나의 합계가 파일시스템 전체 사용량과 같을 이유는 없다. 여기서 볼 것은 삭제 전후의 변화와, 그 파일을 가리키는 열린 참조다. GNU df 매뉴얼 (새 탭에서 열림), GNU du 매뉴얼 (새 탭에서 열림)

작은 파일로 비교한 삭제 전후

원인을 분리하려고 WSL2의 Ubuntu 24.04.4에서 별도 실습을 했다. 새 사용자·마운트·PID 네임스페이스에 크기가 8MiB로 제한된 tmpfs를 만들고, L 문자 1,048,576바이트를 실제로 쓴 파일 하나씩을 비교했다. tmpfs는 메모리를 사용하는 파일시스템이다. 이 실습은 운영 디스크를 채우거나 실제 로그를 삭제하지 않는다.

먼저 파일을 닫은 뒤 삭제했다. 다음에는 Python 프로세스가 파일을 연 채로 이름을 삭제하고, 관찰을 마친 후 파일을 닫았다. 각 단계에서 실습 디렉터리를 현재 경로로 삼아 다음 명령을 실행했다. GNU coreutils 9.4를 사용했으며, -B1로 단위를 바이트로 맞췄다.

df -B1 --output=used,avail .
du -s -B1 .

아래는 실제 출력에서 숫자를 옮긴 요약이다. df used는 사용량, df avail은 사용 가능한 공간이다.

상태                      df used   df avail   du
빈 실습 파일시스템              0    8388608    0
닫은 파일이 있을 때       1048576    7340032    1048576
닫은 파일을 삭제한 뒤           0    8388608    0
열린 파일이 있을 때       1048576    7340032    1048576
열린 채 이름을 삭제한 뒤  1048576    7340032    0
마지막 FD를 닫은 뒤             0    8388608    0

파일을 닫고 삭제한 경우에는 두 사용량이 함께 줄었다. 반면 열린 파일을 삭제하면 du에서는 1MiB가 빠져도 df의 사용량은 남았다. 삭제된 파일의 FD에서 처음 8바이트를 읽자 b'LLLLLLLL'도 반환됐다. 이름이 없어진 것과 파일이 해제된 것은 별개의 변화였다.

실제 서버에서 위 명령을 쓴다면 . 대신 삭제한 로그가 있던 부모 디렉터리를 지정한다. du가 권한 오류로 일부 경로를 읽지 못했다면 그 합계를 완전한 값으로 해석해서는 안 된다. 다른 파일의 쓰기가 함께 일어나는 서버에서는 이 실습처럼 정확히 1MiB씩 움직이지 않을 수도 있다.

보유 프로세스와 파일을 함께 확인한다

Linux 셸과 lsof, 그리고 대상 프로세스가 보이는 /proc가 필요하다. 다른 사용자로 실행되는 서비스를 조사하려면 허가된 관리자 권한이 필요할 수 있다. 컨테이너 안이라면 해당 프로세스와 파일시스템을 볼 수 있는 환경에서 확인한다.

lsof -nP +L1

+L1은 링크 수가 1보다 작은 열린 파일을 고른다. 삭제되어 이름이 남지 않은 파일을 찾는 조건이다. 실습의 lsof 4.95.0에서는 다음 행이 나왔다. 긴 실습 경로만 …로 줄였다. lsof 4.95.0 공식 매뉴얼 (새 탭에서 열림)

COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF NLINK NODE NAME
python3  21 root    3u   REG   0,89  1048576     0    3 …/held.log (deleted)

이 행에서는 PID 21인 Python 프로세스가 일반 파일(REG)을 FD 3으로 보유하고 있다. 3u의 숫자가 FD이고 u는 읽기·쓰기 모드다. NLINK가 0이고 이름 뒤에 (deleted)가 붙었다. SIZE/OFF의 1,048,576은 이 행에서 파일 크기이며, 모든 환경에서 곧바로 회수될 공간을 뜻하지는 않는다.

보유 프로세스를 찾으면 그 프로세스와 FD로 범위를 좁힌다. 아래의 21과 3은 실습 값이므로 실제 조사에서는 방금 확인한 값으로 바꾼다. -a는 PID 조건과 삭제 파일 조건을 함께 적용한다.

lsof -nP -a -p 21 +L1
readlink /proc/21/fd/3
stat -Lc 'links=%h size=%s blocks=%b inode=%i' /proc/21/fd/3

실습에서 readlink는 …/held.log (deleted)를 가리켰다. stat의 출력은 삭제 전 links=1 size=1048576 blocks=2048 inode=3, 삭제 후 links=0 size=1048576 blocks=2048 inode=3이었다. 이름에 연결된 수만 0으로 바뀌었고 같은 inode가 남아 있었다. inode는 파일시스템 안에서 파일을 식별하는 번호다. /proc/PID/fd/번호가 열린 파일을 가리키는 링크라는 점과 접근 권한의 제약은 proc_pid_fd(5) 문서 (새 탭에서 열림)에서 확인할 수 있다.

이번 환경의 lsof에는 /proc/sys/fs/binfmt_misc/status를 확인할 수 없어 출력이 불완전할 수 있다는 경고도 있었다. 대상 파일 행은 출력됐고 위의 FD 검사로 대조했지만, 빈 출력만으로 모든 프로세스에 삭제 파일이 없다고 판단할 수는 없다. 권한 오류나 조회 범위 문제를 먼저 해결해야 한다.

그 서비스의 재열기 절차로 연결을 끊는다

열린 FD가 확인되면 그 파일을 보유한 프로세스가 속한 서비스의 운영 절차를 확인한다. 같은 이름으로 빈 로그를 새로 만든 것만으로 기존 FD가 새 파일을 가리킨다고 판단할 수는 없다. 필요한 동작은 서비스가 새 로그를 열고 이전 파일의 참조를 닫는 것이다.

예를 들어 NGINX 공식 문서는 로그 회전 때 파일의 이름을 바꾼 후 master 프로세스에 USR1을 보내도록 설명한다. master가 로그를 다시 열고 worker에도 재열기를 요청해 이전 파일을 닫는 방식이다. 이는 NGINX의 절차이며, 다른 서비스에 적용할 공통 신호가 아니다. 실제 적용 전에는 해당 인스턴스의 master PID, 로그 경로와 권한, 배포 환경의 기존 로그 회전 설정을 확인한다. 이 글의 실습에서는 NGINX를 실행하거나 신호를 보내지 않았다. NGINX 로그 회전 공식 문서 (새 탭에서 열림)

선택은 다음 순서로 좁힐 수 있다.

  • 서비스에 문서화된 로그 재열기 기능이 있으면 기존 로그 회전 절차에 따라 사용한다. 재열기 실패가 기록되면 새 로그 경로와 쓰기 권한을 확인한다.
  • 재열기 기능이 없으면 정상 종료·시작으로 참조를 닫을 수 있는지 검토하고, 서비스 중단 영향을 반영한 재시작 절차를 잡는다.
  • 조치 후에는 해당 삭제 파일 행이 사라졌는지, 새 로그에 기록이 이어지는지, 같은 파일시스템의 여유 공간이 변하는지 확인한다. 다른 프로세스도 같은 파일을 열고 있다면 그 참조까지 닫혀야 한다.

실습에서는 Python의 마지막 FD를 닫자 df의 사용량이 1,048,576에서 0으로, 여유 공간이 7,340,032에서 8,388,608로 바뀌었다. 같은 프로세스의 삭제 파일 행도 없어졌다. 다만 FD 번호는 재사용된다. 앞선 실습 스크립트에서는 닫은 FD 3이 곧바로 진단 명령의 파이프에 재사용되었다. 나중에 같은 번호가 보이더라도 대상 경로와 파일 식별 정보를 다시 대조해야 한다.

확인한 범위는 Linux 6.18.40.1의 격리 tmpfs와 Python 3.12.3에서 열린 일반 파일 하나를 삭제하고 닫는 동작이다. 운영 서비스의 로그 재열기, ext4·XFS의 공간 회수 시점, Windows 호스트 파일시스템의 동작은 검증하지 않았다. 이 관찰은 삭제 뒤 공간이 남는 원인 하나를 구별하는 근거이며, df와 du의 모든 차이를 설명하지는 않는다.

← 전체 글 보기