같은 물리 디스크라도 어떤 파일시스템으로 포맷하느냐에 따라 최대 파일 크기·복구 능력·성능이 완전히 달라진다. 그 출발점은 파일이 실제로 어떻게 표현되는지다.
inode(index node)는 하나의 파일 또는 디렉터리에 대한 모든 메타데이터를 담는 고정 크기 구조체다.
파일시스템을 만들 때(mkfs) inode 테이블의 개수가 정해지고, 파일 하나당 inode 하나가 소비된다.
항목
설명
파일 타입
일반 파일 / 디렉터리 / 심볼릭 링크 / 디바이스 등
권한 모드
rwxrwxrwx (12비트, SUID/SGID/Sticky 포함)
소유자 UID / GID
누구의 파일인가
링크 카운트
이 inode를 가리키는 하드링크 수
크기
바이트 단위 파일 크기
타임스탬프
atime(접근), mtime(수정), ctime(inode 변경)
데이터 블록 포인터
실제 내용이 저장된 블록 위치들
여기서 결정적으로 빠진 것이 하나 있다. 파일 이름은 inode에 없다. 이름은 부모 디렉터리가 들고 있다.
디렉터리는 "파일을 담는 폴더"가 아니라, "파일이름 → inode번호" 쌍의 목록을 내용으로 가진 특수한 파일이다. ls가 하는 일은 현재 디렉터리 파일을 열어 그 목록을 읽는 것뿐이다. 그래서 디렉터리에서 파일을 지우려면(unlink) 디렉터리에 대한 쓰기 권한이 필요하지, 파일 자체에 대한 권한은 필요 없다. Sticky bit(/tmp의 t)가 이 허점을 막는 장치다.
inode의 데이터 블록 포인터는 전통적으로 직접 블록 12개 + 단일 간접 + 이중 간접 + 삼중 간접 구조를 썼다(ext2/3). 작은 파일은 직접 포인터 12개로 충분하지만, 큰 파일은 "포인터를 담은 블록을 가리키는 포인터"를 단계적으로 쌓아 표현한다. ext4는 이 방식 대신 익스텐트(extent) — "시작 블록 + 연속 길이"로 범위를 한 번에 표현 — 를 도입해, 연속된 큰 파일을 훨씬 적은 메타데이터로 다룬다. 단편화가 적은 대용량 파일에서 성능이 크게 오른다.
⚠️ 함정: "디스크 용량은 남았는데 파일을 못 만든다"는 상황은 inode 고갈이다. df -h는 블록(용량) 사용률을, df -i는 inode 사용률을 따로 보여준다. 수많은 작은 파일(메일 큐, 세션 파일 등)이 쌓이면 용량은 멀쩡한데 inode가 100%가 되어 No space left on device가 뜬다. 시험에서 "용량 여유 있는데 파일 생성 실패 → 원인은?" 답이 inode 고갈이다.
# 직접 쳐보기 — inode 관찰ls -i file.txt # 파일의 inode 번호 출력stat file.txt # inode, 링크 수, 타임스탬프 전부df -i # 파일시스템별 inode 사용량ls -id /tmp # 디렉터리의 inode 번호
"inode"의 어원은 명확하지 않지만 Dennis Ritchie는 "index node"의 줄임이라고 회고했다. 1970년대 초기 유닉스부터 파일 메타데이터를 이름과 분리해 번호로 관리한 이 설계가, 지금까지도 리눅스 파일시스템의 근간으로 살아남았다.
inode의 링크 카운트가 1 증가한다. 둘은 완전히 동등한 "원본"이며, 하나를 지워도 inode는 카운트가 0이 될 때까지 살아남는다.
같은 파일시스템 안에서만 가능하고, 디렉터리에는 보통 못 건다.
심볼릭 링크(symbolic/soft link): 대상의 경로 문자열을 내용으로 가진 별도의 작은 파일(별도 inode).
원본을 지우면 링크는 깨진다(dangling). 파일시스템 경계를 넘을 수 있고 디렉터리도 가리킬 수 있다.
구분
하드링크
심볼릭 링크
inode
원본과 동일
별도 inode
링크 카운트
증가시킴
영향 없음
파일시스템 경계
못 넘음
넘을 수 있음
디렉터리 대상
불가(보통)
가능
원본 삭제 시
데이터 유지
깨진 링크
rm이 하는 일은 데이터를 지우는 게 아니라 디렉터리에서 이름을 떼고 inode의 링크 카운트를 1 줄이는 것이다. 카운트가 0이 되고 그 파일을 연 프로세스도 없을 때 비로소 블록이 회수된다. 그래서 실행 중인 프로세스가 연 로그 파일을 rm해도 디스크가 즉시 비지 않는다 — 프로세스가 닫을 때까지 inode가 살아 있기 때문이다.
ext2까지의 파일시스템은 갑작스러운 정전이나 크래시가 나면 파일시스템이 일관성을 잃을 수 있었다.
예: "새 파일의 블록을 할당했지만 그 블록을 inode에 연결하기 직전에 전원이 나가면", 블록은 점유됐는데 아무도 가리키지 않는 유령 블록이 생긴다.
부팅 시 이걸 잡으려고 fsck가 전체 디스크를 스캔했는데, 대용량 디스크에서는 수십 분~수 시간이 걸렸다.
저널링(journaling)은 실제 위치에 쓰기 전에 앞으로 무엇을 쓸지(트랜잭션)를 저널이라는 전용 영역에 먼저 기록한다.
작업이 정상 완료되면 저널 엔트리를 지우고, 도중에 크래시가 나면 부팅 시 저널만 재생(replay)해 일관성을 복원한다. 전체 스캔이 필요 없어 복구가 초 단위로 끝난다.
ext3/ext4의 저널링 모드 세 가지:
모드
저널에 기록하는 것
안전성
성능
journal
메타데이터 + 데이터 모두
가장 높음
가장 느림(두 번 씀)
ordered (기본)
메타데이터만, 단 데이터를 먼저 디스크에 쓴 뒤 메타데이터 커밋
높음
균형
writeback
메타데이터만, 데이터 순서 보장 안 함
낮음
가장 빠름
흔한 오해로 "저널링이 데이터를 보호한다"는 것이다. 기본 모드(ordered)는 메타데이터의 일관성을 보장할 뿐, 마지막 순간에 쓰던 사용자 데이터 자체의 무손실을 보장하지는 않는다. 데이터까지 저널에 이중 기록하는 journal 모드만 데이터를 보호한다. 시험에서 "ext3 기본 저널링이 보장하는 것은?" → 메타데이터 일관성이 정답이다.
ext4의 저널은 별도의 작은 영역에 존재하며 tune2fs -l로 "Journal inode" 항목을 확인할 수 있다. data=ordered는 마운트 옵션(/etc/fstab 또는 mount -o data=ordered)으로 조절한다. btrfs와 xfs는 ext와 다른 방식을 쓴다 — xfs는 메타데이터 저널링을, btrfs는 copy-on-write(CoW) 를 써서 저널 대신 항상 새 위치에 쓰고 원자적으로 포인터를 교체한다. 그래서 btrfs는 스냅샷이 자연스럽게 공짜로 나온다.
ext3는 ext2에 저널링만 얹어 디스크 포맷 호환을 유지한 채 나왔다(2001). 그래서 ext3 파티션을 ext2로 마운트하거나 그 반대가 가능했다. 이 "기존 자산을 깨지 않는 진화" 전략이 ext 계열이 리눅스 기본 파일시스템으로 오래 군림한 비결이다.
xfs는 줄일 수 없다(shrink 불가). 늘리기(xfs_growfs)만 가능하다. ext4는 resize2fs로 늘리고 줄이는 게(언마운트 후) 모두 된다. 이 비대칭성은 LVM과 함께 묻기 쉽다 — "xfs로 만든 LV를 줄이려면?" → 백업 후 재생성밖에 없다.
btrfs의 Copy-on-Write는 데이터를 덮어쓰지 않고 항상 새 블록에 쓴 뒤 메타데이터 포인터를 원자적으로 교체한다. 덕분에 스냅샷이 거의 공짜(변경된 블록만 추가 점유)이고, 모든 블록에 체크섬이 있어 silent corruption(조용한 데이터 손상)을 탐지·복구할 수 있다. 다만 데이터베이스처럼 같은 위치를 끊임없이 덮어쓰는 워크로드에서는 CoW 단편화로 느려질 수 있어 해당 파일에 nodatacow 속성을 주기도 한다.
# 직접 쳐보기 — 파일시스템 종류 확인df -T # 마운트된 파일시스템과 그 타입lsblk -f # 블록 장치별 FSTYPE, UUIDblkid # 장치별 UUID와 TYPEcat /proc/filesystems # 커널이 지원하는 파일시스템 목록tune2fs -l /dev/sda1 | head # ext 계열 슈퍼블록 정보(저널 포함)