Featured image of post [Bash Shell] 40. du, df - 디스크 사용량 확인

[Bash Shell] 40. du, df - 디스크 사용량 확인

du는 파일·디렉터리가 디스크 블록에서 실제로 차지하는 공간을, df는 마운트된 파일시스템 전체의 사용량과 여유 공간을 보여준다. -h와 --max-depth로 큰 항목을 찾는 법부터 sparse 파일의 크기 차이, df -i로 진단하는 inode 부족 문제까지 실전 예제로 다룬다.

du(disk usage)와 df(disk free)는 이름은 비슷하지만 측정 대상이 정반대다. du는 특정 파일·디렉터리 하나가 디스크에서 실제로 얼마나 차지하는지를 보여주고, df는 그 파일이 놓인 파일시스템 전체가 얼마나 차 있고 얼마나 남았는지를 보여준다. 이 대상 범위의 차이를 모르면 “df로는 여유 공간이 남았는데 왜 파일을 못 만드느냐” 같은 실무에서 자주 겪는 혼란을 풀 수 없다.

이 장을 읽기 전에

선행 챕터: 이 장은 39장: tar에서 여러 파일을 하나의 아카이브로 묶고 압축하는 법을 다룬 뒤 이어지며, Part 6(파일 시스템과 권한)의 마지막 챕터다. find(37장)로 조건에 맞는 파일을 찾고 gzip·tar(38–39장)로 압축·보관하는 법을 익혔다면, 이제 그 파일들이 실제로 디스크 공간을 얼마나 쓰고 있는지, 그리고 디스크 자체에 여유가 얼마나 남았는지 확인하는 도구로 이 Part를 마무리한다. cd(1장)로 디렉터리를 이동하고 ls(2장)로 목록을 보는 정도의 셸 탐색 지식이면 충분하다.

이 장의 깊이: 입문–중급 난이도다. 기본 사용법과 사람이 읽기 좋은 출력, 서브디렉터리별 요약, 파일시스템 전체 사용량 확인까지 실무에서 매일 쓰는 범위를 다룬다. 다루지 않는 것: 파티션을 나누거나 포맷하는 작업(fdisk, mkfs), LVM·RAID 같은 스토리지 가상화 계층, 파일시스템 내부 자료구조(inode 테이블의 물리적 배치 등)는 이 컬렉션의 범위를 벗어난다.

당신의 수준에 맞는 경로

수준읽을 부분핵심 목표
용량 문제가 급한 사람개요+정신 모델, 사용법·옵션, 예시의 “서브디렉터리별 요약”·“파일시스템 전체 확인”du -h --max-depth=1df -h로 무엇이 공간을 차지하는지, 어느 파티션이 꽉 찼는지 바로 찾아낸다
원인까지 진단하려는 사람주의사항·함정, 흔한 오개념 전체sparse 파일·바인드 마운트·inode 고갈처럼 du/df 수치가 어긋나 보이는 상황의 근본 원인을 설명하고 대응할 수 있다

개요 + 정신 모델

du트리를 순회하며 블록을 더하는 계산기에 가깝다. 지정한 파일·디렉터리에서 시작해 하위 트리를 재귀적으로 순회하면서, 각 파일이 디스크에 실제로 할당된 블록 수를 합산한다. 여기서 핵심은 “실제로 할당된 블록"이라는 표현이다 — 파일의 논리적 크기(바이트 수, ls -l이 보여주는 값)와 디스크에 물리적으로 할당된 블록 수는 항상 같지 않다. GNU du는 기본 단위로 1024바이트 블록을 쓰며(POSIXLY_CORRECT 환경변수가 설정되면 512바이트로 바뀐다), 파일 하나가 10바이트뿐이어도 파일시스템의 최소 할당 단위(예: 4KB 블록) 하나를 통째로 차지한다. 즉 du의 측정 대상은 명령줄에 준 경로 하나(와 그 하위 트리)로 범위가 좁다.

