LLM WikiAccess-protected knowledge portal

WIKI

파일시스템 선택과 마운트 최적화: ext4, XFS, Btrfs

파일시스템 선택이 데이터베이스 성능에 미치는 영향 "파일시스템은 그냥 ext4 쓰면 된다"는 말을 자주 듣는다. 많은 경우 맞다. 그런데 Allegro가 Kafka 클러스터를 ext4에서 XFS로 바꿨을 때 produce 꼬리 레이턴시가 80% 이상 개선되었다. PostgreSQL WAL이 있는 ext4 서버에서 nobarrier 마운트 옵션을 잘못 설정했다가 장애 이후 데이터가 손상된 사례도 있다. 파일시스템은 운영체제와 스

경로human/study/content/storage-and-io/03-filesystem-selection-mount-optimization.md
카테고리Study
태그#filesystem #mount #optimization #selection #study

파일시스템 선택이 데이터베이스 성능에 미치는 영향

"파일시스템은 그냥 ext4 쓰면 된다"는 말을 자주 듣는다. 많은 경우 맞다. 그런데 Allegro가 Kafka 클러스터를 ext4에서 XFS로 바꿨을 때 produce 꼬리 레이턴시가 80% 이상 개선되었다. PostgreSQL WAL이 있는 ext4 서버에서 nobarrier 마운트 옵션을 잘못 설정했다가 장애 이후 데이터가 손상된 사례도 있다.

파일시스템은 운영체제와 스토리지 하드웨어 사이의 번역 계층이다. 어떤 번역 계층을 쓰느냐에 따라 동일한 NVMe라도 성능이 달라지고, 동일한 마운트 실수라도 결과가 달라진다.

이 글에서 다루는 내용:


ext4

내부 구조

Extent 기반 블록 관리

ext4 이전(ext3)은 간접 블록 포인터(indirect block pointer)로 파일 위치를 기록했다. 파일이 크면 포인터 체인이 길어지고 메타데이터 접근이 많아진다. ext4는 이를 Extent 방식으로 대체했다.

Extent는 "시작 블록 + 연속 길이"로 표현된다. 4KB 블록 기준 최대 128MB를 extent 하나로 기술할 수 있다. inode 안에 extent 4개까지 직접 저장되고, 그 이상이면 B+ 트리(extent tree)로 확장된다.

ext3: 간접 블록 포인터 inode (직접 포인터 12개 + 간접 포인터) 블록 1 블록 2 블록 3 단점: 큰 파일 = 긴 포인터 체인 조각화 발생 시 비연속 블록 나열 최대 파일 크기: 2 TiB 랜덤 접근: 메타데이터 다단계 조회 대규모 파일: 성능·단편화 문제 ext4: Extent 방식 inode (Extent 최대 4개 직접 저장) Extent: 블록 500~756 (연속 257개 = 1 MiB) 시작 블록 번호 + 연속 길이 = 하나의 레코드 5개 이상 → inode 내 B+ 트리(extent tree)로 확장 최대 파일 크기: 16 TiB (4KB 블록 기준) 랜덤 접근: 최대 1회 메타데이터 조회 연속 할당 → 단편화 대폭 감소 대용량 파일 쓰기 성능 향상
ext4 Extent 구조

저널(Journal)과 세 가지 모드

파일시스템은 쓰기 도중 전원이 나가도 일관된 상태를 유지해야 한다. ext4는 JBD2(Journaling Block Device 2) 저널을 사용한다. data= 마운트 옵션으로 동작 방식을 선택한다.

모드저널에 기록하는 것안전성성능
data=journal데이터 + 메타데이터최고 (이중 쓰기)최저
data=ordered메타데이터만; 데이터는 먼저 디스크에 기록높음중간 (기본값)
data=writeback메타데이터만; 데이터 순서 보장 없음낮음최고

data=ordered가 기본값이며 대부분의 데이터베이스에 적합하다. InnoDB처럼 자체 더블 라이트 버퍼를 갖는 엔진은 data=writeback을 사용해도 안전하다.

Delayed Allocation(지연 할당)

ext4는 실제 디스크 쓰기 직전까지 블록 할당을 미룬다. 이를 통해 더 큰 연속 extent를 한 번에 할당할 수 있어 단편화를 줄이고 순차 쓰기 성능이 향상된다. 단, fsync() 없이 프로세스가 비정상 종료하면 0으로 채워진 블록이 남을 수 있다. 데이터베이스 엔진은 일반적으로 fsync()를 올바르게 호출하므로 문제가 없다.

Fast Commit (커널 5.10+)

커널 5.10(2020년 11월)부터 도입된 경량 저널 방식이다. 풀 트랜잭션 블록 대신 메타데이터 변경분만 기록한다. data=ordered 기준 fsync 집중 워크로드에서 20~200% 쓰기 성능 향상이 보고되었다.

주요 한계


XFS

내부 구조: 할당 그룹(Allocation Group)

XFS의 핵심은 Allocation Group(AG) 이다. 파일시스템 전체를 여러 AG로 분할하고, 각 AG는 독립적인 lock을 가진다.

