
아이노드(inode): 유닉스 파일의 메타데이터
리눅스에서 파일 이름은 껍데기에 불과하다. 진짜 주인은 주민등록번호(inode number)다. ls -i의 비밀.

리눅스에서 파일 이름은 껍데기에 불과하다. 진짜 주인은 주민등록번호(inode number)다. ls -i의 비밀.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

미로를 탈출하는 두 가지 방법. 넓게 퍼져나갈 것인가(BFS), 한 우물만 팔 것인가(DFS). 최단 경로는 누가 찾을까?

이름부터 빠릅니다. 피벗(Pivot)을 기준으로 나누고 또 나누는 분할 정복 알고리즘. 왜 최악엔 느린데도 가장 많이 쓰일까요?

매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

No space left on device. 이 에러 메시지를 처음 마주쳤을 때, df -h로 디스크 사용률을 확인하니 47%였다. 용량은 절반밖에 안 찼는데 왜 "디스크 꽉 참"인 거지? 구글링 시작. "disk full but space available linux"라고 쳤더니 스택오버플로우 첫 답변이 떴다. "Check your inodes. Run df -i."
inode? 처음 들어본다. 파일시스템을 배울 때 "파일은 데이터 블록에 저장된다"까지만 배웠지, inode가 뭔지는 몰랐다. df -i 쳐보니 IUsed가 100%. 뭔지도 모르는 inode가 고갈된 상태였다.
로그 파일이 많아지는 환경에서는 이런 상황이 실제로 발생한다. 로깅 라이브러리가 1KB씩 파일을 쪼개서 저장하는 설정이라면, 몇 달 방치 시 inode를 모두 소진할 수 있다. 그날 밤, inode를 찾아 헤매다 깨달았다. 파일 이름은 껍데기에 불과하다는 사실을. 리눅스에서 진짜 주인공은 inode 번호다.
처음엔 당연하게 생각했다. hello.txt라는 파일이 있으면, 리눅스가 "hello.txt"라는 이름으로 디스크에 데이터를 저장하겠지. 그런데 아니었다.
리눅스는 파일 이름을 거의 신경 쓰지 않는다. 대신 inode number라는 숫자로 파일을 관리한다. 파일명은 사람을 위한 별명일 뿐, 운영체제가 진짜로 관리하는 건 숫자다.
이게 무슨 말인지 확인해보자. ls -i 명령어를 치면 파일 앞에 숫자가 붙어 나온다:
$ ls -i
12345678 hello.txt
12345679 world.txt
12345680 README.md
저 숫자가 바로 inode number다. 리눅스 입장에선 hello.txt가 아니라 12345678번 파일이 중요하다. 마치 사람 이름보다 주민등록번호를 더 신뢰하는 것처럼.
그럼 파일 이름은 어디에 저장되는가? 디렉토리에 저장된다. 디렉토리는 파일명 : inode번호 매핑을 담고 있는 일종의 전화번호부다. 디렉토리 자체도 파일이다. 다만 내용이 "텍스트"가 아니라 "이름-숫자 쌍의 리스트"일 뿐.
처음 이걸 알았을 때 혼란스러웠다. 파일 이름과 파일 실체가 분리돼있다는 게 직관적이지 않았다. 근데 곰곰이 생각해보니 이 구조 덕분에 hard link가 가능하다는 걸 깨달았다.
inode를 이해하는 가장 쉬운 비유는 주민등록증이다.
주민등록번호(inode)는 변하지 않지만, 이름(파일명)은 개명할 수 있다. 주민등록증(inode)에는 집 주소(데이터 블록 위치)가 적혀있다. 주민등록번호 하나에 여러 별명(hard link)을 붙일 수 있다.
이제 inode에 뭐가 들어있는지 보자. stat 명령어를 치면 inode 내용이 쫙 나온다:
$ stat hello.txt
File: hello.txt
Size: 1024 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 12345678 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/ user) Gid: ( 1000/ user)
Access: 2025-03-15 10:30:00.000000000 +0900
Modify: 2025-03-15 10:25:00.000000000 +0900
Change: 2025-03-15 10:25:00.000000000 +0900
Birth: -
inode에 들어있는 정보:
rw-r--r--)파일 이름은 디렉토리가 가지고 있다. 디렉토리는 아래처럼 생긴 테이블이다:
파일명 inode번호
hello.txt 12345678
world.txt 12345679
README.md 12345680
hello.txt를 goodbye.txt로 mv 명령어로 바꾸면? 디렉토리 테이블만 업데이트된다. inode는 그대로다. 데이터 블록도 그대로다. 이름만 바뀐다. 리네임은 O(1) 연산이다. 파일을 복사하지 않기 때문이다.
이제 hard link가 이해된다. ln hello.txt alias.txt 하면:
$ ls -i
12345678 hello.txt
12345678 alias.txt
같은 inode 번호다. 디렉토리에 엔트리 하나만 추가됐을 뿐, 데이터는 복사되지 않았다. 두 이름이 같은 주민등록번호를 가리킨다. stat으로 보면 Links: 2가 된다. hard link는 "같은 inode를 가리키는 또 다른 이름"이다.
inode가 진짜 파워풀한 건 데이터 블록 포인터 때문이다. 파일 크기가 작으면 direct pointer만 쓰지만, 큰 파일은 indirect pointer를 쓴다.
ext4 파일시스템 기준으로:
이 구조 덕분에 작은 파일은 빠르게 접근하고, 큰 파일도 효율적으로 저장할 수 있다.
비유하면 이렇다:
큰 파일일수록 탐색 비용이 증가한다. 하지만 대부분의 파일은 작기 때문에 direct pointer로 충분하다. 이게 바로 최적화다.
앞서 얘기한 inode 고갈 상황을 재현해보면 df -i를 쳤을 때 이렇게 보인다:
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 1000000 1000000 0 100% /
inode가 100% 찼다. 용량은 47%밖에 안 찼는데 왜? 대량의 파일을 다루는 환경에서는 로그 디렉토리에 작은 파일이 수백만 개 쌓이는 일이 생긴다. 로깅 라이브러리가 1KB씩 파일을 쪼개서 저장하는 설정이라면, 몇 달간 방치되면서 inode를 모두 소진할 수 있다.
파일을 삭제하면 inode가 해제된다. 작은 파일 수백만 개를 삭제하고 나니 다시 정상화됐다:
$ find /var/log/app -type f -name "*.log" -delete
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 1000000 300000 700000 30% /
교훈:
df -h와 df -i 둘 다 봐야 한다.
inode 개수는 파일시스템 생성 시 결정된다. mkfs.ext4 할 때 -i 옵션으로 "bytes-per-inode" 비율을 정할 수 있다. 기본값은 16KB당 inode 하나. 작은 파일이 많을 걸 예상하면 8KB당 inode 하나로 늘릴 수 있다.
Windows의 NTFS는 inode 대신 MFT (Master File Table)를 쓴다. 개념은 비슷하다. 각 파일이 MFT 엔트리를 가지고, 거기에 메타데이터와 데이터 블록 포인터가 저장된다.
차이점:
NTFS는 inode 고갈 문제가 덜하다. MFT가 디스크 용량 허용 범위 내에서 계속 커질 수 있기 때문. 하지만 MFT 단편화가 발생하면 성능 저하가 온다.
Unix/Linux의 inode 방식은 예측 가능하고 단순하다. 파일시스템 생성 시 inode 개수가 정해지니까 "최대 몇 개 파일까지 만들 수 있는지" 명확하다. 운영하기 편하다.
파일시스템마다 inode 구조가 다릅니다.
대용량 스토리지 서버를 구축한다면 XFS가, 일반적인 OS 부팅용 디스크는 Ext4가 추천되는 이유 중 하나입니다.
가장 흔한 이슈입니다. 로그 파일을 rm 했는데 df -h 용량이 그대로인 경우.
rm은 디렉토리 엔트리만 지웁니다. inode의 Link Count를 1 줄입니다. 하지만 프로세스가 잡고 있으면 Reference Count가 남아있어서 inode와 데이터 블록은 삭제되지 않습니다.systemctl restart apache2)하거나, /proc/{pid}/fd를 찾아서 kill 해야 합니다.rm 하지 말고 echo "" > access.log 하세요. (파일 내용은 비워지고 inode는 유지됨)리눅스는 파일을 "읽기(Read)"만 해도 inode의 atime (Access Time)을 갱신합니다.
즉, 읽기 작업이 쓰기 작업(Metadata Write)을 유발합니다. 웹 서버처럼 읽기가 많은 환경에서는 이게 치명적인 성능 저하를 가져옵니다.
그래서 실제로는 /etc/fstab에 noatime 또는 relatime 옵션을 켜서 마운트합니다.
atime을 아예 갱신하지 않음. (가장 빠름)mtime(수정 시간)이 바뀌었을 때만 atime을 갱신함. (호환성과 성능의 타협점, 요즘 리눅스 기본값)ln -s target link로 만드는 심볼릭 링크(Soft Link)는 하드 링크와 다릅니다. 새 inode를 가집니다.
그럼 "원본 파일의 경로"는 어디에 저장될까요?
Fast Symlink: 경로가 60바이트보다 짧으면, 데이터 블록을 따로 쓰지 않고 inode 내부의 포인터 저장 공간에 경로 문자열을 직접 저장합니다. 디스크 I/O가 줄어서 엄청 빠릅니다.
Slow Symlink: 경로가 길면, 일반 파일처럼 데이터 블록을 할당해서 거기에 경로를 적습니다.
Q: inode가 꽉 차면 어떻게 늘리나요?
A: 불행히도 Ext4 같은 파일시스템은 포맷할 때(mkfs.ext4) inode 개수가 정해집니다. 이미 운영 중이라면 디스크를 포맷하고 다시 마운트해야 합니다. (끔찍하죠?) 그래서 처음 구축할 때 df -i를 고려해서 넉넉하게 잡는 게 중요합니다. (XFS는 동적 할당이라 좀 낫습니다.)
Q: 디렉토리도 inode를 먹나요? A: 네, 디렉토리도 파일의 일종입니다. 폴더 하나 만들 때마다 inode 하나가 소모됩니다. 그래서 무수히 많은 빈 폴더를 만드는 것도 inode 고갈의 원인이 됩니다.
Q: 하드 링크는 inode를 먹나요?
A: 아니요! 하드 링크는 기존 inode를 가리키는 "이름표"만 하나 더 붙이는 겁니다. inode는 그대로고 Link Count만 1 증가합니다.
리눅스에서는 ext4, xfs, ntfs 등 서로 다른 파일시스템을 동시에 마운트해서 쓸 수 있습니다. 어떻게 가능할까요?
커널 중간에 VFS (가상 파일 시스템) 계층이 있기 때문입니다.
cp a.txt b.txt를 실행하면, cp 프로그램은 ext4인지 뭔지 모릅니다. 그냥 VFS의 open(), read(), write() 시스템 콜을 호출할 뿐이죠.
VFS가 알아서 "아, 이건 ext4 드라이버한테 넘겨야지", "이건 네트워크 파일시스템(NFS)이네" 하고 라우팅해 줍니다.
"Everything is a file"이라는 유닉스 철학을 지탱하는 기술입니다.
inode를 알기 전엔 "파일 이름 = 파일"이라고 생각했다. 지금은 안다. 파일 이름은 디렉토리가 관리하는 별명일 뿐, 진짜 정체성은 inode 번호라는 걸.
이 지식이 실제로 유용했던 순간:
mv가 왜 빠른지 (inode는 안 바뀌고 디렉토리 엔트리만 수정)rm 했는데 용량이 안 줄어드는 이유 (다른 프로세스가 파일 디스크립터 열고 있으면 inode는 살아있음)파일시스템은 보이지 않는 곳에서 묵묵히 일한다. inode는 그 중심에 있는 설계다. ls -i와 stat 명령어 한 번씩 쳐보시길. 눈에 보이던 파일명 뒤에 숨겨진 숫자가 보이기 시작한다.