05 — Filesystems
한 줄 답: 파일시스템은 바이트 덩어리에 이름·메타데이터·계층 구조를 부여하는 규약이다. S3는 그래서 파일시스템이 아니다 — key-value 객체 저장소다.
Why — 왜 파일시스템이 필요한가
디스크는 번호가 매겨진 거대한 바이트 배열이다. 1TB SSD는 약 2,000,000,000개 섹터(512바이트씩) 또는 256,000,000개 페이지(4KB씩)다.
이 위에 다음이 필요하다:
| 필요 | 해결 |
|---|---|
| 어떤 영역이 어떤 파일에 속하는가 | inode + 블록 매핑 |
| 이름으로 파일을 찾기 | 디렉토리 (= 이름→inode 색인) |
| 권한·시간·소유자 같은 메타데이터 | inode의 stat 필드 |
| 동시에 두 프로세스가 써도 깨지지 않게 | 락(lock), 저널링 |
| 전원이 갑자기 나가도 복구 가능하게 | 저널링, COW |
| 큰 파일을 빠르게 탐색하기 | 익스텐트(extent), B-tree |
| 수억 개 파일에서도 빠르게 찾기 | 해시 테이블, B-tree 디렉토리 |
→ 이 모든 것을 묶은 on-disk format + 커널 코드가 파일시스템이다.
How — 어떻게 동작하나
1) 모든 파일시스템의 공통 골격
이 골격은 ext4/APFS/NTFS/XFS 모두 거의 동일하다. 차이는 (a) inode 구조의 디테일, (b) 블록 할당 알고리즘, (c) 저널링 방식, (d) 부가 기능이다.
2) inode — 파일의 진짜 신원
inode는 고정 크기 구조체다 (ext4: 256바이트). 그 안에 들어가는 정보:
struct ext4_inode {
uint16_t i_mode; // 파일 타입 + 권한 (예: -rw-r--r--)
uint16_t i_uid; // 소유자 ID
uint32_t i_size; // 파일 크기 (바이트)
uint32_t i_atime; // access time
uint32_t i_ctime; // change time (메타데이터 변경)
uint32_t i_mtime; // modification time (내용 변경)
uint16_t i_links_count; // 하드링크 개수
uint32_t i_blocks; // 사용한 디스크 블록 수
uint32_t i_block[15]; // 데이터 블록 포인터
// 직접 12개 + 간접 1 + 이중간접 1 + 삼중간접 1
// ... 확장 속성, ACL, 등
};핵심: inode는 이름을 모른다. 이름은 디렉토리에 있다. inode는 데이터의 위치와 메타데이터만 안다.
3) 디렉토리 — 사실은 특별한 파일
디렉토리도 파일이다. 다만 그 내용이 [이름, inode번호] 쌍의 배열이다.
디렉토리 /home/raw 의 내용:
[".", inode=88] ← 자기 자신
["..", inode=12] ← 부모
["photo.jpg", inode=1234567]
["resume.pdf",inode=1234568]
["videos", inode=1234569] ← 하위 디렉토리도 그냥 inode이 구조 덕분에:
- 하드링크가 가능 (같은 inode를 가리키는 여러 entry)
- 이름 변경은 디렉토리 entry만 바꾸면 됨 — 데이터 이동 없음
- mv within same filesystem = 디렉토리 entry 이동 (atomic)
- mv across filesystems = 복사 + 삭제 (atomic 아님)
4) 저널링 — 전원 차단 복구
전통적 파일시스템(ext2 등)은 쓰기 도중 전원이 나가면 반쯤 쓴 상태가 남았다. 부팅 시 fsck가 몇 시간 동안 디스크 전체를 검사해야 했다.
저널링 (ext3/4, NTFS, XFS):
- 변경할 메타데이터를 먼저 저널 영역에 기록
- 디스크에 sync
- 실제 위치에 메타데이터 기록
- 저널에서 해당 항목 제거
→ 전원 차단 시 부팅 후 저널만 재생하면 일관성 복구. fsck가 수 초에 끝남.
모드:
journal: 데이터까지 저널 → 가장 안전, 가장 느림ordered: 데이터 먼저 → 메타데이터 저널 (ext4 기본)writeback: 메타데이터만 저널, 데이터 순서 보장 X → 빠름, 위험
5) Copy-on-Write (COW) — APFS, ZFS, Btrfs
수정 시 원본 블록을 덮어쓰지 않고 새 블록에 쓴 뒤 메타데이터만 갱신.
- 스냅샷이 공짜 (데이터 블록 공유)
- 부분 쓰기 실패해도 원본 보존
- 단점: 단편화(fragmentation) — 같은 파일의 블록이 디스크 곳곳에 흩어짐
→ 이 차이가 macOS의 cp가 순간적으로 끝나는 이유 (clone, APFS의 clonefile()).
What — 주요 파일시스템 비교
데스크탑·서버 파일시스템
| 파일시스템 | OS | 등장 | 최대 파일 | 최대 볼륨 | 특징 |
|---|---|---|---|---|---|
| ext4 | Linux | 2008 | 16 TB | 1 EB | 저널링, extent, 안정성 |
| XFS | Linux | 1994 (IRIX), 2002 (Linux) | 8 EB | 8 EB | 대용량, 병렬 I/O |
| Btrfs | Linux | 2009 | 16 EB | 16 EB | COW, 스냅샷, 압축 |
| ZFS | Solaris/FreeBSD/Linux | 2005 | 16 EB | 256 ZB | COW, 풀, 체크섬, 중복제거 |
| APFS | macOS/iOS | 2017 | 8 EB | 8 EB | COW, 스냅샷, 암호화 통합, clone |
| HFS+ | macOS (legacy) | 1998 | 8 EB | 8 EB | APFS 이전 macOS 기본 |
| NTFS | Windows | 1993 | 16 EB | 16 EB | 저널링, ACL, 압축, EFS 암호화 |
| FAT32 | 모든 OS | 1996 | 4 GB | 2 TB | 호환성 ↑, 큰 파일 X (USB·SD에 흔함) |
| exFAT | 모든 OS | 2006 | 16 EB | 128 PB | FAT 후속, SD/SSD 카드 표준 |
모바일·임베디드
| 파일시스템 | 용도 |
|---|---|
| F2FS | Android (NAND flash 최적화) |
| YAFFS2 | 임베디드 NAND |
| JFFS2 | 임베디드 NOR flash |
| SquashFS | 읽기 전용 압축 (Live USB, 컨테이너 이미지) |
| OverlayFS | Docker 레이어 (read-only base + read-write upper) |
분산·네트워크
| 시스템 | 용도 |
|---|---|
| NFS | UNIX 표준 네트워크 파일공유 (RFC 7530, NFSv4) |
| SMB / CIFS | Windows 네트워크 파일공유 (Samba) |
| 9P | Plan 9, WSL2의 \wsl$ 공유 |
| Lustre, GPFS, Ceph | HPC 클러스터, PB급 |
| HDFS | Hadoop 분산 파일시스템 |
”파일시스템이 아닌” 저장소 — 객체 스토리지
| 시스템 | 인터페이스 | 특징 |
|---|---|---|
| AWS S3 | HTTP REST API | flat key-value, 디렉토리 없음 |
| Google Cloud Storage | HTTP REST | S3와 유사 |
| Azure Blob Storage | HTTP REST | container/blob 구조 |
| MinIO | S3 호환 API | 자체 호스팅 |
→ 이게 이 챕터의 핵심: S3는 파일시스템이 아니다. 다음 절에서 자세히.
What-if — S3는 왜 파일시스템이 아닌가
1) S3의 데이터 모델
버킷: my-bucket
키: folder1/folder2/photo.jpg
객체: [13MB의 바이트 + 메타데이터(Content-Type, ETag, ...)]→ S3는 flat key-value store다. folder1/folder2/는 키의 일부일 뿐 디렉토리가 아니다.
2) 파일시스템 vs S3 — 결정적 차이
| 연산 | 파일시스템 (POSIX) | S3 |
|---|---|---|
| 디렉토리 생성 | mkdir — atomic O(1) | 존재하지 않음 (prefix는 키의 일부) |
ls dir/ | 디렉토리 inode 한 번 읽기 — O(directory size) | ListObjectsV2(prefix=dir/) API 호출 — eventual consistency 가능 |
| 파일 이동 | rename() — atomic, 데이터 이동 없음 | 존재하지 않음 — Copy + Delete 두 API (atomic 아님) |
| 부분 쓰기 | pwrite(offset, ...) — 임의 위치 수정 | 불가능 — 객체 전체를 다시 PUT해야 함 |
| 추가 쓰기 | O_APPEND 플래그 | 불가능 — Multipart Upload로만 (조립 후 새 객체) |
| 파일 잠금 | flock, fcntl | 없음 (애플리케이션 레벨 락 필요) |
| 정확한 크기 일치성 | write() 후 즉시 다른 프로세스도 봄 | read-after-write consistency (S3는 2020년부터 강한 일관성, 그 전엔 약함) |
| 메타데이터 수정 | chmod, touch 등 | 객체 전체 복사해서 메타데이터 교체 |
| 비용 | 디스크 운영비만 | API 호출당 과금 — ls도 돈 |
3) 자주 발생하는 사고
a) “디렉토리 비어 있는데 안 지워져요”
S3에 mybucket/temp/라는 0-byte 객체가 있을 수 있다 (콘솔에서 “폴더 만들기”하면 생성됨). 다른 객체가 없으면 디렉토리가 사라지는 게 아니라 그 0-byte 객체가 남아 있는 것이다.
b) “rsync로 S3 마운트했는데 너무 느려요”
s3fs-fuse로 S3를 마운트하면 rename 한 번이 GB 다운로드 + GB 업로드가 된다. 절대 일반 파일시스템처럼 쓰지 말 것. AWS S3는 파일시스템 시뮬레이션이 아니다.
c) “두 Lambda가 동시에 같은 파일 처리”
S3는 파일 락이 없다. 멱등성(idempotency)을 애플리케이션이 책임져야 한다. 흔한 패턴:
- 처리 시작 시
processed/<key>마커 객체 생성 시도 (If-None-Match: *로 조건부 PUT) - 마커가 이미 있으면 다른 워커가 처리 중 → skip
d) “S3 객체 내용은 같은데 ETag가 다르네요”
ETag는 일반적으로 객체의 MD5지만 Multipart Upload로 올렸으면 **조각별 MD5의 MD5 + -N**이 된다. 따라서 ETag로 콘텐츠 동일성을 비교할 수 없다. SSE-C/SSE-KMS 암호화 시에도 ETag가 MD5와 무관해진다.
4) S3가 파일시스템이 아닌데도 잘 동작하는 이유
객체 스토리지는 전혀 다른 트레이드오프를 선택했다:
| 차원 | POSIX FS | 객체 스토리지 |
|---|---|---|
| 일관성 | 강함 (단일 노드) | 약함→강함 (분산) |
| 확장성 | TB 수준 | EB 수준 |
| 가용성 | 단일 디스크/RAID | 11 nines (99.999999999%) |
| 동시성 모델 | byte-level lock | object-level write |
| 가격 | 디스크 비용 | 매우 저렴 + API 과금 |
| 지연시간 | μs (in-memory cache) | 수십~수백 ms (HTTP) |
→ S3는 콜드 데이터·미디어·백업·정적 자산에 최적, 데이터베이스 페이지나 OS 같은 hot path엔 부적합.
5) “S3FS, Goofys, ObjectiveFS”는 어떤가
S3 위에 POSIX 인터페이스를 얹는 어댑터들이다. 흉내만 낸다.
mv→Copy + Delete(atomic 아님)truncate→ 다운로드 + 자르기 + 업로드ls→ API 호출- 작은 파일 수만 개 → 폭발적 비용
→ 대량의 정적 파일을 read-only로 읽기엔 OK, 쓰기 워크로드엔 절대 X.
Insight — 흥미로운 이야기
”ext4의 inode 번호는 재사용된다”
inode 한 개를 unlink로 풀면, 같은 번호가 다른 파일에 재할당될 수 있다.
→ “파일 hash로 캐시 키를 만들 때 inode 번호를 쓰면 위험” — 다른 파일이 같은 inode를 차지할 수 있다.
→ 안전한 식별자: (device_id, inode, generation) 또는 콘텐츠 해시.
”macOS의 .DS_Store는 어떻게 생긴 파일인가”
Finder가 각 폴더마다 만드는 바이너리 인덱스 파일이다. 폴더 보기 옵션, 아이콘 위치, 정렬 순서를 저장한다. 매직: 00 00 00 01 42 75 64 31 (Bud1).
→ 다른 OS로 폴더를 옮길 때 불필요한 메타 파일이 따라간다. ZIP 만들 때 --exclude='.DS_Store' 권장.
”FAT32의 4GB 한도가 만든 비극”
USB 메모리는 대부분 FAT32 포맷이다. 4GB 영화 파일을 복사하려 하면 File too large 에러. 사용자는 USB가 32GB여서 들어갈 거라 생각한다.
→ 해결: USB를 exFAT로 재포맷. 또는 macOS는 diskutil eraseDisk ExFAT NEW /dev/disk2.
→ 이 한도의 원인: FAT32의 directory entry에 32비트 부호 없는 정수로 파일 크기를 기록 → 2^32 - 1 = 4,294,967,295 = 약 4GB.
”왜 Linux는 파일을 삭제해도 디스크가 바로 안 비나”
rm은 디렉토리 entry만 제거한다. inode가 링크 0 + 열린 fd 0이 되어야 실제로 데이터 블록이 회수된다.
- 어떤 프로세스가 그 파일을 열고 있다면 fd가 살아있어 inode도 살아있음
- 그래서 진행 중인 로그 파일을
rm하고 디스크 안 비어서 당황하는 사고가 흔하다 →lsof | grep deleted로 확인 후 프로세스 재시작
→ Windows는 정반대: 열린 파일은 삭제 자체가 안 된다. 이게 Windows에서 업데이트하면 재부팅이 필요한 이유 — 사용 중인 DLL을 못 지움.
”S3는 어떻게 디렉토리 없이 사용자에게 디렉토리처럼 보이는가”
AWS 콘솔과 CLI는 키에서 /를 보면 디렉토리로 가장한다:
ListObjectsV2(prefix="folder1/", delimiter="/")→CommonPrefixes로 서브폴더처럼 응답aws s3 cp같은 명령이 재귀 디렉토리를 흉내
이건 클라이언트 측의 환상이지 서버에는 그런 개념이 없다.
”S3의 진짜 이름은 Simple Storage Service”
2006년 출시. 그 시점에 이미 “단순함”이 핵심 가치였다. 디렉토리·POSIX·락 등을 일부러 빼서 무한 확장성과 11 nines 내구성을 얻었다. → 추상화의 가장 어려운 결정은 무엇을 안 넣을지다.
요약 + 다이어그램
파일시스템 = 바이트 덩어리에 이름·메타데이터·계층 구조를 부여하는 규약 (inode + 디렉토리 + 저널). 데스크탑/서버: ext4 / APFS / NTFS — POSIX 호환, 강한 일관성, atomic rename. S3는 파일시스템이 아닌 key-value 객체 저장소 — flat, atomic rename 없음, 부분 쓰기 없음. 흉내내기 어댑터(s3fs)는 비싸고 위험. 객체 스토리지는 별개의 도구로 받아들여야 한다.
챕터 마무리
여기까지 읽었다면 다음 5가지를 분리해서 이해할 수 있어야 한다:
- 바이트 시퀀스 — 모든 파일의 본질 (
01) - 인코딩 — 바이트를 글자로 해석하는 약속 (
02) - MIME + 매직 — 파일 종류를 식별하는 두 레이어 (
03) - 확장자 vs MIME vs 매직의 어긋남 — 보안의 첫 줄 (
04) - 파일시스템 vs 객체 스토리지 — 저장 모델의 결정적 차이 (
05)
다음 챕터(01-transfer/)는 이 식별·저장된 파일이 네트워크를 어떻게 건너오는가를 다룬다 — HTTP, multipart, Range, resumable, CDN, 서명 URL.