
파일 시스템: 데이터를 조직하는 방법
하드디스크는 그저 0과 1이 적힌 거대한 운동장입니다. 여기에 '파일'과 '폴더'라는 개념을 입히는 마법.

하드디스크는 그저 0과 1이 적힌 거대한 운동장입니다. 여기에 '파일'과 '폴더'라는 개념을 입히는 마법.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

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

프론트엔드 개발자가 알아야 할 4가지 저장소의 차이점과 보안 이슈(XSS, CSRF), 그리고 언제 무엇을 써야 하는지에 대한 명확한 기준.

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

처음 파일 시스템이라는 개념을 접했을 때, 나는 당연한 것을 너무 당연하게 받아들이고 있었다는 걸 깨달았다. "파일을 저장한다"는 행위가 얼마나 복잡한 추상화의 결과물인지 이해하지 못했던 것이다.
만약 파일 시스템이 없다면 어떻게 될까? 우리는 데이터를 저장할 때 이렇게 해야 한다. "하드디스크의 섹터 10,234,512번부터 500바이트를 읽어줘." 파일 이름도 없고, 폴더도 없고, 그냥 물리적 주소만 있다. 끔찍하다. 이게 바로 Raw Disk의 현실이다.
내가 파일 시스템을 진짜 이해하게 된 건 도커를 공부하면서였다. OverlayFS라는 개념을 접했는데, "파일 시스템 위에 또 다른 파일 시스템을 얹는다"는 설명을 보고 멘붕이 왔다. 그때 깨달았다. 결국 파일 시스템은 그저 데이터 접근을 위한 인터페이스이고, 여러 계층으로 쌓을 수 있는 추상화 레이어라는 걸.
파일 시스템을 공부하면서 가장 헷갈렸던 건 세 가지였다.
첫째, 파일 시스템이 OS 일부인가, 아니면 디스크 일부인가? 답은 둘 다였다. 디스크에는 파일 시스템의 "포맷"(메타데이터 구조)이 물리적으로 기록되어 있고, OS는 그걸 읽고 해석하는 "드라이버"를 갖고 있다. NTFS로 포맷된 디스크를 리눅스에 꽂으면 리눅스 커널의 NTFS 드라이버가 그걸 읽는다. 디스크는 데이터 저장소, OS는 통역사인 셈이다.
둘째, 블록(Block)과 섹터(Sector)의 차이가 뭔가? 섹터는 하드웨어 단위(보통 512바이트), 블록은 파일 시스템 단위(보통 4KB)다. 파일 시스템은 섹터를 직접 다루지 않고 여러 섹터를 묶어서 블록으로 관리한다. 효율성 때문이다. 1바이트짜리 파일을 저장해도 최소 1블록(4KB)을 차지한다. 이게 내부 단편화(Internal Fragmentation)의 원인이다.
셋째, inode가 대체 뭔가? 처음엔 "파일의 메타데이터"라는 설명이 너무 추상적이었다. 나는 이렇게 이해했다. inode는 도서관의 카탈로그 카드다. 책 제목(파일명)은 디렉토리에 있고, 카탈로그 카드에는 "몇 번 서가에 있는지"(블록 주소), "책이 몇 페이지인지"(파일 크기), "누가 빌릴 수 있는지"(권한)가 적혀 있다. 실제 책 내용은 서가(블록)에 있다.
파일 시스템을 제대로 이해하려면 세 가지 개념을 확실히 잡아야 한다.
디스크는 블록이라는 고정 크기 단위로 나뉜다. ext4에서는 보통 4KB다. 파일 내용은 이 블록들에 저장된다. 작은 파일은 한 블록에, 큰 파일은 여러 블록에 걸쳐 저장된다.
문제는 "어떤 블록들을 사용할 것인가?"다. 세 가지 방식이 있다.
연속 할당(Contiguous Allocation): 파일을 연속된 블록에 저장. 빠르지만 외부 단편화가 심하다. 옛날 방식이다.
연결 할당(Linked Allocation): 각 블록이 다음 블록의 주소를 가리킨다. FAT32가 이 방식이다. 단편화는 없지만 랜덤 접근이 느리다.
인덱스 할당(Indexed Allocation): inode에 블록 주소 목록을 저장. ext4, NTFS가 이 방식이다. 빠르고 유연하다. 요즘 대부분 이걸 쓴다.
inode는 파일의 모든 정보를 담고 있다. 파일 크기, 권한, 소유자, 타임스탬프, 그리고 가장 중요한 데이터 블록 주소 목록.
중요한 건, inode는 파일명을 모른다. 파일명은 디렉토리가 관리한다. 이게 하드링크의 원리다. 여러 파일명이 같은 inode를 가리킬 수 있다.
리눅스에서 ls -i 명령으로 inode 번호를 볼 수 있다.
$ ls -i myfile.txt
1234567 myfile.txt
1234567이 inode 번호다. 이 숫자로 파일의 실제 메타데이터를 찾는다.
디렉토리는 사실 특별한 파일이다. 내용은 "파일명 → inode 번호" 매핑 테이블이다.
. -> inode 2
.. -> inode 1
myfile.txt -> inode 1234567
subdir -> inode 1234568
파일을 열 때 OS는 이렇게 동작한다.
/home/user/myfile.txt)/)의 inode를 읽는다home의 inode 번호를 찾는다home 디렉토리를 읽어서 user의 inode를 찾는다user 디렉토리를 읽어서 myfile.txt의 inode를 찾는다이 과정이 너무 느려서 OS는 dentry cache(directory entry cache)를 사용한다.
파일을 저장하려면 빈 블록을 찾아야 한다. 파일 시스템은 빈 공간을 어떻게 추적할까? 두 가지 방식이 있다.
비트맵(Bitmap): 각 블록당 1비트. 0이면 빈 블록, 1이면 사용 중. 간단하고 빠르다. ext4가 이걸 쓴다.
블록: 0 1 2 3 4 5 6 7
비트: 1 1 0 1 0 0 0 1
↑ ↑ ↑ ↑ ↑
사용 사용 빈 사용 빈 빈 빈 사용
연결 리스트(Linked List): 빈 블록들을 연결 리스트로 관리. 첫 번째 빈 블록에 다음 빈 블록 주소를 저장. 간단하지만 느리다. 옛날 방식이다.
파일을 생성하고 삭제하다 보면 빈 공간이 여기저기 조각나서 흩어진다. 100MB 파일을 저장하려는데 빈 공간이 총 200MB지만 10MB씩 20조각으로 흩어져 있으면 저장이 안 된다. 이게 외부 단편화다.
옛날 윈도우는 이 때문에 주기적으로 "디스크 조각 모음"을 했다. 파일들을 몰아서 재배치하는 작업이다. 요즘 SSD는 이게 필요 없다. 랜덤 접근이 빠르기 때문이다.
파일을 쓰는 도중 전원이 나가면 어떻게 될까? 데이터 블록은 썼는데 inode는 업데이트 안 됐다면? 파일 시스템이 깨진다.
저널링은 이를 방지한다. 데이터를 쓰기 전에 "이제 이걸 할 거야"라고 로그(저널)에 먼저 적는다. 크래시 후 재부팅하면 저널을 읽어서 미완료 작업을 마저 하거나 롤백한다.
ext4, NTFS, APFS 모두 저널링을 지원한다. 데이터베이스의 WAL(Write-Ahead Logging)과 같은 원리다.
# ext4 저널 상태 확인
$ sudo dumpe2fs /dev/sda1 | grep "Filesystem features"
Filesystem features: has_journal ext_attr resize_inode
내가 자주 마주치는 파일 시스템들을 정리해본다.
1996년에 나온 파일 시스템. 엄청 오래됐지만 모든 OS가 지원한다. USB 메모리는 거의 다 FAT32다. 단점은 명확하다.
그래도 USB나 SD 카드는 여전히 FAT32를 쓴다. 윈도우, 맥, 리눅스, TV, 게임기 모두 읽을 수 있어야 하니까.
뉴 테크놀로지 파일 시스템(New Technology File System). 윈도우 NT부터 쓰기 시작했다.
문제는 맥과 리눅스에서 쓰기가 불안정하다. 리눅스의 ntfs-3g 드라이버는 느리다. 크로스 플랫폼 외장 하드는 exFAT를 쓰는 게 낫다.
Extended File System 4. 리눅스의 사실상 표준이다. 서버에서 가장 많이 본다.
내가 리눅스 서버를 셋업할 때는 무조건 ext4를 쓴다. 검증된 안정성이 최고다.
# ext4로 포맷
$ sudo mkfs.ext4 /dev/sdb1
# 파일 시스템 정보 확인
$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda1 ext4 20G 12G 7.2G 63% /
Apple File System. 2017년에 나왔다. SSD에 최적화되어 있다.
맥 쓰면서 부팅이 빨라진 게 APFS 덕분이다. 메타데이터 접근이 HFS+보다 훨씬 빠르다.
XFS: 대용량 파일에 최적화. 영상 편집 서버나 빅데이터 플랫폼에서 쓴다. Red Hat이 밀어준다.
Btrfs: Copy-on-Write, 스냅샷, RAID 기능 내장. "차세대 리눅스 파일 시스템"이라고 불렸지만 아직 안정성 논란이 있다. SUSE가 기본으로 쓴다.
나는 아직 프로덕션에서 Btrfs를 안 써봤다. ext4가 너무 안정적이라서.
이론만 알고 실제로 못 다루면 의미 없다. 내가 자주 쓰는 명령어들을 정리해본다.
# 전체 파일 시스템 용량 확인
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 20G 12G 7.2G 63% /
/dev/sdb1 100G 45G 55G 45% /data
# 특정 디렉토리 크기 확인
$ du -sh /var/log
2.3G /var/log
# 현재 디렉토리 하위 디렉토리별 크기
$ du -h --max-depth=1 | sort -hr
12G .
5.2G ./node_modules
3.8G ./build
2.1G ./data
# 마운트된 파일 시스템 확인
$ mount | grep "^/dev"
/dev/sda1 on / type ext4 (rw,relatime)
/dev/sdb1 on /data type ext4 (rw,relatime)
# 새 디스크 마운트
$ sudo mount /dev/sdc1 /mnt/backup
# 부팅 시 자동 마운트 설정 (fstab 편집)
$ sudo vim /etc/fstab
# /dev/sdc1 /mnt/backup ext4 defaults 0 2
# ext4로 포맷
$ sudo mkfs.ext4 /dev/sdb1
# XFS로 포맷
$ sudo mkfs.xfs /dev/sdb1
# 파일 시스템 체크 및 복구
$ sudo fsck /dev/sdb1
# inode 사용량 확인 (파일 개수가 많을 때 중요)
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 1310720 234567 1076153 18% /
도커를 공부하면서 파일 시스템의 진짜 파워를 느꼈다. 도커는 OverlayFS를 사용한다.
OverlayFS는 여러 파일 시스템을 겹쳐서(overlay) 하나처럼 보이게 한다. 아래층(lower)은 읽기 전용, 위층(upper)은 읽기/쓰기 가능. 파일을 수정하면 아래층은 그대로 두고 위층에만 변경사항을 저장한다. 이게 Copy-on-Write다.
도커 이미지가 왜 레이어로 쌓이는지, 왜 베이스 이미지를 공유할 수 있는지 이해하게 됐다. 결국 파일 시스템의 추상화 덕분이다.
# 도커 이미지 레이어 확인
$ docker image inspect nginx:latest | jq '.[0].RootFS.Layers'
[
"sha256:abc123...",
"sha256:def456...",
"sha256:ghi789..."
]
# 실제 OverlayFS 마운트 확인
$ mount | grep overlay
overlay on /var/lib/docker/overlay2/abc123/merged type overlay
이걸 이해하고 나니 컨테이너가 왜 빠른지, 왜 디스크를 아끼는지 명확히 와닿았다.
파일 시스템을 공부하면서 깨달은 건, 모든 추상화는 trade-off라는 점이다.
FAT32는 단순해서 호환성이 좋지만 기능이 부족하다. NTFS는 기능이 많지만 다른 OS 지원이 약하다. ext4는 안정적이지만 최신 기능(스냅샷, CoW)이 없다. Btrfs는 기능이 많지만 아직 성숙도가 낮다.
정답은 없다. 용도에 맞게 선택하는 게 핵심이다.
파일 시스템은 OS의 가장 기초적인 컴포넌트지만, 가장 복잡하고 중요한 부분이다. Raw Disk라는 거대한 숫자 덩어리를 "파일"이라는 친숙한 개념으로 변환해주는 마법. 이 마법 덕분에 우리는 섹터 번호를 외우지 않아도 된다.
나는 이제 df -h를 칠 때마다 그 숫자 뒤에 있는 블록, inode, 저널을 떠올린다. 그리고 감사한다. 파일 시스템 없는 세상에 살지 않아도 되는 것에.