스토리지와 마운트 · 디스크 사용량 추적 · 이론
df 와 du 는 왜 다른 답을 하나
한 줄 요약
df 는 파일시스템에게 묻고 du 는 디렉터리를 걸어다니며 센다. 그래서 이름이 없는 파일은 df 에는 잡히고 du 에는 안 잡힌다.
왜 이게 필요했나
로그가 디스크를 다 먹어서 큰 파일을 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 deletedfind / -xdev -size +500M -printf '%s %p\n' | sort -rn | head각 도구가 답하는 질문이 다르다.
| 도구 | 질문 | 놓치는 것 |
| --- | --- | --- |
| df | 파일시스템에 얼마나 남았나 | 어디가 먹었는지 |
| df -i | inode 는 얼마나 남았나 | 블록 사용량 |
| du | 이 경로 아래가 얼마나 크나 | 이름 없는 파일, 다른 파일시스템 |
| lsof +L1 | 지워졌는데 열려 있는 파일이 있나 | 이미 닫힌 것 |
du 에는 함정이 두 개 더 있다.
- 하드 링크는 한 번만 센다. 같은 inode 를 여러 이름이 가리키면 du 는 처음 만난 하나만 계산한다. 세대 백업 디렉터리의 크기를 잴 때 이 성질 때문에 값이 예상보다 작게 나온다.
- 희소 파일은 실제 점유만 센다.
--apparent-size를 주면 논리 크기가 나온다. 두 값의 차이가 곧 희소 영역이다.
삭제된 열린 파일 복구
프로세스가 아직 열고 있다면 /proc/PID/fd 아래에 그 파일로 가는 심링크가 살아 있다.
ls -l /proc/1234/fd | grep deletedcp /proc/1234/fd/7 /backup/recovered.log여기서 순서가 가장 중요하다. 프로세스를 재기동하는 순간 마지막 참조가 사라지고 블록이 반환되어 복구 기회가 완전히 없어진다. 용량이 급한 상황에서도 첫 행동은 재기동이 아니라 복사여야 한다.
공간만 급하게 회수해야 한다면 > /proc/1234/fd/7 로 파일을 비울 수 있다. 로그 파일이라면 이 방법이 프로세스를 살린 채 공간을 돌려준다.
현장에서 만나는 모습
inode 고갈. 세션 파일이나 메일 큐가 작은 파일 수백만 개를 만들면 블록은 남아도 inode 가 먼저 떨어진다. df -i 를 안 보면 원인을 못 찾는다. 그리고 ext4 는 포맷 후 inode 를 늘릴 수 없으므로, 근본 해결은 재포맷이거나 그 워크로드를 다른 파일시스템(XFS 는 동적 할당)으로 옮기는 것이다.
du 가 오래 걸려 장애 대응이 늦어진다. 파일이 수백만 개면 du 는 몇 분씩 걸린다. 급할 때는 du --max-depth=1 로 위에서부터 좁혀 들어가는 편이 빠르다.
다음 실습에서 할 것
작은 파일을 대량 생성해 inode 소비를 관찰하고, 희소 파일의 두 가지 크기를 비교하고, 삭제됐지만 열려 있는 파일을 직접 만들어 df 와 du 의 불일치를 재현한다.