df는 반대로 커널에 마운트된 파일시스템 목록을 조회하는 질의다. 특정 파일이 아니라 그 파일이 속한 파일시스템(마운트 지점) 전체의 총 용량·사용량·여유 공간을 가져온다. 인자로 파일 경로를 주더라도 df는 그 파일 자체의 크기가 아니라 “이 경로가 속한 파일시스템"을 찾아내는 데만 그 경로를 쓴다. 그래서 df 어떤파일df 같은디렉터리의다른파일은 같은 마운트 지점 위에 있는 한 완전히 같은 결과를 낸다. 요약하면 du는 “이 서랍 하나가 얼마나 찼는가"를 재는 도구이고, df는 “이 서랍이 들어있는 창고 건물 전체가 얼마나 찼는가"를 재는 도구다 — 대상 범위 자체가 다르므로 두 수치가 일치하지 않는 것은 버그가 아니라 설계다.

사용법 · 옵션

1
2
du [옵션] [파일...]
df [옵션] [파일...]

du — 표시 제어

옵션설명
-h, --human-readable크기를 K/M/G 단위로 사람이 읽기 좋게 출력
-s, --summarize각 인자에 대한 총합만 출력(하위 항목 생략)
-a, --all디렉터리뿐 아니라 파일 단위로도 출력
-c, --total마지막 줄에 전체 합계를 추가로 출력

du — 범위·깊이

옵션설명
-d N, --max-depth=N명령줄 인자 기준 N단계 이하 디렉터리까지만 합계를 출력(--max-depth=0-s와 동일)
-x, --one-file-system다른 파일시스템에 있는 디렉터리는 건너뜀
-L, --dereference심볼릭 링크를 따라가 대상 파일 기준으로 계산
-l, --count-links하드링크된 파일도 매번 다시 집계(기본값은 같은 inode를 한 번만 센다)

du — 크기 측정

옵션설명
--apparent-size실제 디스크 사용량 대신 파일의 논리적(apparent) 크기를 출력
-B, --block-size=SIZE출력 단위를 SIZE(예: 1K, 1M)로 지정
--time디렉터리(와 하위 항목) 중 가장 최근 수정 시각도 함께 출력

df — 표시 제어

옵션설명
-h, --human-readable크기를 1024 단위 기준 K/M/G로 출력
-H-h와 같되 1000 단위(SI) 기준으로 출력
-T, --print-type파일시스템 종류(ext4, tmpfs 등) 열을 추가
-P, --portabilityPOSIX 표준 출력 형식 사용(스크립트 파싱에 안정적)

df — 필터링과 inode

옵션설명
-t TYPE, --type=TYPE지정한 타입의 파일시스템만 출력
-x TYPE, --exclude-type=TYPE지정한 타입의 파일시스템을 제외
--total마지막 줄에 전체 합계를 추가
-i, --inodes블록 사용량 대신 inode 개수 기준으로 출력

예시

특정 디렉터리 사용량 요약

1
2
3
4
5
# 현재 디렉터리 전체가 차지하는 용량만 사람이 읽기 좋게
du -sh .

# 홈 디렉터리 전체 용량
du -sh ~

서브디렉터리별 요약(가장 자주 쓰는 관용구)

1
2
3
4
5
# 1단계 하위 디렉터리별 용량을 사람이 읽기 좋게, 큰 순서로 정렬
du -h --max-depth=1 . | sort -h

# 위와 동일하지만 -d 1로 축약(GNU du의 짧은 옵션)
du -h -d 1 /var | sort -rh | head -10

파일 단위까지 훑어서 가장 큰 항목 찾기

1
2
# 디렉터리뿐 아니라 파일까지 포함해 상위 10개 큰 항목 찾기
du -ah . | sort -rh | head -10

파일시스템 전체 확인

1
2
3
4
5
6
7
8
# 마운트된 모든 파일시스템의 사용량·여유 공간
df -h

# 현재 디렉터리가 속한 파일시스템만
df -h .

# 파일시스템 종류까지 함께 확인(tmpfs, overlay 등 구분)
df -hT

inode 사용량 확인