다중 스레드 동시 요청 (InnoDB buffer pool flusher, WAL writer, …) Thread 1 Thread 2 Thread 3 Thread 4 AG 0 inode B+ 트리 free space B+ 트리 extent map B+ 트리 독립 Lock → 경합 없음 AG 1 inode B+ 트리 free space B+ 트리 extent map B+ 트리 독립 Lock → 경합 없음 AG 2 inode B+ 트리 free space B+ 트리 extent map B+ 트리 독립 Lock → 경합 없음 ext4 비교 전역 inode 할당 lock → inode 생성이 직렬화 파일 수백만 개 생성 시 lock 경합 → 병목 XFS: 이 병목 없음
XFS 할당 그룹(AG) 구조와 동시성

각 AG는 독립적인 B+ 트리로 inode·free space·extent를 관리한다. 스레드가 동시에 각자 다른 AG에서 작업하면 lock 경합이 없다. 다중 코어·다중 디스크 구성에서 XFS가 ext4보다 높은 동시 쓰기 처리량을 보이는 근본 이유다.

저널(Log)

XFS 저널은 메타데이터만 기록한다(data=ordered 같은 모드 선택 없음). 저널은 파일시스템 내부(기본) 또는 외부 별도 디바이스에 배치할 수 있다. logbsize(로그 버퍼 크기, 기본 32KB~256KB)와 logbufs(인메모리 버퍼 수, 기본 8)로 튜닝한다.

Speculative Preallocation(투기적 선할당)

파일이 증가 중일 때 XFS는 요청된 크기보다 더 많은 공간을 미리 확보한다. 파일이 닫힐 때 미사용 공간을 해제한다. 이 메커니즘이 대용량 파일 쓰기에서 XFS의 연속 extent 크기를 크게 유지하는 비결이다.

주요 한계


Btrfs

Copy-on-Write(CoW)

Btrfs의 모든 쓰기는 기존 블록을 수정하지 않고 새 위치에 복사본을 만드는 CoW 방식이다. 이를 통해 원자적 쓰기, 즉각 스냅샷, 블록 수준 체크섬을 지원한다.

전통적 파일시스템 (ext4/XFS)
블록 A (현재 데이터)
↓ 쓰기 요청
블록 A (새 데이터로 덮어씀)
이전 데이터 → 사라짐
전원 장애 시 반쪽 쓰기 위험
Btrfs (CoW)
블록 A (기존, 유지됨)
↓ 쓰기 요청
블록 A' (새 위치에 기록)
↓ 메타데이터 업데이트
포인터: A → A' 원자적 전환
기존 블록 A → 스냅샷이 참조 중이면 보존
체크섬으로 무결성 검증 가능
데이터베이스 문제점: 4KB InnoDB 페이지 업데이트 → 항상 새 위치에 복사 → 단편화 누적 + 메타데이터 트리 연쇄 CoW → 쓰기 증폭
Btrfs CoW vs 전통적 In-place Write

CoW와 데이터베이스: 왜 문제인가

InnoDB, PostgreSQL처럼 랜덤 업데이트가 많은 엔진은 Btrfs의 CoW와 충돌한다.

chattr +C로 CoW 비활성화

# 디렉터리에 적용 (이후 생성되는 파일에 적용)
mkdir /var/lib/mysql
chattr +C /var/lib/mysql

# 기존 빈 파일에 적용
truncate -s 0 /var/lib/mysql/ibdata1
chattr +C /var/lib/mysql/ibdata1

# 확인
lsattr /var/lib/mysql/

중요 제약:

RAID 프로필 안정성 (2026년 기준)

프로필상태비고
single, RAID 0, 1, 10안정 (프로덕션 가능)
RAID 5, 6프로덕션 불가커널 6.12부터 CONFIG_BTRFS_EXPERIMENTAL 필요

RAID 5/6는 write hole 문제(전원 장애 시 패리티 불일치)가 아직 해결되지 않았다.

투명 압축

# zstd 레벨 3 압축으로 마운트 (콜드 데이터 볼륨에 적합)
mount -o compress=zstd:3 /dev/sdb1 /data/archive

압축 알고리즘: lzo(가장 빠름), zstd:1~3(추천 균형점), zlib(최고 압축률). 로그·텍스트 데이터가 많은 볼륨에서 쓰기 바이트 감소로 실질적 I/O가 줄어드는 효과가 있다. 단, nodatacow(+C)와 같이 쓸 수 없다.


데이터베이스별 권장 파일시스템

MySQL InnoDB
XFS 권장 / ext4 가능
• NVMe: ext4 사용 시 dioread_nolock 필수
data=writeback 허용 (InnoDB 자체 보호)
• Btrfs: 피할 것
PostgreSQL
XFS 권장 / ext4 가능
• WAL 쓰기 fsync 집중 → XFS 저널 유리
data=ordered (ext4 기본값) 적절
• Btrfs: 피할 것
ClickHouse
ext4 공식 권장 / XFS 가능
• 공식 문서: ext4 + noatime
• ClickHouse 자체 LZ4/ZSTD 압축 사용
• 압축 파일시스템 사용 금지
Apache Kafka
XFS 강력 권장
• ext4: data=writeback 필수 (Kafka 자체 복구)
• XFS 전환 시 꼬리 레이턴시 80%+ 개선 사례
• 순차 I/O 패턴 → XFS와 잘 맞음
데이터베이스별 파일시스템 선택 가이드

