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):

  1. 변경할 메타데이터를 먼저 저널 영역에 기록
  2. 디스크에 sync
  3. 실제 위치에 메타데이터 기록
  4. 저널에서 해당 항목 제거

→ 전원 차단 시 부팅 후 저널만 재생하면 일관성 복구. fsck수 초에 끝남.

모드:

  • journal: 데이터까지 저널 → 가장 안전, 가장 느림
  • ordered: 데이터 먼저 → 메타데이터 저널 (ext4 기본)
  • writeback: 메타데이터만 저널, 데이터 순서 보장 X → 빠름, 위험

5) Copy-on-Write (COW) — APFS, ZFS, Btrfs

수정 시 원본 블록을 덮어쓰지 않고 새 블록에 쓴 뒤 메타데이터만 갱신.

  • 스냅샷이 공짜 (데이터 블록 공유)
  • 부분 쓰기 실패해도 원본 보존
  • 단점: 단편화(fragmentation) — 같은 파일의 블록이 디스크 곳곳에 흩어짐

→ 이 차이가 macOS의 cp순간적으로 끝나는 이유 (clone, APFS의 clonefile()).


What — 주요 파일시스템 비교

데스크탑·서버 파일시스템

파일시스템OS등장최대 파일최대 볼륨특징
ext4Linux200816 TB1 EB저널링, extent, 안정성
XFSLinux1994 (IRIX), 2002 (Linux)8 EB8 EB대용량, 병렬 I/O
BtrfsLinux200916 EB16 EBCOW, 스냅샷, 압축
ZFSSolaris/FreeBSD/Linux200516 EB256 ZBCOW, 풀, 체크섬, 중복제거
APFSmacOS/iOS20178 EB8 EBCOW, 스냅샷, 암호화 통합, clone
HFS+macOS (legacy)19988 EB8 EBAPFS 이전 macOS 기본
NTFSWindows199316 EB16 EB저널링, ACL, 압축, EFS 암호화
FAT32모든 OS19964 GB2 TB호환성 ↑, 큰 파일 X (USB·SD에 흔함)
exFAT모든 OS200616 EB128 PBFAT 후속, SD/SSD 카드 표준

모바일·임베디드

파일시스템용도
F2FSAndroid (NAND flash 최적화)
YAFFS2임베디드 NAND
JFFS2임베디드 NOR flash
SquashFS읽기 전용 압축 (Live USB, 컨테이너 이미지)
OverlayFSDocker 레이어 (read-only base + read-write upper)

분산·네트워크

시스템용도
NFSUNIX 표준 네트워크 파일공유 (RFC 7530, NFSv4)
SMB / CIFSWindows 네트워크 파일공유 (Samba)
9PPlan 9, WSL2의 \wsl$ 공유
Lustre, GPFS, CephHPC 클러스터, PB급
HDFSHadoop 분산 파일시스템

”파일시스템이 아닌” 저장소 — 객체 스토리지

시스템인터페이스특징
AWS S3HTTP REST APIflat key-value, 디렉토리 없음
Google Cloud StorageHTTP RESTS3와 유사
Azure Blob StorageHTTP RESTcontainer/blob 구조
MinIOS3 호환 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 수준
가용성단일 디스크/RAID11 nines (99.999999999%)
동시성 모델byte-level lockobject-level write
가격디스크 비용매우 저렴 + API 과금
지연시간μs (in-memory cache)수십~수백 ms (HTTP)

S3는 콜드 데이터·미디어·백업·정적 자산에 최적, 데이터베이스 페이지나 OS 같은 hot path엔 부적합.

5) “S3FS, Goofys, ObjectiveFS”는 어떤가

S3 위에 POSIX 인터페이스를 얹는 어댑터들이다. 흉내만 낸다.

  • mvCopy + 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가지를 분리해서 이해할 수 있어야 한다:

  1. 바이트 시퀀스 — 모든 파일의 본질 (01)
  2. 인코딩 — 바이트를 글자로 해석하는 약속 (02)
  3. MIME + 매직 — 파일 종류를 식별하는 두 레이어 (03)
  4. 확장자 vs MIME vs 매직의 어긋남 — 보안의 첫 줄 (04)
  5. 파일시스템 vs 객체 스토리지 — 저장 모델의 결정적 차이 (05)

다음 챕터(01-transfer/)는 이 식별·저장된 파일이 네트워크를 어떻게 건너오는가를 다룬다 — HTTP, multipart, Range, resumable, CDN, 서명 URL.