1
2
3
4
5
# 블록이 아니라 inode 개수 기준 사용량
df -i

# 특정 마운트 지점의 inode 사용률만
df -i /var

여러 디렉터리 합계

1
2
# 여러 디렉터리 각각의 용량 + 마지막 줄에 전체 합계
du -sch /var/log /var/cache /var/tmp

주의사항·함정

du -h --max-depth=1(-d 1)은 가장 먼저 외워야 할 관용구다. 아무 옵션 없이 du -h 디렉터리를 실행하면 모든 하위 디렉터리를 재귀적으로 훑어 출력이 수백–수천 줄로 쏟아진다. --max-depth=1(짧은 형태는 -d 1)로 깊이를 한 단계로 제한하고 sort -h(사람이 읽기 좋은 단위를 그대로 정렬)와 조합하면, “어느 서브디렉터리가 공간을 많이 쓰는지"를 한눈에 좁혀 나갈 수 있다. 이 조합을 한 단계씩 더 깊은 디렉터리에 반복 적용하는 것이 실무에서 디스크 공간 부족 원인을 찾는 표준 절차다.

sparse(구멍난) 파일에서는 duls -l이 다른 크기를 보여준다. ls -l은 파일의 논리적 크기(파일 끝 오프셋)를 보여주지만, du는 실제로 디스크에 할당된 블록만 센다. truncatedd ... seek=처럼 파일 중간을 건너뛰어 쓰면, 건너뛴 구간은 실제 블록을 할당하지 않고도 논리적 크기만 커진 “구멍(hole)“이 생긴다.

1
2
3
4
5
# 1GB 크기의 sparse 파일 생성(실제로 1GB를 쓰지는 않음)
truncate -s 1G sparse.img

ls -lh sparse.img   # 1.0G — 논리적 크기(파일 끝 오프셋)
du -h sparse.img     # 수 KB 수준 — 실제로 할당된 블록만 집계

가상 머신 디스크 이미지나 데이터베이스 파일처럼 미리 크기를 예약해 두는 파일에서 이 차이가 자주 나타난다. du --apparent-size를 쓰면 duls -l과 같은 논리적 크기를 보여주도록 바꿀 수 있으니, 어느 쪽을 볼지는 “디스크가 실제로 얼마나 찼는지"와 “파일이 논리적으로 얼마나 큰지” 중 무엇이 궁금한지에 따라 옵션으로 선택하면 된다.

df는 마운트 지점 기준이라 바인드 마운트·tmpfs에서 헷갈리기 쉽다. 바인드 마운트(mount --bind)로 같은 디스크의 디렉터리를 다른 경로에 다시 연결하면, 두 경로에서 df를 실행해도 같은 밑바탕 파일시스템을 가리키므로 완전히 동일한 사용량·여유 공간이 나온다 — 경로가 다르다고 별개의 저장 공간이 아니다. /tmp나 컨테이너 안의 /dev/shm처럼 tmpfs로 마운트된 영역은 디스크가 아니라 메모리(와 필요 시 스왑)를 사용하므로, df -h가 보여주는 크기는 물리 디스크 여유 공간과 무관한 별도의 상한이며 재부팅하면 내용이 사라진다. df -hT로 파일시스템 종류 열을 함께 보면 지금 보고 있는 항목이 실제 디스크인지 tmpfs·overlay 같은 가상 계층인지 바로 구분할 수 있다.

df -i로 확인하는 inode 부족은 실무에서 실제로 자주 터지는 함정이다. ext4 같은 전통적인 리눅스 파일시스템은 포맷할 때 inode 개수를 고정으로 할당한다. 파일 하나(디렉터리·심볼릭 링크 포함)를 만들 때마다 inode를 하나 소비하므로, 아주 작은 파일을 대량으로 만드는 워크로드(메일 서버의 Maildir, PHP·웹 세션 파일, 캐시 디렉터리, 빌드 산출물의 수많은 소파일 등)에서는 디스크 블록은 절반도 안 썼는데 inode를 모두 소진해 버릴 수 있다. 이 상태에서 새 파일을 만들면 디스크 용량이 남아 있는데도 커널은 똑같이 “No space left on device”(ENOSPC) 에러를 낸다. df -h만 보면 여유가 있어 보여 원인을 못 찾고 헤매기 쉬운데, df -iIUse%(inode 사용률)를 확인하면 100%에 가까운 값이 바로 드러난다.

