LabHub
배우기 러닝패스 코스

Storage and Mounts

Why df and du Give Different Answers

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

df파일시스템에게 묻고 du디렉터리를 걸어다니며 센다. 그래서 이름이 없는 파일은 df 에는 잡히고 du 에는 안 잡힌다.

Concept map: 파일시스템에게 · 디렉터리를 걸어다니며 · 디렉터리에서 이름을 하나 떼어내는(unlink) 명령 · 이름이 하나도 남아 있지 않고

왜 이게 필요했나

로그가 디스크를 다 먹어서 큰 파일을 rm 으로 지웠는데 df 의 여유 공간이 1바이트도 늘지 않는다. 다들 한 번씩 겪고 한 번씩 당황한다.

여기서 "rm 이 안 먹혔다" 고 결론 내리면 그날 밤이 길어진다. 실제로 일어난 일은 이렇다. rm 은 파일을 지우는 명령이 아니라 디렉터리에서 이름을 하나 떼어내는(unlink) 명령이다. 데이터 블록이 반환되려면 두 조건이 동시에 성립해야 한다.

  1. 그 실체를 가리키는 이름이 하나도 남아 있지 않고
  2. 그 실체를 열어 둔 프로세스도 하나도 없을 때

로그를 쓰던 애플리케이션이 아직 그 파일을 열고 있으면 두 번째 조건이 깨진다. 이름은 사라졌지만 데이터는 살아 있고, du 는 경로를 걸어다니며 세기 때문에 이름 없는 그 파일을 영원히 찾지 못한다. df 와 du 가 어긋나는 순간이 바로 이 상황의 신호다.

어떻게 동작하나

용량 문제를 좁히는 순서는 이렇다.

df -h                                    # 블록 사용량
df -i                                    # inode 사용량 (이게 100% 일 수도 있다)
du -x -h --max-depth=1 / | sort -h       # 어느 디렉터리가 큰가 (-x: 다른 fs 로 안 넘어감)
lsof +L1                                 # 삭제됐지만 열려 있는 파일
ls -l /proc/<PID>/fd | grep deleted
find / -xdev -size +500M -printf '%s %p\n' | sort -rn | head

각 도구가 답하는 질문이 다르다.

도구 질문 놓치는 것
df 파일시스템에 얼마나 남았나 어디가 먹었는지
df -i inode 는 얼마나 남았나 블록 사용량
du 이 경로 아래가 얼마나 크나 이름 없는 파일, 다른 파일시스템
lsof +L1 지워졌는데 열려 있는 파일이 있나 이미 닫힌 것

df 는 슈퍼블록에 물어 100 기가바이트가 쓰였다고 답하고, du 는 디렉터리를 걸어다니며 이름이 있는 40 기가바이트만 센다. 차이인 60 기가바이트는 지워져 이름이 없지만 프로세스의 fd 7 이 붙잡고 있는 파일이고, 두 숫자의 차이가 곧 그 크기다

du 에는 함정이 두 개 더 있다.

삭제된 열린 파일 복구

프로세스가 아직 열고 있다면 /proc/PID/fd 아래에 그 파일로 가는 심링크가 살아 있다.

ls -l /proc/1234/fd | grep deleted
cp /proc/1234/fd/7 /backup/recovered.log

여기서 순서가 가장 중요하다. 프로세스를 재기동하는 순간 마지막 참조가 사라지고 블록이 반환되어 복구 기회가 완전히 없어진다. 용량이 급한 상황에서도 첫 행동은 재기동이 아니라 복사여야 한다.

공간만 급하게 회수해야 한다면 > /proc/1234/fd/7 로 파일을 비울 수 있다. 로그 파일이라면 이 방법이 프로세스를 살린 채 공간을 돌려준다.

무엇이 얼마나 차지하는지 빠르게 좁히기

용량 문제는 시간과의 싸움이다. 전체를 훑는 대신 위에서부터 반씩 잘라 내려가면 대개 서너 번 만에 범인이 나온다.

du -xh --max-depth=1 / 2>/dev/null | sort -rh | head

-x다른 파일 시스템으로 넘어가지 않게 한다. 이것을 빼면 /proc, /sys, 네트워크 마운트까지 세느라 오래 걸리고 숫자도 틀린다. --max-depth=1 로 한 층씩 내려가면서 가장 큰 디렉터리로 cd 하는 것을 반복한다.

큰 파일 몇 개인지, 작은 파일 수백만 개인지 먼저 가른다. 처방이 다르다.

find /var -xdev -type f -size +100M -printf '%s\t%p\n' | sort -rn | head
find /var -xdev -type f | wc -l

앞의 것이 답을 주면 그 파일을 지우거나 옮기면 끝난다. 뒤의 숫자가 수백만이면 inode 와 디렉터리 탐색이 문제이므로, 지우는 것 자체가 오래 걸린다.

오래된 것부터 지운다. rm 에 인자를 너무 많이 넘기면 Argument list too long 이 나므로 -deletexargs 를 쓴다.

find /var/log/app -xdev -type f -name '*.log' -mtime +14 -delete
find /var/cache/thumbs -xdev -type f -mtime +30 -print0 | xargs -0 -r rm -f

지우기 전에 무엇이 쓰고 있는지 본다. 어떤 프로세스가 계속 쓰고 있는 파일을 지우면 용량은 안 돌아오고 그 프로세스만 이상해진다(앞 모듈의 "지웠지만 열려 있는 파일").

lsof -nP +D /var/log/app 2>/dev/null | head

같은 일이 반복되지 않게 만든다. 한 번 치우고 끝내면 반드시 다시 찬다. 로그는 logrotate 에 맡기고(copytruncate 는 열린 fd 를 다루는 안전장치다), 컨테이너 호스트라면 docker system df 로 이미지·볼륨·빌드 캐시를 따로 보고 --filter until= 로 기간을 정해 정리한다. 경보는 90%가 아니라 증가 속도로 거는 편이 낫다. 하루에 5%씩 차고 있다면 이틀 뒤가 언제인지 알 수 있고, 그때는 새벽에 깨지 않아도 된다.

현장에서 만나는 모습

inode 고갈. 세션 파일이나 메일 큐가 작은 파일 수백만 개를 만들면 블록은 남아도 inode 가 먼저 떨어진다. df -i 를 안 보면 원인을 못 찾는다. 그리고 ext4 는 포맷 후 inode 를 늘릴 수 없으므로, 근본 해결은 재포맷이거나 그 워크로드를 다른 파일시스템(XFS 는 동적 할당)으로 옮기는 것이다.

du 가 오래 걸려 장애 대응이 늦어진다. 파일이 수백만 개면 du 는 몇 분씩 걸린다. 급할 때는 du --max-depth=1 로 위에서부터 좁혀 들어가는 편이 빠르다.

다음 실습에서 할 것

작은 파일을 대량 생성해 inode 소비를 관찰하고, 희소 파일의 두 가지 크기를 비교하고, 삭제됐지만 열려 있는 파일을 직접 만들어 df 와 du 의 불일치를 재현한다.