핵심 마운트 옵션 정리

옵션ext4XFSBtrfs설명
noatime접근 시간(atime) 갱신 생략 — 항상 권장
data=ordered기본값데이터→디스크 먼저, 그 후 메타데이터 저널
data=writeback선택메타데이터만 저널, 순서 보장 없음 (InnoDB/Kafka)
dioread_nolockNVMe 필수ext4 NVMe Direct I/O 처리량 저하 방지
logbsize=256k권장XFS 로그 버퍼 크기 (최대 256KB)
logbufs=8권장XFS 인메모리 로그 버퍼 수
discard주의주의주의온라인 TRIM — DB 워크로드에는 주기적 fstrim 권장
compress=zstd:3아카이브투명 압축 (nodatacow와 함께 사용 불가)
nodatacowDB용CoW 비활성화 (체크섬·압축도 비활성화됨)

Write Barrier: 언제 비활성화해도 안전한가

저널링 파일시스템은 안전한 커밋을 위해 캐시 플러시(barrier) 명령을 스토리지에 보낸다.

[데이터 플러시] → [저널 커밋 블록 기록] → [커밋 블록 플러시]

이 순서가 보장되지 않으면 전원 장애 후 저널이 일관되지 않은 상태가 될 수 있다.

상황barrier 비활성화 안전 여부이유
하드웨어 BBU(배터리 백업) 컨트롤러안전정전 시 배터리로 캐시 플러시 완료
PLP(Power Loss Protection) NVMe 커패시터안전커패시터가 DRAM→NAND 플러시 에너지 공급
쓰기 캐시 없는 Write-through 스토리지안전이미 동기 쓰기
소비자용 SSD (PLP 없음)위험정전 시 캐시 데이터 유실
BBU 없는 RAID 컨트롤러위험컨트롤러 DRAM 휘발
가상 머신위험하드웨어 캐시 속성 불투명
클라우드 블록 스토리지 (EBS 등)위험제공자가 PLP 보장 명시 없으면 유지

XFS와 barrier의 역사적 변화:

따라서 XFS에서는 nobarrier 마운트 옵션을 사용하지 않는다. BBU/PLP 장치에서는 드라이버 레벨에서 write-through를 보고하도록 설정하면 XFS가 자동으로 barrier를 생략한다.

ext4는 여전히 barrier=0 또는 nobarrier를 지원하지만, 확인된 BBU/PLP 없이는 사용하지 않는 것이 원칙이다. Red Hat 공식 문서 기준 barrier 유지 시 성능 페널티는 현대 하드웨어에서 약 3% 수준이다.


권장 fstab 예시

# MySQL InnoDB — ext4 + NVMe
/dev/nvme0n1p1 /var/lib/mysql ext4 defaults,noatime,data=ordered,dioread_nolock 0 2

# MySQL InnoDB — XFS
/dev/nvme0n1p1 /var/lib/mysql xfs defaults,noatime,logbsize=256k,logbufs=8 0 2

# Kafka 로그 — ext4
/dev/sdb1 /var/kafka-logs ext4 defaults,noatime,nodiratime,data=writeback 0 2

# Kafka 로그 — XFS
/dev/sdb1 /var/kafka-logs xfs defaults,noatime,nodiratime 0 2

# ClickHouse — ext4 (공식 권장)
/dev/sdc1 /var/lib/clickhouse ext4 defaults,noatime 0 2

# 아카이브 / 콜드 데이터 — Btrfs + 압축
/dev/sdd1 /data/archive btrfs defaults,noatime,compress=zstd:3 0 2

운영 체크리스트

□ 파일시스템 확인
    df -Th          # 마운트된 파일시스템 타입 확인
    cat /proc/mounts | grep -E 'xfs|ext4|btrfs'

□ ext4 NVMe 서버: dioread_nolock 마운트 확인
    findmnt -O dioread_nolock

□ XFS 저널 버퍼 확인
    xfs_info /var/lib/mysql | grep -E 'log|bsize'

□ atime 비활성화 확인
    findmnt -O noatime

□ nobarrier 잔재 확인 (XFS에서는 커널 4.19+ 에서 오류 유발)
    cat /proc/mounts | grep nobarrier

□ Btrfs 데이터베이스 디렉터리 CoW 상태 확인
    lsattr -d /var/lib/mysql    # 'C' 플래그 확인

□ TRIM 설정 확인 (discard 마운트 옵션 대신 fstrim 사용)
    systemctl status fstrim.timer
    # 없으면 활성화:
    systemctl enable --now fstrim.timer

Open Questions


References