1
2
df -h /var    # Use% 40% — 블록 기준으로는 여유가 있어 보임
df -i /var    # IUse% 100% — 실제 원인은 inode 고갈

진단되면 대응은 두 갈래다. 당장은 오래된 소파일(로그, 캐시, 세션 파일)을 정리해 inode를 회수하고, 근본적으로는 파일시스템을 다시 만들 때 mkfs.ext4 -N <inode수>처럼 inode 개수를 넉넉히 지정하거나, 애초에 대량의 소파일을 만드는 워크로드라면 inode 상한이 없는 파일시스템(XFS는 기본적으로 동적 inode 할당을 쓴다)을 검토한다.

흔한 오개념

“삭제한 파일은 rm 실행 즉시 그 공간을 돌려준다"는 항상 맞지는 않다. 리눅스에서 rm은 디렉터리 목록에서 파일 이름과 inode의 연결(link)만 끊는다. 그 파일을 열어 둔 프로세스가 아직 실행 중이면, 커널은 그 프로세스가 파일을 닫을 때까지 inode와 데이터 블록을 계속 유지한다. 이 상태에서 du는 더 이상 그 파일을 볼 수 없다(디렉터리 트리 어디에도 이름이 없으므로 순회 대상에서 빠진다) — 그런데 df는 여전히 그 블록을 “사용 중"으로 집계한다. 로그 파일을 실수로 지웠는데 df상 디스크 사용량이 줄지 않는 흔한 현상이 바로 이것이다. lsof +L1로 링크 카운트가 0인(=삭제됐지만 열려 있는) 파일을 찾아 해당 프로세스를 재시작하거나, /proc/<PID>/fd/<번호>를 직접 비우면 공간을 돌려받을 수 있다.

du의 합계가 개별 파일 크기의 합과 정확히 같을 것이라는 가정도 틀리기 쉽다. 파일마다 파일시스템의 최소 할당 단위(블록) 단위로 반올림되어 집계되므로 실제 바이트 합보다 du 합계가 커지는 경우가 흔하고, 반대로 여러 경로에서 같은 inode를 가리키는 하드링크는 GNU du가 기본적으로 한 번만 계산하므로(모든 하드링크를 각각 세려면 -l/--count-links가 필요하다) 예상보다 합계가 작게 나올 수도 있다. “왜 개별 파일 크기를 손으로 더한 값과 du -sh가 다르지?“라는 질문의 답은 대부분 이 두 가지 중 하나다.

다음 장에서는

지금까지 로컬 파일 시스템을 다뤘다면, Part 7부터는 네트워크 너머의 원격 시스템과 통신하는 법을 다룬다. 다음은 41장: curl, wget — HTTP로 파일을 내려받고 API를 호출하는 두 도구를 다룬다.

평가 기준

  • dudf가 측정하는 대상 범위(특정 파일/디렉터리 vs 마운트된 파일시스템 전체)의 차이를 설명할 수 있다.
  • du -h --max-depth=1(또는 -d 1)로 서브디렉터리별 사용량을 확인하고 sort -h와 조합해 큰 항목을 찾아낼 수 있다.
  • sparse 파일에서 du가 보여주는 실제 디스크 사용량과 ls -l이 보여주는 논리적 크기가 다를 수 있는 이유를 설명할 수 있다.
  • df -i로 inode 사용량을 확인하고, 디스크 용량은 남았는데 “No space left on device” 에러가 나는 원인을 진단할 수 있다.
  • 바인드 마운트·tmpfs 환경에서 df 결과를 해석할 때 무엇을 주의해야 하는지 설명할 수 있다.

참고