F2FS 파일시스템 심화
플래시 저장장치 특성에 맞춘 F2FS를 중심으로 로그 구조 쓰기 경로, NAT/SIT/SSA 메타데이터의 역할, hot/warm/cold 분리 기록, foreground/background GC 정책, 체크포인트와 롤포워드 복구, segment 청소 비용 제어, 압축·fscrypt·fsverity 기능, Android 워크로드에서의 mount 옵션 및 수명/성능 튜닝 전략을 상세히 다룹니다.
핵심 요약
- 계층 이해 — VFS, 캐시, 하위 FS 경계를 구분합니다.
- 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
- 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
- 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
- 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.
단계별 이해
- 경계 계층 파악
요청이 VFS에서 어디로 내려가는지 확인합니다. - 메타/데이터 분리
어느 경로에서 무엇이 갱신되는지 나눠 봅니다. - 동기화/플러시 확인
쓰기 반영 시점과 순서를 검증합니다. - 복구 시나리오 점검
비정상 종료 후 일관성 회복을 확인합니다.
개요 & 역사
F2FS(Flash-Friendly File System)는 Samsung Electronics가 개발한 Linux 파일시스템으로, NAND Flash 스토리지(eMMC, SD 카드, SSD)의 특성에 최적화된 로그 구조(Log-Structured) 설계를 채택합니다. Linux 3.8(2013년 2월)에 메인라인 머지되었습니다.
Flash 스토리지 특성
F2FS 설계를 이해하려면 NAND Flash의 물리적 제약을 먼저 알아야 합니다:
| 특성 | HDD | NAND Flash |
|---|---|---|
| 읽기 단위 | 섹터 (512B) | 페이지 (4KB~16KB) |
| 쓰기 단위 | 섹터 (제자리) | 페이지 (빈 페이지만) |
| 삭제 단위 | 불필요 | 블록 (128~512 페이지) |
| 제자리 덮어쓰기 | 가능 | 불가능 (erase-before-write) |
| 수명 | 기계적 마모 | P/E cycle 제한 (TLC: ~3000, QLC: ~1000) |
Flash 스토리지에서는 erase-before-write 제약으로 인해 제자리 갱신(in-place update)이 비효율적입니다. FTL(Flash Translation Layer)이 이를 추상화하지만, 파일시스템 레벨에서 Flash 특성을 고려하면 성능과 수명을 크게 개선할 수 있습니다.
설계 목표
- Flash 친화적 쓰기: 순차 쓰기(append-only) 패턴으로 FTL 부하 최소화
- Wandering tree 문제 해결: NAT을 통한 간접 주소 변환으로 연쇄 갱신 방지
- 다중 로그 헤드: Hot/Warm/Cold 데이터 분리로 GC 효율 극대화
- 빠른 복구: 체크포인트 + roll-forward 복구로 빠른 마운트
- 모바일 환경 최적화: 빈번한 fsync, 소형 파일, 랜덤 쓰기 패턴 대응
F2FS 역사
| 시기 | 이정표 |
|---|---|
| 2012.10 | Samsung, LSFMM에서 F2FS 설계 발표 |
| 2013.02 | Linux 3.8 메인라인 머지 (Jaegeuk Kim) |
| 2014 | Motorola Moto G, F2FS 최초 상용 적용 |
| 2016 | Huawei P9, F2FS 기본 파일시스템 채택 |
| 2018 | Google Pixel 3, userdata 파티션 F2FS 채택 |
| 2019 | 압축(LZO/LZ4) 지원, zoned storage 지원 |
| 2020 | ZSTD 압축 지원, 다중 디바이스 지원 |
| 2022 | Android 13+ 기본 파일시스템으로 광범위 채택 |
아키텍처 & 온디스크 레이아웃
F2FS는 전체 볼륨을 고정 크기 세그먼트(segment, 2MB)로 분할하고, 세그먼트를 묶어 섹션(section), 섹션을 묶어 존(zone)을 구성합니다. 온디스크 레이아웃은 6개 영역으로 나뉩니다:
Superblock (SB)
볼륨의 기본 정보를 저장하며, 2개의 복사본이 존재합니다 (0번, 1번 블록):
/* include/linux/f2fs_fs.h */
struct f2fs_super_block {
__le32 magic; /* 0xF2F52010 */
__le16 major_ver; /* 주 버전 */
__le16 minor_ver; /* 부 버전 */
__le32 log_sectorsize; /* 섹터 크기 (log2) */
__le32 log_sectors_per_block; /* 블록 당 섹터 수 (log2) */
__le32 log_blocksize; /* 블록 크기 (log2), 보통 12 (4KB) */
__le32 log_blocks_per_seg; /* 세그먼트 당 블록 (log2), 보통 9 (512) */
__le32 segs_per_sec; /* 섹션 당 세그먼트 수 */
__le32 secs_per_zone; /* 존 당 섹션 수 */
__le32 checksum_offset; /* SB 내 체크섬 위치 */
__le64 block_count; /* 전체 블록 수 */
__le32 section_count; /* 섹션 수 */
__le32 segment_count; /* 세그먼트 수 */
__le32 segment_count_ckpt; /* CP 영역 세그먼트 수 */
__le32 segment_count_sit; /* SIT 영역 세그먼트 수 */
__le32 segment_count_nat; /* NAT 영역 세그먼트 수 */
__le32 segment_count_ssa; /* SSA 영역 세그먼트 수 */
__le32 segment_count_main; /* Main Area 세그먼트 수 */
__le32 segment0_blkaddr; /* 세그먼트 0 시작 블록 주소 */
__le32 cp_blkaddr; /* CP 시작 블록 주소 */
__le32 sit_blkaddr; /* SIT 시작 블록 주소 */
__le32 nat_blkaddr; /* NAT 시작 블록 주소 */
__le32 ssa_blkaddr; /* SSA 시작 블록 주소 */
__le32 main_blkaddr; /* Main Area 시작 블록 주소 */
/* ... feature flags, encryption, etc. */
};
6개 영역 요약
| 영역 | 크기 | 역할 |
|---|---|---|
| Superblock (SB) | 1개 세그먼트 | 볼륨 메타데이터, 매직 넘버, 레이아웃 정보 |
| Checkpoint (CP) | 2개 세그먼트 | 파일시스템 일관성 상태, 유효 NAT/SIT 저널, orphan inode |
| SIT (Segment Info Table) | N 세그먼트 | 각 세그먼트의 유효 블록 비트맵 & 카운트 |
| NAT (Node Address Table) | N 세그먼트 | 노드 ID → 물리 블록 주소 매핑 (wandering tree 해결) |
| SSA (Segment Summary Area) | N 세그먼트 | 블록별 역매핑: 어느 노드의 어느 오프셋인지 |
| Main Area | 대부분 | 실제 노드 블록 + 데이터 블록 저장 |
로그 구조 쓰기 (LFS)
전통적인 LFS(Log-Structured File System)는 단일 로그 헤드를 사용하지만, F2FS는 6개의 로그 헤드(current segment)를 동시에 운용합니다. 데이터 온도(temperature)에 따라 분리 기록하여 GC 효율을 극대화합니다:
| 로그 헤드 | 온도 | 용도 |
|---|---|---|
| CURSEG_HOT_NODE | Hot | 직접 노드 블록 (inode) |
| CURSEG_WARM_NODE | Warm | 간접 노드 블록 |
| CURSEG_COLD_NODE | Cold | 간접-간접 노드 블록 |
| CURSEG_HOT_DATA | Hot | 디렉토리 엔트리 |
| CURSEG_WARM_DATA | Warm | 일반 파일 데이터 |
| CURSEG_COLD_DATA | Cold | 멀티미디어, GC에 의해 이동된 데이터 |
/* fs/f2fs/segment.h */
enum {
CURSEG_HOT_DATA = 0,
CURSEG_WARM_DATA,
CURSEG_COLD_DATA,
CURSEG_HOT_NODE,
CURSEG_WARM_NODE,
CURSEG_COLD_NODE,
NR_CURSEG_TYPE, /* = 6 */
};
Append-Only 쓰기 패턴
F2FS는 데이터를 제자리에서 갱신(in-place update)하지 않고, 항상 현재 세그먼트의 빈 블록에 순차적으로 추가(append)합니다. 이전 위치의 블록은 무효(invalid)로 표시됩니다:
/* fs/f2fs/segment.c - 새 블록 할당 */
static void __allocate_new_segment(struct f2fs_sb_info *sbi,
int type, bool new_sec)
{
struct curseg_info *curseg = CURSEG_I(sbi, type);
unsigned int old_segno = curseg->segno;
/* 현재 세그먼트가 가득 찼으면 새 세그먼트 할당 */
if (!curseg->inited || !get_valid_blocks(sbi, old_segno, false) ||
curseg->alloc_type == SSR)
new_curseg(sbi, type, true);
}
할당 방식: LFS vs SSR
F2FS는 두 가지 할당 방식을 사용합니다:
- LFS (Log-Structured, Normal): 깨끗한(clean) 세그먼트를 순차적으로 사용. 최적의 순차 쓰기 패턴
- SSR (Slack Space Recycling): 이미 부분적으로 사용된 세그먼트의 빈 블록을 채움. 여유 공간이 부족할 때 GC 없이 공간 확보
NAT (Node Address Table)
NAT은 F2FS의 핵심 혁신으로, 전통적 LFS의 wandering tree 문제를 해결합니다.
Wandering Tree 문제
전통적 LFS에서 데이터 블록이 새 위치에 기록되면, 이를 가리키는 간접 노드 → inode → inode map 전체가 연쇄적으로 갱신되어야 합니다. F2FS는 NAT 테이블을 도입하여 노드 ID와 물리 주소 사이에 간접 계층을 둡니다:
/* include/linux/f2fs_fs.h */
struct f2fs_nat_entry {
__u8 version; /* 버전 (SIT와 일관성 체크) */
__le32 ino; /* 소속 inode 번호 */
__le32 block_addr; /* 노드의 실제 물리 블록 주소 */
} __packed;
NAT 영역은 SIT와 마찬가지로 이중 버퍼링됩니다. 체크포인트 시 현재 유효한 NAT 페이지 세트가 기록되며, CP의 nat_bitmap이 어느 세트가 최신인지 가리킵니다.
SIT (Segment Information Table)
SIT는 각 세그먼트의 상태를 추적합니다. 세그먼트 내 각 블록의 유효/무효 여부를 비트맵으로 관리하고, 유효 블록 수를 카운트합니다:
/* include/linux/f2fs_fs.h */
struct f2fs_sit_entry {
__le16 vblocks; /* 유효 블록 수 + type(10bit+6bit) */
__u8 valid_map[SIT_VBLOCK_MAP_SIZE]; /* 유효 블록 비트맵 (64 bytes = 512 bits) */
__le64 mtime; /* 세그먼트 수정 시간 (GC 비용 계산) */
} __packed;
SIT 이중 버퍼링
SIT 영역은 두 개의 복사본(set 0, set 1)으로 구성됩니다. 체크포인트마다 번갈아 갱신하여 원자적 갱신을 보장합니다. CP의 sit_nat_version_bitmap이 각 SIT 페이지의 유효한 세트를 가리킵니다.
SIT 활용
- GC 대상 선택: 유효 블록이 적은 세그먼트를 GC 대상으로 선택 (비용-이득 분석)
- 여유 공간 계산: 전체 유효 블록 수에서 여유 세그먼트 수 산출
- SSR 모드: 유효 블록 비트맵을 참조하여 빈 블록 위치 결정
SSA (Segment Summary Area)
SSA는 Main Area의 각 블록에 대한 역매핑(reverse mapping) 정보를 저장합니다. 블록의 물리 주소로부터 해당 블록이 어느 노드의 어느 오프셋에 속하는지 역추적할 수 있습니다:
/* include/linux/f2fs_fs.h */
struct f2fs_summary {
__le32 nid; /* 부모 노드 ID */
union {
__u8 reserved;
struct {
__u8 version; /* 노드 버전 */
__le16 ofs_in_node; /* 부모 노드 내 오프셋 */
};
};
} __packed;
GC에서의 SSA 역할
GC가 세그먼트를 정리할 때, 각 유효 블록을 새 위치로 이동해야 합니다. SSA 엔트리를 통해 해당 블록의 부모 노드를 찾고, 노드의 포인터를 갱신합니다:
- GC 대상 세그먼트 선택 (SIT 기반)
- SIT 비트맵에서 유효 블록 식별
- SSA에서 각 유효 블록의 부모 노드(nid) 조회
- NAT에서 부모 노드의 물리 주소 조회
- 유효 블록을 새 세그먼트로 복사
- 부모 노드의 포인터 갱신 (out-of-place)
체크포인트 & 복구
F2FS는 이중 체크포인트(dual checkpoint) 방식으로 파일시스템 일관성을 보장합니다. 체크포인트는 특정 시점의 파일시스템 상태를 안정적으로 기록합니다.
체크포인트 구조
/* include/linux/f2fs_fs.h */
struct f2fs_checkpoint {
__le64 checkpoint_ver; /* 체크포인트 버전 (단조 증가) */
__le64 user_block_count; /* 사용자 블록 총 수 */
__le64 valid_block_count; /* 유효 블록 수 */
__le32 rsvd_segment_count; /* 예약 세그먼트 (GC 용) */
__le32 overprov_segment_count; /* 오버프로비저닝 세그먼트 */
__le32 free_segment_count; /* 여유 세그먼트 수 */
__le32 ckpt_flags; /* CP_UMOUNT_FLAG, CP_FSCK_FLAG 등 */
__le32 cp_pack_total_block_count; /* CP 팩 전체 블록 수 */
__le32 cp_pack_start_sum; /* 요약 블록 시작 오프셋 */
__le32 valid_node_count; /* 유효 노드 수 */
__le32 valid_inode_count; /* 유효 inode 수 */
__le32 next_free_nid; /* 다음 사용 가능 노드 ID */
__le32 sit_ver_bitmap_bytesize; /* SIT 버전 비트맵 크기 */
__le32 nat_ver_bitmap_bytesize; /* NAT 버전 비트맵 크기 */
__le32 checksum_offset; /* 체크섬 위치 */
__le64 elapsed_time; /* 마운트 누적 시간 */
/* 이후 current segment 정보, orphan inode, SIT/NAT 저널 */
};
체크포인트 절차
- 모든 dirty 노드/데이터 페이지 flush
- dirty NAT/SIT 엔트리를 NAT/SIT 영역에 기록 (또는 CP 내 저널에 저장)
- CP 팩 기록: header → orphan inode → 요약(summary) → footer
- CP 팩의 footer에 체크섬 기록 → 원자적 커밋 포인트
checkpoint_ver이 더 큰 쪽이 최신 유효 CP입니다. CP 기록 중 비정상 종료가 발생해도 이전 CP가 유효하므로 데이터 손실을 방지합니다.
Roll-Forward 복구
F2FS는 마지막 체크포인트 이후에 fsync()된 데이터를 roll-forward 기법으로 복구합니다:
- 마운트 시 최신 CP 로드
- CP 이후에 기록된 노드 블록을 Main Area에서 스캔
- fsync 마크(
FADVISE_FSYNC_BIT)가 있는 노드의 데이터를 복구 - NAT/SIT 갱신하여 파일시스템 상태 일관성 확보
/* fs/f2fs/recovery.c */
static int f2fs_recover_fsync_data(struct f2fs_sb_info *sbi, bool check_only)
{
/* Step 1: curseg 이후의 노드 스캔 */
find_fsync_dnodes(sbi, &inode_list, check_only);
/* Step 2: 유효한 fsync 노드의 데이터 복구 */
do_recover_data(sbi, &inode_list);
/* Step 3: 복구 완료 후 체크포인트 */
f2fs_write_checkpoint(sbi, CP_RECOVERY_FLAG);
return 0;
}
가비지 컬렉션 (GC)
로그 구조 파일시스템에서 GC는 핵심 동작입니다. 무효화된 블록이 포함된 세그먼트를 정리하여 깨끗한 세그먼트를 확보합니다.
GC 모드
| 모드 | 트리거 | 대상 선택 | 특징 |
|---|---|---|---|
| Background GC (BG_GC) | 커널 스레드 주기적 실행 | Cost-Benefit | 유휴 시 점진적, I/O 영향 최소 |
| Foreground GC (FG_GC) | 여유 세그먼트 부족 | Greedy | 즉시 공간 확보, 지연 발생 |
대상 선택 알고리즘
Greedy 정책 (Foreground GC): 유효 블록이 가장 적은 세그먼트를 선택합니다. 이동할 데이터가 적어 빠르게 공간을 확보할 수 있습니다.
Cost-Benefit 정책 (Background GC): 유효 블록 수뿐 아니라 세그먼트의 나이(age)도 고려합니다. 오래된 세그먼트의 데이터는 Cold 데이터일 확률이 높아, 이동 후 다시 무효화될 가능성이 낮습니다:
- Cost-Benefit 공식:
- cost = (1 - u) / (2 * u * age)
- u = valid_blocks / total_blocks (utilization)
- age = current_time - segment_mtime
- 비용이 낮은 세그먼트가 GC에 유리 */
GC 실행 흐름
/* fs/f2fs/gc.c */
static int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control)
{
/* 1. victim 세그먼트 선택 */
f2fs_get_victim(sbi, &segno, gc_type, type, alloc_mode, age);
/* 2. 유효 블록 이동 */
do_garbage_collect(sbi, segno, &gc_list, gc_type, force);
/* - 데이터 블록: 새 COLD_DATA 세그먼트로 복사 */
/* - 노드 블록: 새 노드 세그먼트로 복사 */
/* - SIT/NAT 갱신 */
/* 3. 정리된 세그먼트를 free 세그먼트로 반환 */
return seg_freed;
}
GC 튜닝 파라미터
| sysfs 파라미터 | 기본값 | 설명 |
|---|---|---|
gc_min_sleep_time | 30000 (30s) | BG GC 최소 대기 시간 (ms) |
gc_max_sleep_time | 60000 (60s) | BG GC 최대 대기 시간 (ms) |
gc_no_gc_sleep_time | 300000 (5m) | GC 불필요 시 대기 시간 |
gc_idle | 0 | 0=비활성, 1=BG GC, 2=FG GC 강제 |
gc_urgent | 0 | 0=일반, 1=저속 GC, 2=고속 GC, 3=최고속 |
gc_urgent_high_limit | - | 긴급 GC 공간 상한 (사용률 %) |
인라인 데이터 & 인라인 디렉토리
소형 파일과 디렉토리에 대해 별도의 데이터 블록을 할당하지 않고, inode 블록 내부의 여유 공간에 직접 저장합니다.
인라인 데이터
F2FS inode는 4KB 블록을 차지하며, 메타데이터 이후의 여유 공간(약 3.4KB)에 소형 파일의 데이터를 직접 저장할 수 있습니다:
/* fs/f2fs/f2fs.h */
#define MAX_INLINE_DATA(inode) \
(sizeof(__le32) * (DEF_ADDRS_PER_INODE - \
get_inline_xattr_addrs(inode) - \
F2FS_INLINE_XATTR_ADDRS - 1))
/* 보통 ~3,488 bytes까지 인라인 저장 가능 */
FI_INLINE_DATA플래그가 설정된 inode에 적용- 파일이 커지면 자동으로 일반 데이터 블록으로 변환 (
f2fs_convert_inline_inode()) - 추가 블록 할당이 불필요하여 공간/성능 모두 이점
인라인 디렉토리
소규모 디렉토리(엔트리 수가 적은 경우)도 inode 블록 내에 저장됩니다:
/* fs/f2fs/f2fs.h */
#define NR_INLINE_DENTRY(inode) \
(MAX_INLINE_DATA(inode) * BITS_PER_BYTE / \
((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * BITS_PER_BYTE + 1))
/* 약 20~30개 엔트리까지 인라인 저장 가능 */
Extent Cache
F2FS는 논리 블록 주소 → 물리 블록 주소 매핑을 인메모리 extent 캐시로 가속합니다. Red-black tree로 관리되며, 빈번한 파일 읽기에서 NAT/노드 탐색을 우회합니다:
/* fs/f2fs/extent_cache.c */
struct extent_info {
unsigned int fofs; /* 파일 내 시작 오프셋 (블록 단위) */
unsigned int len; /* 연속 블록 수 */
block_t blk; /* 물리 시작 블록 주소 */
};
struct extent_node {
struct rb_node rb_node; /* rb-tree 노드 */
struct extent_info ei; /* extent 정보 */
struct list_head list; /* LRU 리스트 */
};
- Read extent cache: 논리→물리 매핑 캐싱, 순차 읽기에서 높은 hit rate
- Age extent cache: 블록의 나이 정보 캐싱, GC 비용 계산 가속
- LRU 정책으로 메모리 사용량 제어 (
extent_cache_count)
압축 (Compression)
F2FS는 Linux 5.4부터 투명 파일 압축을 지원합니다. 클러스터(cluster) 단위로 연속 블록을 압축하여 저장 공간을 절약합니다.
지원 알고리즘
| 알고리즘 | 커널 설정 | 특징 |
|---|---|---|
| LZO | CONFIG_F2FS_FS_LZO | 빠른 압축/해제, 낮은 압축률 |
| LZ4 | CONFIG_F2FS_FS_LZ4 | 매우 빠른 해제, 모바일 환경 최적 |
| ZSTD | CONFIG_F2FS_FS_ZSTD | 높은 압축률, 상대적으로 느림 |
| LZORLE | CONFIG_F2FS_FS_LZORLE | LZO + RLE, LZO 개선판 |
클러스터 기반 압축
파일을 고정 크기 클러스터(기본 16 블록 = 64KB)로 분할한 후, 각 클러스터를 독립적으로 압축합니다:
- 클러스터 압축 구조 (16블록 클러스터 예시)
- 원본: [blk0][blk1]...[blk15] (16 * 4KB = 64KB)
- ↓ LZ4 압축
- 압축: [cblk0][cblk1]...[cblk9][NULL]...[NULL]
- 10블록(40KB) 사용 + 6블록 절약
- NULL 블록은 공간 반환 (sparse block)
/* fs/f2fs/compress.c */
static int f2fs_compress_pages(struct compress_ctx *cc)
{
const struct f2fs_compress_ops *cops =
f2fs_cops[F2FS_I(cc->inode)->i_compress_algorithm];
/* 클러스터 페이지를 연속 버퍼에 복사 */
f2fs_compress_gather_pages(cc);
/* 알고리즘별 압축 수행 */
ret = cops->compress_pages(cc);
/* 압축 결과가 원본보다 크면 비압축 저장 */
if (cc->nr_cpages >= cc->nr_rpages)
return -EAGAIN;
return 0;
}
압축 사용법
# 파일시스템 레벨 압축 활성화 (마운트 옵션)
mount -t f2fs -o compress_algorithm=lz4,compress_log_size=4 /dev/sda1 /mnt
# 특정 파일/디렉토리에 압축 속성 설정
f2fs_io setflags compression /mnt/data
chattr +c /mnt/data # FS_COMPR_FL
# 마운트 옵션
compress_algorithm=lz4 # 압축 알고리즘
compress_log_size=4 # 클러스터 크기 (2^N 블록, 4=16블록)
compress_extension=*.log # 자동 압축 대상 확장자
compress_chksum # 압축 데이터 체크섬 검증
암호화 & 무결성
fscrypt (파일 레벨 암호화)
F2FS는 Linux 커널의 fscrypt 프레임워크를 사용하여 파일 단위 암호화를 지원합니다. Android에서 FBE(File-Based Encryption)의 기반 기술입니다:
- 파일 내용: AES-256-XTS
- 파일 이름: AES-256-CTS 또는 AES-256-HCTR2
- 디렉토리별 독립 암호화 정책 설정 가능
- inline encryption 하드웨어 가속 지원 (UFS/eMMC)
# 디렉토리에 암호화 정책 설정
fscryptctl set_policy --contents=AES-256-XTS --filenames=AES-256-CTS /mnt/private/
# F2FS 마운트 옵션
mount -t f2fs -o inlinecrypt /dev/sda1 /mnt # 하드웨어 inline encryption
fsverity (무결성 검증)
fs-verity는 파일의 읽기 전용 인증 메커니즘으로, Merkle tree 기반의 무결성 검증을 제공합니다:
- 파일 내용에 대한 Merkle tree 해시 생성
- 읽기 시 각 블록의 해시를 실시간 검증
- Android APK 서명 검증(APK Signature Scheme v4)에 활용
대소문자 무시 (Casefold)
F2FS는 디렉토리별 대소문자 무시(case-insensitive) 기능을 지원합니다. Android에서 Windows/macOS 호환 파일 접근에 사용됩니다:
# casefold 활성화 (mkfs 시)
mkfs.f2fs -O casefold /dev/sda1
# 디렉토리에 casefold 속성 설정
chattr +F /mnt/shared # 이후 생성되는 파일에 대소문자 무시 적용
다중 디바이스 & Zoned Storage
다중 디바이스 지원
F2FS는 최대 8개의 블록 디바이스로 구성된 볼륨을 지원합니다. Device Mapper 없이 파일시스템 레벨에서 직접 다중 디바이스를 관리합니다:
# 다중 디바이스로 F2FS 생성
mkfs.f2fs -f /dev/sda /dev/sdb /dev/sdc
# 메타데이터는 첫 번째 디바이스, 데이터는 분산 저장
Zoned Storage (ZNS/SMR)
F2FS의 로그 구조 설계는 Zoned Storage와 자연스럽게 호환됩니다. ZNS(Zoned Namespaces) SSD와 SMR(Shingled Magnetic Recording) HDD를 직접 지원합니다:
- F2FS 섹션(section)을 존(zone)에 직접 매핑
- 순차 쓰기만 허용하는 존 제약에 LFS 쓰기 패턴이 자연스럽게 부합
- GC 시 존 리셋(zone reset)으로 전체 존을 한번에 해제
- Conventional zone을 메타데이터/랜덤 쓰기에, Sequential zone을 데이터에 활용
# ZNS SSD에 F2FS 생성
mkfs.f2fs -f -m /dev/nvme0n1 # -m: zoned device mode
# Conventional + Sequential 혼합 구성
mkfs.f2fs -f -c /dev/nvme0n2 /dev/nvme0n1 # conventional + zoned
Device Aliasing (커널 6.13+)
커널 6.13에서 F2FS에 device aliasing 기능이 도입되었습니다. 하나의 물리 디바이스를 여러 논리 영역으로 나누어, 각 영역에 서로 다른 쓰기 정책(LFS/adaptive)이나 라이프타임 힌트를 적용할 수 있습니다. 다중 디바이스 구성 없이도 핫/콜드 데이터 분리 효과를 얻을 수 있어, 단일 디바이스 환경에서의 GC 효율이 향상됩니다.
# device aliasing으로 F2FS 생성 (v6.13+)
# 단일 디바이스에서 논리적 영역 분리
mkfs.f2fs -f -a 1 /dev/nvme0n1
16KB 페이지 크기 지원 개선 (커널 6.19+)
ARM64 플랫폼에서 16KB 기본 페이지 크기 커널을 사용하는 환경(예: 최신 Android)에 맞춰, F2FS의 16KB 페이지 지원이 커널 6.19에서 개선되었습니다. NAND 플래시의 물리 페이지 크기(4KB~16KB)와 OS 페이지 크기의 불일치 문제를 해소하여, 16KB 페이지 커널에서의 공간 효율과 성능이 향상되었습니다.
Android 통합
F2FS는 Android 기기의 /data (userdata) 파티션에 광범위하게 사용됩니다. Android 환경에 특화된 여러 기능이 있습니다:
Android 최적화 기능
| 기능 | 설명 |
|---|---|
| Atomic write | SQLite WAL 파일의 원자적 갱신. F2FS_IOC_START_ATOMIC_WRITE / F2FS_IOC_COMMIT_ATOMIC_WRITE |
| GC urgent mode | IDLE 상태에서 공격적 GC 실행. Android JobScheduler와 연계 |
| Pin file | GC에 의한 이동 방지. 대용량 파일(OBB, 미디어)의 fragmentation 방지 |
| FBE (File-Based Encryption) | fscrypt 기반 파일별 암호화. CE/DE 키 분리 |
| fsverity | APK Signature Scheme v4 인증 |
| Checkpoint disable | OTA 업데이트 중 CP 비활성화로 A/B 롤백 지원 |
| Compression | LZ4 압축으로 /data 용량 절약 (Android 12+) |
Atomic Write
Android의 SQLite 데이터베이스는 WAL(Write-Ahead Log) 모드를 사용합니다. F2FS의 atomic write는 SQLite의 fsync 빈도를 줄이면서도 데이터 무결성을 보장합니다:
/* Atomic write ioctl 흐름 */
ioctl(fd, F2FS_IOC_START_ATOMIC_WRITE); /* COW 시작 */
write(fd, data, len); /* 별도 공간에 기록 */
ioctl(fd, F2FS_IOC_COMMIT_ATOMIC_WRITE); /* 원자적 커밋 (또는 ABORT) */
Android 마운트 옵션 예시
# 일반적인 Android /data 마운트 옵션
/dev/block/by-name/userdata /data f2fs \
noatime,nosuid,nodev,discard,fsync_mode=nobarrier,\
reserve_root=32768,resgid=1065,\
inlinecrypt,checkpoint_merge,\
compress_algorithm=lz4,compress_log_size=3,\
compress_extension=*.apk,compress_extension=*.dex
핵심 커널 자료구조
f2fs_sb_info
F2FS 파일시스템 인스턴스의 중앙 자료구조입니다. 마운트 시 할당되며 모든 서브시스템이 참조합니다:
/* fs/f2fs/f2fs.h */
struct f2fs_sb_info {
struct super_block *sb; /* VFS superblock */
struct f2fs_super_block *raw_super; /* 온디스크 superblock */
struct f2fs_checkpoint *ckpt; /* 현재 체크포인트 */
/* 세그먼트 관리 */
struct f2fs_sm_info *sm_info; /* 세그먼트 매니저 */
struct sit_info *sit_info; /* SIT 관리 */
struct free_segmap_info *free_info; /* 여유 세그먼트 비트맵 */
struct dirty_seglist_info *dirty_info; /* dirty 세그먼트 */
struct curseg_info *curseg_array; /* 6개 current segment */
/* 노드 관리 */
struct f2fs_nm_info *nm_info; /* 노드 매니저 (NAT) */
/* GC */
struct f2fs_gc_kthread *gc_thread; /* BG GC 스레드 */
unsigned int cur_victim_sec; /* 현재 GC 대상 */
/* 통계 & 디버깅 */
struct f2fs_stat_info *stat_info; /* /sys/kernel/debug/f2fs */
atomic_t nr_pages[NR_COUNT_TYPE]; /* 페이지 카운터 */
};
f2fs_inode_info
/* fs/f2fs/f2fs.h */
struct f2fs_inode_info {
struct inode vfs_inode; /* VFS inode (임베디드) */
unsigned long i_flags; /* F2FS inode 플래그 */
unsigned char i_advise; /* fadvise 힌트 */
unsigned char i_dir_level; /* 디렉토리 해시 레벨 */
unsigned int i_current_depth; /* 디렉토리 현재 깊이 */
unsigned int i_pino; /* 부모 inode 번호 */
umode_t i_acl_mode; /* ACL 모드 */
/* 인라인 */
unsigned int i_inline_xattr_size; /* 인라인 xattr 크기 */
/* 압축 */
unsigned char i_compress_algorithm; /* 압축 알고리즘 */
unsigned char i_log_cluster_size; /* 클러스터 크기 (log2) */
unsigned int i_cluster_size; /* 클러스터 블록 수 */
/* extent cache */
struct extent_tree *extent_tree[NR_EXTENT_CACHES];
struct rw_semaphore i_gc_rwsem[2]; /* GC 보호 */
};
curseg_info
/* fs/f2fs/segment.h */
struct curseg_info {
struct mutex curseg_mutex; /* 세그먼트 잠금 */
struct f2fs_summary_block *sum_blk; /* 요약 블록 캐시 */
struct rw_semaphore journal_rwsem; /* 저널 잠금 */
unsigned int segno; /* 현재 세그먼트 번호 */
unsigned short next_blkoff; /* 다음 블록 오프셋 */
unsigned int zone; /* 현재 존 번호 */
unsigned int next_segno; /* 다음 세그먼트 */
int alloc_type; /* LFS or SSR */
bool inited; /* 초기화 여부 */
};
성능 튜닝
주요 마운트 옵션
| 옵션 | 설명 |
|---|---|
noatime | atime 갱신 비활성화 (쓰기 감소) |
discard / nodiscard | 실시간 TRIM 활성/비활성 |
fsync_mode=posix|strict|nobarrier | fsync 동작 모드. nobarrier는 배터리 백업 환경용 |
checkpoint_merge | 다중 체크포인트 요청을 병합하여 I/O 감소 |
gc_merge | GC I/O를 체크포인트와 병합 |
iostat | /sys/kernel/debug/f2fs에 I/O 통계 활성화 |
mode=adaptive|lfs | LFS 전용 모드 (zoned device) 또는 적응 모드 |
active_logs=2|4|6 | 활성 로그 헤드 수 (기본 6) |
alloc_mode=default|reuse | 세그먼트 할당 모드 |
sysfs 튜닝 파라미터
F2FS는 /sys/fs/f2fs/<devname>/ 경로에 다양한 런타임 튜닝 파라미터를 제공합니다:
# 주요 sysfs 파라미터 확인/설정
echo 40 > /sys/fs/f2fs/sda1/gc_urgent_sleep_time # GC 간격 (ms)
echo 100 > /sys/fs/f2fs/sda1/min_seq_blocks # 순차 쓰기 판단 블록 수
cat /sys/fs/f2fs/sda1/gc_urgent # 긴급 GC 모드
cat /sys/fs/f2fs/sda1/dirty_segments # dirty 세그먼트 수
cat /sys/fs/f2fs/sda1/free_segments # 여유 세그먼트 수
cat /sys/fs/f2fs/sda1/ovp_segments # 오버프로비저닝 세그먼트
cat /sys/fs/f2fs/sda1/lifetime_write_kbytes # 누적 기록량
I/O 스케줄러 조합
none(noop) 또는 mq-deadline I/O 스케줄러를 권장합니다. F2FS 자체가 I/O 순서를 최적화하므로 복잡한 스케줄러는 불필요합니다.
# I/O 스케줄러 설정
echo none > /sys/block/sda/queue/scheduler
# discard granularity 확인
cat /sys/block/sda/queue/discard_granularity
관리 도구 (f2fs-tools)
f2fs-tools 패키지는 F2FS 파일시스템 생성, 검사, 디버깅을 위한 유저스페이스 도구를 제공합니다:
mkfs.f2fs
# 기본 생성
mkfs.f2fs /dev/sda1
# 주요 옵션
mkfs.f2fs -f \ # 강제 포맷
-l "myvolume" \ # 볼륨 라벨
-O extra_attr,inode_checksum \ # feature 활성화
-O encrypt,verity,compression \ # 보안/압축 feature */
-O casefold \ # 대소문자 무시
-s 1 \ # 섹션 당 세그먼트 수
-z 1 \ # 존 당 섹션 수
-o 5 \ # 오버프로비저닝 비율 (%) */
/dev/sda1
fsck.f2fs
# 검사만 수행
fsck.f2fs /dev/sda1
# 자동 복구
fsck.f2fs -a /dev/sda1 # auto repair
fsck.f2fs -f /dev/sda1 # 강제 전체 검사
fsck.f2fs -p /dev/sda1 # preen (가벼운 검사)
dump.f2fs
# superblock 정보 출력
dump.f2fs /dev/sda1
# 특정 inode 덤프
dump.f2fs -i 3 /dev/sda1 # inode #3 (root dir)
# NAT 영역 덤프
dump.f2fs -n 0~100 /dev/sda1 # NAT 엔트리 0~100
# SIT 영역 덤프
dump.f2fs -s 0~10 /dev/sda1 # SIT 엔트리 0~10
기타 도구
| 도구 | 설명 |
|---|---|
sload.f2fs | 오프라인 상태에서 디렉토리 트리를 F2FS 이미지에 로드 |
resize.f2fs | F2FS 볼륨 크기 조절 (오프라인) |
defrag.f2fs | 파일 단편화 해소 |
f2fs_io | F2FS ioctl 래퍼 (압축, pin_file 등) |
f2fstat | F2FS 통계 모니터링 (debugfs 기반) |
ext4 / Btrfs / XFS 비교
| 항목 | F2FS | ext4 | Btrfs | XFS |
|---|---|---|---|---|
| 설계 철학 | Flash 최적화 LFS | 범용 저널링 | COW B-tree | 고성능 저널링 |
| 쓰기 패턴 | Append-only (out-of-place) | In-place + 저널 | COW (out-of-place) | In-place + WAL |
| 메타데이터 구조 | NAT/SIT/SSA 테이블 | Extent Tree, 비트맵 | COW B-tree | B+tree (AG별) |
| GC 필요성 | 필수 | 불필요 | 불필요 (balance 별도) | 불필요 |
| Flash 최적화 | 네이티브 | 제한적 | 일부 (COW) | 없음 |
| 압축 | LZO/LZ4/ZSTD | 없음 | ZLIB/LZO/ZSTD | 없음 |
| 암호화 | fscrypt (파일 레벨) | fscrypt | 없음 (계획 중) | 없음 |
| 스냅샷 | 없음 | 없음 | 네이티브 | 없음 |
| 최대 볼륨 | 16TB | 1EB | 16EB | 8EB |
| 최대 파일 | 3.94TB | 16TB | 16EB | 8EB |
| 랜덤 쓰기 | 매우 우수 | 보통 | 우수 (COW) | 보통 |
| 순차 읽기 | 우수 | 우수 | 우수 | 매우 우수 |
| 모바일 적합성 | 최적 | 양호 | 부적합 | 부적합 |
| Zoned Storage | 네이티브 | 없음 | 지원 | 없음 |
| 주 사용처 | 모바일, SSD, IoT | 범용 서버/데스크톱 | NAS, 데스크톱 | 엔터프라이즈 서버 |
세그먼트 / 섹션 / 존 구조 심화
F2FS의 공간 관리 계층은 Block → Segment → Section → Zone 4단계로 구성됩니다. 이 계층이 GC, 할당 정책, Zoned Storage 매핑의 기본 단위가 됩니다.
6개 세그먼트 타입
F2FS는 Main Area의 세그먼트를 6가지 타입으로 분류합니다. 각 타입은 전용 current segment(로그 헤드)를 갖습니다:
| 타입 | 상수 | 데이터 종류 | 온도 | GC 특성 |
|---|---|---|---|---|
| HOT_DATA | CURSEG_HOT_DATA | 디렉토리 엔트리 | Hot | 자주 갱신, 빠르게 무효화 |
| WARM_DATA | CURSEG_WARM_DATA | 일반 파일 데이터 | Warm | 중간 갱신 빈도 |
| COLD_DATA | CURSEG_COLD_DATA | 멀티미디어, GC 이동 데이터 | Cold | 거의 갱신 안 됨 |
| HOT_NODE | CURSEG_HOT_NODE | 직접 노드 (inode) | Hot | 메타데이터 변경 시 갱신 |
| WARM_NODE | CURSEG_WARM_NODE | 간접 노드 | Warm | 파일 확장 시 갱신 |
| COLD_NODE | CURSEG_COLD_NODE | 이중 간접 노드 | Cold | 대형 파일에서만 사용 |
관리 단위와 GC 관계
GC는 섹션(section) 단위로 수행됩니다. 하나의 섹션 내 모든 세그먼트의 유효 블록을 이동한 후, 섹션 전체를 free로 반환합니다. Zoned Storage에서는 섹션이 존(zone)과 1:1 매핑되어 zone reset 명령으로 일괄 해제합니다.
/* fs/f2fs/segment.h - 세그먼트/섹션/존 변환 매크로 */
#define GET_SEC_FROM_SEG(sbi, segno) \
((segno) / (sbi)->segs_per_sec)
#define GET_ZONE_FROM_SEC(sbi, secno) \
((secno) / (sbi)->secs_per_zone)
#define GET_SEG_FROM_SEC(sbi, secno) \
((secno) * (sbi)->segs_per_sec)
/* 섹션 내 유효 블록 수 계산 */
static unsigned int get_valid_blocks(
struct f2fs_sb_info *sbi,
unsigned int segno, bool section)
{
if (section) {
unsigned int start = GET_SEG_FROM_SEC(sbi,
GET_SEC_FROM_SEG(sbi, segno));
unsigned int vblocks = 0;
for (int i = 0; i < sbi->segs_per_sec; i++)
vblocks += get_seg_entry(sbi, start + i)->valid_blocks;
return vblocks;
}
return get_seg_entry(sbi, segno)->valid_blocks;
}
mkfs.f2fs의 -s 옵션으로 설정합니다. 기본값은 1(섹션 = 세그먼트)이지만, Zoned Storage에서는 존 크기에 맞춰 자동 조정됩니다. 값이 클수록 GC 단위가 커져 공간 효율이 좋지만, GC 한 번의 I/O 비용도 증가합니다.
다중 헤드 로깅 심화
F2FS의 다중 로그 헤드 설계는 전통적 LFS의 단일 로그 헤드에서 발생하는 hot/cold 데이터 혼합 문제를 해결합니다. 각 로그 헤드는 독립적인 curseg_info 구조체로 관리됩니다.
온도 결정 로직
F2FS는 데이터의 종류와 접근 패턴을 분석하여 온도를 결정합니다:
/* fs/f2fs/segment.c - 데이터 온도 결정 */
static int __get_segment_type(struct f2fs_io_info *fio)
{
if (fio->type == DATA) {
struct inode *inode = fio->page->mapping->host;
/* 디렉토리 데이터 → HOT_DATA */
if (S_ISDIR(inode->i_mode))
return CURSEG_HOT_DATA;
/* GC에 의해 이동된 데이터 → COLD_DATA */
if (is_cold_data(fio->page))
return CURSEG_COLD_DATA;
/* 사용자 힌트 또는 확장자 기반 → COLD_DATA */
if (file_is_cold(inode))
return CURSEG_COLD_DATA;
/* 기본 → WARM_DATA */
return CURSEG_WARM_DATA;
}
/* NODE 타입 분류 */
if (IS_DNODE(fio->page)) {
if (is_cold_node(fio->page))
return CURSEG_WARM_NODE;
return CURSEG_HOT_NODE;
}
return CURSEG_COLD_NODE;
}
Active Log 관리
각 current segment는 curseg_info 구조체에서 다음 쓰기 위치(next_blkoff)를 추적합니다. 세그먼트가 가득 차면(512번째 블록 기록 후) 새 세그먼트를 할당합니다:
/* fs/f2fs/segment.c - 블록 할당 핵심 경로 */
static void do_write_page(struct f2fs_summary *sum,
struct f2fs_io_info *fio)
{
int type = __get_segment_type(fio);
struct curseg_info *curseg = CURSEG_I(sbi, type);
down_read(&sbi->sm_info->curseg_lock);
mutex_lock(&curseg->curseg_mutex);
/* 현재 세그먼트의 다음 빈 블록에 기록 */
__allocate_new_block(sbi, curseg, type);
/* 요약 정보 기록 (SSA용) */
__add_sum_entry(sbi, type, sum, curseg->next_blkoff);
/* 세그먼트가 가득 찼으면 새 세그먼트로 교체 */
if (curseg->next_blkoff >= sbi->blocks_per_seg)
allocate_segment_by_default(sbi, type, false);
mutex_unlock(&curseg->curseg_mutex);
up_read(&sbi->sm_info->curseg_lock);
}
GC 알고리즘 심화
F2FS의 GC는 Foreground GC(FG_GC)와 Background GC(BG_GC) 두 가지 모드를 운용합니다. 각 모드는 서로 다른 대상 선택 정책을 사용하며, 공간 긴급도에 따라 자동 전환됩니다.
Cost-Benefit 분석 상세
Background GC의 핵심은 비용-이득 분석(Cost-Benefit Analysis)입니다. 단순히 유효 블록이 적은 세그먼트를 선택하는 것이 아니라, 세그먼트의 나이도 함께 고려합니다:
/* fs/f2fs/gc.c - Cost-Benefit 비용 계산 */
static unsigned int get_cb_cost(struct f2fs_sb_info *sbi,
unsigned int segno)
{
struct sit_info *sit_i = SIT_I(sbi);
unsigned int secno = GET_SEC_FROM_SEG(sbi, segno);
unsigned int start = GET_SEG_FROM_SEC(sbi, secno);
unsigned long long mtime = 0;
unsigned int vblocks;
unsigned int usable_segs = f2fs_usable_segs_in_sec(sbi, segno);
/* 섹션 내 모든 세그먼트의 평균 수정 시간 계산 */
for (unsigned int i = 0; i < usable_segs; i++)
mtime += get_seg_entry(sbi, start + i)->mtime;
mtime = div_u64(mtime, usable_segs);
/* 유효 블록 수 */
vblocks = get_valid_blocks(sbi, segno, true);
/* 사용률 u = vblocks / total_blocks */
/* age = current_time - mtime */
/* cost = (1-u) / (2*u) * age (값이 높을수록 GC에 유리) */
if (vblocks == 0)
return UINT_MAX; /* 유효 블록 없음 → 최우선 */
return div_u64(mtime * (usable_segs * sbi->blocks_per_seg - vblocks),
2 * vblocks);
}
GC Urgent 모드
Android 환경에서는 IDLE 상태일 때 공격적으로 GC를 수행하여 향후 쓰기 성능을 확보합니다:
| gc_urgent 값 | 모드 | 동작 | 사용 시나리오 |
|---|---|---|---|
| 0 | 일반 | 기본 BG GC | 일반 운용 |
| 1 | 저속 긴급 | BG GC 주기 단축 | 약간의 여유 공간 확보 |
| 2 | 고속 긴급 | FG GC 수준의 공격적 GC | Android IDLE maintenance |
| 3 | 최고속 | sleep 없이 연속 GC | 긴급 공간 확보 |
/* fs/f2fs/gc.c - GC 스레드 메인 루프 */
static int gc_thread_func(void *data)
{
struct f2fs_sb_info *sbi = data;
struct f2fs_gc_kthread *gc_th = sbi->gc_thread;
wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head;
do {
wait_event_interruptible_timeout(*wq,
kthread_should_stop() || freezing(current) ||
waitqueue_active(wq),
msecs_to_jiffies(gc_th->min_sleep_time));
/* urgent 모드에 따라 GC 강도 조절 */
if (sbi->gc_mode == GC_URGENT_HIGH)
gc_control.init_gc_type = FG_GC; /* 공격적 */
else
gc_control.init_gc_type = BG_GC; /* 일반 */
f2fs_gc(sbi, &gc_control);
} while (!kthread_should_stop());
return 0;
}
NAT / SIT 구조 심화
NAT와 SIT는 F2FS 메타데이터 관리의 두 축입니다. 둘 다 이중 버퍼링과 저널(journal) 메커니즘을 사용하여 체크포인트 빈도를 줄이고 성능을 개선합니다.
NAT 저널 상세
NAT 저널은 체크포인트 팩 내에 소규모 dirty NAT 엔트리를 직접 저장하는 최적화입니다. dirty 엔트리 수가 저널 용량을 초과하면 NAT 영역에 flush합니다:
/* include/linux/f2fs_fs.h - NAT 저널 구조 */
struct f2fs_nat_journal {
struct nat_journal_entry entries[NAT_JOURNAL_ENTRIES];
__u8 reserved[NAT_JOURNAL_RESERVED];
} __packed;
struct nat_journal_entry {
__le32 nid; /* 노드 ID */
struct f2fs_nat_entry ne; /* NAT 엔트리 (version, ino, block_addr) */
} __packed;
/* NAT_JOURNAL_ENTRIES = ~480 엔트리
* dirty NAT 엔트리가 이 수를 초과하면
* NAT 영역에 직접 기록 (flush) */
SIT 저널 상세
/* include/linux/f2fs_fs.h - SIT 저널 구조 */
struct f2fs_sit_journal {
struct sit_journal_entry entries[SIT_JOURNAL_ENTRIES];
__u8 reserved[SIT_JOURNAL_RESERVED];
} __packed;
struct sit_journal_entry {
__le32 segno; /* 세그먼트 번호 */
struct f2fs_sit_entry se; /* SIT 엔트리 (vblocks, valid_map, mtime) */
} __packed;
/* SIT_JOURNAL_ENTRIES = ~6 엔트리
* SIT는 저널 용량이 작으므로 대부분 flush됨
* NAT보다 flush 빈도가 높음 */
체크포인트 심화
F2FS의 체크포인트는 파일시스템의 일관된 스냅샷을 디스크에 기록하는 핵심 동작입니다. CP 팩의 구성, 복구 흐름, fsync 최적화를 상세히 살펴봅니다.
CP 팩 구조 상세
CP 팩은 여러 블록으로 구성되며, 첫 블록(header)과 마지막 블록(footer)에 동일한 f2fs_checkpoint 구조체가 기록됩니다. footer의 체크섬이 원자적 커밋 포인트 역할을 합니다:
/* fs/f2fs/checkpoint.c - CP 기록 순서 */
static int do_checkpoint(struct f2fs_sb_info *sbi,
struct cp_control *cpc)
{
struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
/* 1. 모든 dirty 페이지 flush */
f2fs_flush_merged_writes(sbi);
/* 2. orphan inode 목록 기록 */
write_orphan_inodes(sbi, sbi->cur_cp_pack);
/* 3. NAT/SIT 저널 또는 flush */
flush_nat_entries(sbi, cpc);
flush_sit_entries(sbi, cpc);
/* 4. current segment 요약 블록 기록 */
write_data_summaries(sbi, sbi->cur_cp_pack);
write_node_summaries(sbi, sbi->cur_cp_pack);
/* 5. CP header 기록 (checkpoint_ver++) */
ckpt->checkpoint_ver = cpu_to_le64(
le64_to_cpu(ckpt->checkpoint_ver) + 1);
/* 6. CP footer 기록 + checksum → 커밋 포인트! */
commit_checkpoint(sbi, ckpt, sbi->cur_cp_pack);
/* 7. 다음 CP는 반대쪽 팩에 기록 */
sbi->cur_cp_pack ^= 1;
return 0;
}
fsync 최적화
전통적 방식에서 fsync()는 전체 체크포인트를 트리거하지만, F2FS는 노드 레벨 fsync 마킹으로 이를 최적화합니다:
/* fs/f2fs/file.c - fsync 최적화 */
static int f2fs_do_sync_file(struct file *file,
loff_t start, loff_t end, int datasync)
{
struct inode *inode = file->f_mapping->host;
/* CP 이후 변경이 없으면 바로 반환 */
if (!f2fs_is_volatile_file(inode) &&
!f2fs_need_inode_block_update(sbi, inode->i_ino))
return 0;
/* 방법 1: CP 이후 변경량이 적으면 노드만 기록 */
if (need_node_only_sync(inode)) {
f2fs_write_inode(inode, NULL);
f2fs_set_fsync_mark(inode); /* FADVISE_FSYNC_BIT 설정 */
f2fs_issue_flush(sbi);
return 0;
}
/* 방법 2: 변경량이 크면 전체 체크포인트 */
return f2fs_write_checkpoint(sbi, CP_SYNC_FLAG);
}
checkpoint_merge 마운트 옵션은 이런 동시 CP 요청을 하나로 병합하여 디스크 I/O를 크게 줄입니다. 이 최적화 덕분에 앱 설치, SQLite 쓰기 등의 성능이 10~30% 개선됩니다.
압축 모드 심화
F2FS의 투명 압축은 클러스터(cluster) 단위로 수행됩니다. 각 클러스터는 독립적으로 압축/해제되어 랜덤 읽기 성능에 미치는 영향을 최소화합니다.
압축 컨텍스트 (compress_ctx)
압축의 핵심 자료구조인 compress_ctx는 한 클러스터의 압축/해제 과정을 관리합니다:
/* fs/f2fs/f2fs.h - 압축 컨텍스트 */
struct compress_ctx {
struct inode *inode; /* 대상 inode */
pgoff_t cluster_idx; /* 클러스터 인덱스 */
unsigned int cluster_size; /* 클러스터 블록 수 (2^N) */
unsigned int log_cluster_size; /* log2(cluster_size) */
struct page **rpages; /* 원본(raw) 페이지 배열 */
unsigned int nr_rpages; /* 원본 페이지 수 */
struct page **cpages; /* 압축(compressed) 페이지 배열 */
unsigned int nr_cpages; /* 압축 페이지 수 */
void *rbuf; /* 원본 데이터 버퍼 */
void *cbuf; /* 압축 데이터 버퍼 */
size_t rlen; /* 원본 크기 */
size_t clen; /* 압축 크기 */
};
압축 읽기 경로
압축된 클러스터를 읽을 때는 디스크에서 압축 블록을 읽은 후, 메모리에서 해제하여 원본 데이터를 복원합니다:
/* fs/f2fs/compress.c - 압축 해제 읽기 */
static int f2fs_decompress_pages(struct decompress_io_ctx *dic)
{
const struct f2fs_compress_ops *cops =
f2fs_cops[F2FS_I(dic->inode)->i_compress_algorithm];
/* 압축 블록을 연속 버퍼에 모으기 */
f2fs_decompress_gather(dic);
/* 알고리즘별 해제 수행 */
ret = cops->decompress_pages(dic);
if (ret) {
f2fs_err(sbi, "cluster %lu decompression failed: %d",
dic->cluster_idx, ret);
return ret;
}
/* 체크섬 검증 (compress_chksum 옵션) */
if (F2FS_I(dic->inode)->i_flags & F2FS_COMPR_FL)
f2fs_verify_compress_chksum(dic);
return 0;
}
ZNS SSD 지원 심화
ZNS(Zoned Namespaces) SSD는 데이터를 존(zone) 단위로 관리하며, 각 존은 순차 쓰기만 허용합니다. F2FS의 로그 구조 설계는 이 제약과 자연스럽게 호환됩니다.
존-섹션 매핑
F2FS는 ZNS SSD의 각 존을 하나의 섹션(section)에 1:1 매핑합니다. 이 매핑의 핵심은 segs_per_sec을 존 용량에 맞게 설정하는 것입니다:
/* fs/f2fs/super.c - ZNS 디바이스 초기화 */
static int f2fs_init_zoned_device(struct f2fs_sb_info *sbi)
{
struct request_queue *q = bdev_get_queue(sbi->sb->s_bdev);
sector_t zone_sectors = bdev_zone_sectors(sbi->sb->s_bdev);
/* 존 크기 = segs_per_sec * 세그먼트 크기
* 예: 256MB zone → segs_per_sec = 256MB / 2MB = 128 */
sbi->segs_per_sec = zone_sectors >>
(sbi->log_blocksize - 9 + sbi->log_blocks_per_seg);
/* mode=lfs 강제 (순차 쓰기만 허용) */
set_opt(sbi, LFS);
/* 존 타입 배열 초기화 */
f2fs_report_zones(sbi);
return 0;
}
GC와 Zone Reset
ZNS SSD에서 GC는 특별한 의미를 갖습니다. 기존 SSD에서는 GC 후 무효 블록을 TRIM하지만, ZNS에서는 존 전체를 zone reset하여 쓰기 포인터를 초기화합니다:
/* fs/f2fs/segment.c - 섹션 해제 시 zone reset */
static void f2fs_reset_zone(struct f2fs_sb_info *sbi,
unsigned int secno)
{
sector_t zone_start = sec_start_blkaddr(sbi, secno)
<< (sbi->log_blocksize - 9);
sector_t zone_len = (sector_t)sbi->segs_per_sec
<< (sbi->log_blocks_per_seg + sbi->log_blocksize - 9);
/* 존 리셋: 쓰기 포인터를 존 시작으로 초기화 */
blkdev_zone_mgmt(sbi->sb->s_bdev, REQ_OP_ZONE_RESET,
zone_start, zone_len, GFP_NOFS);
}
커널 내부 자료구조 심화
F2FS의 인메모리 자료구조는 f2fs_sb_info를 중심으로 세그먼트 매니저, 노드 매니저, GC 스레드가 계층적으로 연결됩니다.
f2fs_sb_info 초기화
마운트 시 f2fs_fill_super()에서 f2fs_sb_info를 할당하고 각 서브시스템을 초기화합니다:
/* fs/f2fs/super.c - 마운트 초기화 */
static int f2fs_fill_super(struct super_block *sb,
void *data, int silent)
{
struct f2fs_sb_info *sbi;
/* 1. f2fs_sb_info 할당 */
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
sb->s_fs_info = sbi;
sbi->sb = sb;
/* 2. 슈퍼블록 읽기 & 검증 */
read_raw_super_block(sbi, &raw_super, &valid_super_block);
/* 3. 체크포인트 읽기 */
f2fs_get_valid_checkpoint(sbi);
/* 4. 세그먼트 매니저 초기화 */
f2fs_build_segment_manager(sbi); /* SIT, free_info, curseg */
/* 5. 노드 매니저 초기화 */
f2fs_build_node_manager(sbi); /* NAT 캐시, free NID */
/* 6. GC 스레드 시작 */
f2fs_start_gc_thread(sbi);
/* 7. roll-forward 복구 */
f2fs_recover_fsync_data(sbi, false);
return 0;
}
f2fs_inode_info 캐시
F2FS는 VFS inode에 F2FS 전용 필드를 추가한 f2fs_inode_info를 사용합니다. slab 캐시(f2fs_inode_cachep)로 관리되며, container_of 매크로로 VFS inode에서 변환합니다:
/* fs/f2fs/f2fs.h */
static inline struct f2fs_inode_info *F2FS_I(
struct inode *inode)
{
return container_of(inode, struct f2fs_inode_info, vfs_inode);
}
/* 주요 F2FS inode 플래그 */
#define FI_INLINE_DATA 0x0001 /* 인라인 데이터 */
#define FI_INLINE_DENTRY 0x0002 /* 인라인 디렉토리 */
#define FI_ATOMIC_FILE 0x0004 /* atomic write 진행 중 */
#define FI_VOLATILE_FILE 0x0008 /* volatile 파일 */
#define FI_COMPRESS_ENABLED 0x0010 /* 압축 활성화 */
#define FI_PIN_FILE 0x0020 /* GC 이동 금지 */
/sys/fs/f2fs/<dev>/ram_thresh 파라미터(기본 10%)로 전체 메모리 대비 F2FS 캐시 사용량 상한을 제어할 수 있습니다. 모바일 기기에서는 이 값을 낮게 설정하여 메모리 압박을 줄이기도 합니다.
ftrace / bpftrace F2FS 추적
F2FS는 풍부한 tracepoint를 제공하여 GC, 체크포인트, 세그먼트 할당 등 내부 동작을 실시간으로 추적할 수 있습니다.
주요 tracepoint 목록
| tracepoint | 설명 | 주요 필드 |
|---|---|---|
f2fs:f2fs_gc_begin | GC 시작 | gc_type, no_bg_gc, nr_free_secs |
f2fs:f2fs_gc_end | GC 종료 | ret, seg_freed, sec_freed |
f2fs:f2fs_write_checkpoint | 체크포인트 시작 | reason, msg |
f2fs:f2fs_submit_page_bio | 페이지 BIO 제출 | ino, blkaddr, type |
f2fs:f2fs_submit_write_bio | 쓰기 BIO 제출 | type, size |
f2fs:f2fs_new_inode | 새 inode 생성 | ino, dir_ino |
f2fs:f2fs_unlink_enter | 파일 삭제 시작 | ino, name |
f2fs:f2fs_truncate | 파일 잘라내기 | ino, size, blocks |
f2fs:f2fs_sync_file_enter | fsync 시작 | ino, datasync |
f2fs:f2fs_sync_file_exit | fsync 완료 | ino, need_cp, ret |
f2fs:f2fs_readpage | 페이지 읽기 | ino, index, blkaddr |
f2fs:f2fs_do_write_data_page | 데이터 쓰기 | ino, index, blkaddr |
f2fs:f2fs_compress_pages_start | 압축 시작 | ino, cluster_idx |
f2fs:f2fs_decompress_pages | 압축 해제 | ino, cluster_idx |
ftrace 사용 예시
# GC 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_gc_begin/enable
echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_gc_end/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 체크포인트 추적
echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_write_checkpoint/enable
# fsync 성능 분석
echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable
echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable
# 전체 F2FS 이벤트 활성화
echo 1 > /sys/kernel/debug/tracing/events/f2fs/enable
# 추적 중지 및 결과 저장
echo 0 > /sys/kernel/debug/tracing/events/f2fs/enable
cat /sys/kernel/debug/tracing/trace > /tmp/f2fs_trace.txt
bpftrace 고급 추적
# GC 소요 시간 히스토그램
bpftrace -e '
tracepoint:f2fs:f2fs_gc_begin { @start[tid] = nsecs; }
tracepoint:f2fs:f2fs_gc_end /@start[tid]/ {
@gc_latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
'
# fsync 호출 빈도 (프로세스별)
bpftrace -e '
tracepoint:f2fs:f2fs_sync_file_enter {
@fsync_count[comm] = count();
}
interval:s:5 { print(@fsync_count); clear(@fsync_count); }
'
# 체크포인트 사유 추적
bpftrace -e '
tracepoint:f2fs:f2fs_write_checkpoint {
printf("%s CP reason=%d\n", strftime("%H:%M:%S", nsecs), args->reason);
}
'
# 세그먼트 할당 패턴 분석
bpftrace -e '
kprobe:f2fs_allocate_data_block {
@alloc_type[arg3] = count(); /* segment type */
}
interval:s:10 { print(@alloc_type); clear(@alloc_type); }
'
debugfs 통계
F2FS는 /sys/kernel/debug/f2fs/에 상세 통계를 노출합니다:
# F2FS 전체 통계
cat /sys/kernel/debug/f2fs/status
# 출력 예시:
# [f2fs: sda1]
# - Main Area: 16384 segments, 2048 sections, 256 zones
# - valid blocks: 1234567 / 8388608
# - dirty segments: 42
# - free segments: 4096
# - node pages: 12345 (dirty: 67)
# - data pages: 23456 (dirty: 89)
# - GC calls: 128 (BG: 120, FG: 8)
# - CP calls: 256 (total: 512s)
# - extent cache: hit=98.5% miss=1.5%
# I/O 통계 (iostat 마운트 옵션 필요)
cat /sys/kernel/debug/f2fs/<dev>/iostat_info
f2fs_gc_begin/end tracepoint와 함께 /sys/fs/f2fs/<dev>/gc_urgent, free_segments 값을 모니터링하세요. FG_GC 빈도가 높으면 over-provisioning 비율 증가 또는 gc_urgent 모드 활용을 검토합니다.
성능 튜닝 심화
F2FS는 다양한 마운트 옵션, sysfs 파라미터, mkfs 설정을 통해 워크로드에 맞춘 미세 조정이 가능합니다.
오버프로비저닝(OVP) 분석
오버프로비저닝은 GC가 동작할 여유 공간을 확보합니다. OVP 비율이 높을수록 GC 빈도가 줄어 쓰기 증폭이 감소하지만, 사용 가능 용량이 줄어듭니다:
| OVP 비율 | 사용 가능 용량 | GC 빈도 | 쓰기 증폭 | 권장 환경 |
|---|---|---|---|---|
| 3% (기본) | 97% | 높음 | 높음 | 용량 우선 |
| 5% | 95% | 중간 | 중간 | 일반 모바일 |
| 10% | 90% | 낮음 | 낮음 | 수명 우선 |
| 20% | 80% | 매우 낮음 | 매우 낮음 | 고성능 SSD |
# OVP 5%로 포맷
mkfs.f2fs -f -o 5 /dev/sda1
# 현재 OVP 세그먼트 확인
cat /sys/fs/f2fs/sda1/ovp_segments
# 예약 세그먼트 확인 (GC 최소 여유)
cat /sys/fs/f2fs/sda1/reserved_blocks
워크로드별 최적 설정
| 워크로드 | 마운트 옵션 | sysfs 조정 | 설명 |
|---|---|---|---|
| Android /data | noatime,checkpoint_merge,compress_algorithm=lz4,active_logs=6 | gc_urgent=2 (IDLE 시) | SQLite fsync, 소형 파일 최적화 |
| 데이터베이스 | noatime,fsync_mode=strict,active_logs=6 | gc_min_sleep_time=10000 | 데이터 일관성 우선 |
| 미디어 저장소 | noatime,compress_algorithm=zstd,compress_log_size=5 | gc_urgent=0 | 대형 파일, 공간 절약 |
| IoT 임베디드 | noatime,mode=lfs,active_logs=2 | ram_thresh=5 | 제한된 RAM, 순차 쓰기 |
| ZNS SSD | noatime,mode=lfs | (자동) | 존 매핑 자동 설정 |
성능 모니터링 체크리스트
#!/bin/bash - F2FS 성능 진단 스크립트
DEV=sda1
# 1. 여유 공간 상태
FREE=$(cat /sys/fs/f2fs/$DEV/free_segments)
DIRTY=$(cat /sys/fs/f2fs/$DEV/dirty_segments)
OVP=$(cat /sys/fs/f2fs/$DEV/ovp_segments)
echo "Free=$FREE Dirty=$DIRTY OVP=$OVP"
# 2. GC 상태
echo "GC urgent: $(cat /sys/fs/f2fs/$DEV/gc_urgent)"
# 3. 누적 기록량 (수명 추정)
WRITTEN=$(cat /sys/fs/f2fs/$DEV/lifetime_write_kbytes)
echo "Lifetime written: $((WRITTEN / 1048576)) GB"
# 4. extent cache 적중률
cat /sys/kernel/debug/f2fs/status | grep "extent"
# 5. BG/FG GC 횟수
cat /sys/kernel/debug/f2fs/status | grep "GC"
# 6. 경고: 여유 세그먼트가 OVP의 2배 미만이면 위험
if [ $FREE -lt $((OVP * 2)) ]; then
echo "WARNING: 여유 공간 부족! FG_GC 빈발 가능"
echo " → gc_urgent=2 설정 또는 불필요 파일 삭제 권장"
fi
discard 마운트 옵션은 블록 해제 시 즉시 TRIM을 발생시켜 I/O 지연이 발생할 수 있습니다. 성능이 중요한 환경에서는 nodiscard로 마운트하고, 주기적으로 fstrim을 실행(예: 주 1회)하는 것이 효율적입니다. Android에서는 IDLE maintenance 작업에서 fstrim을 수행합니다.
쓰기 증폭(WAF) 분석
쓰기 증폭(Write Amplification Factor)은 호스트가 요청한 쓰기량 대비 실제 디스크에 기록된 양의 비율입니다. F2FS에서 WAF를 측정하고 최적화하는 방법을 살펴봅니다:
# WAF 측정
# 호스트 쓰기량 (F2FS가 측정한 누적 기록량)
HOST_WRITTEN=$(cat /sys/fs/f2fs/sda1/lifetime_write_kbytes)
# 디바이스 쓰기량 (SSD SMART 데이터)
NAND_WRITTEN=$(smartctl -A /dev/sda | grep "Total_LBAs_Written" | awk '{print $10}')
# WAF = NAND 쓰기량 / 호스트 쓰기량
# 이상적 WAF = 1.0, 일반적 F2FS WAF = 1.1~2.5
# F2FS 내부 쓰기 유형별 통계
cat /sys/kernel/debug/f2fs/status | grep -A 20 "IO"
# 출력 예시:
# - data write: 1234567 blocks
# - node write: 234567 blocks
# - meta write: 34567 blocks
# - gc data write: 45678 blocks ← GC에 의한 추가 쓰기
# - gc node write: 12345 blocks ← GC에 의한 추가 쓰기
WAF 감소 전략
| 전략 | 설정 | 효과 | 비용 |
|---|---|---|---|
| OVP 증가 | mkfs.f2fs -o 10 | GC 빈도 30~50% 감소 | 사용 가능 용량 10% 감소 |
| active_logs=6 | 마운트 옵션 | hot/cold 분리로 GC 효율 향상 | RAM 약간 증가 |
| checkpoint_merge | 마운트 옵션 | CP I/O 40~60% 감소 | 약간의 fsync 지연 |
| 압축 활성화 | compress_algorithm=lz4 | 쓰기량 20~50% 감소 | CPU 사용량 증가 |
| noatime | 마운트 옵션 | 읽기 시 불필요한 쓰기 제거 | atime 정보 미갱신 |
| BG GC 최적화 | gc_urgent=2 (IDLE) | FG GC 빈도 감소 | IDLE 시 I/O 증가 |
Android 환경 튜닝 가이드
Android 기기에서 F2FS 성능을 최적화할 때 고려해야 할 주요 사항입니다:
# Android /data 파티션 권장 마운트 옵션 (Android 13+)
/dev/block/by-name/userdata /data f2fs \
noatime,nosuid,nodev,\
discard,\
fsync_mode=nobarrier,\
reserve_root=32768,\
resgid=1065,\
inlinecrypt,\
checkpoint_merge,\
gc_merge,\
compress_algorithm=lz4,\
compress_log_size=3,\
compress_extension=*.apk,\
compress_extension=*.dex,\
compress_extension=*.so,\
atgc,\
age_extent_cache
# ATGC (Age-Threshold-based GC) 파라미터
# Android에서 GC 성능 개선을 위한 확장
echo 600 > /sys/fs/f2fs/sda1/atgc_candidate_ratio
echo 25 > /sys/fs/f2fs/sda1/atgc_candidate_count
echo 1073741824 > /sys/fs/f2fs/sda1/atgc_age_weight
echo 60 > /sys/fs/f2fs/sda1/atgc_age_threshold
atgc 마운트 옵션으로 활성화하며, Android 12+에서 기본 적용됩니다.
SSD 수명 추정
F2FS의 누적 기록량 정보를 활용하여 SSD 수명을 추정할 수 있습니다:
#!/bin/bash - SSD 수명 추정 스크립트
DEV=sda1
ENDURANCE_TB=600 # SSD TBW 스펙 (테라바이트)
# 누적 기록량 (KB)
WRITTEN_KB=$(cat /sys/fs/f2fs/$DEV/lifetime_write_kbytes)
WRITTEN_TB=$(echo "scale=2; $WRITTEN_KB / 1073741824" | bc)
# 사용률
USED_PCT=$(echo "scale=1; $WRITTEN_TB * 100 / $ENDURANCE_TB" | bc)
echo "누적 기록: ${WRITTEN_TB} TB"
echo "SSD TBW: ${ENDURANCE_TB} TB"
echo "수명 소모: ${USED_PCT}%"
# 마운트 시간 (F2FS 내부 시계)
MOUNT_TIME=$(cat /sys/fs/f2fs/$DEV/mounted_time_sec)
DAYS=$((MOUNT_TIME / 86400))
DAILY_GB=$(echo "scale=1; $WRITTEN_KB / 1048576 / $DAYS" | bc)
echo "일 평균 기록: ${DAILY_GB} GB/day"
# 남은 수명 추정
REMAIN_TB=$(echo "scale=2; $ENDURANCE_TB - $WRITTEN_TB" | bc)
REMAIN_DAYS=$(echo "scale=0; $REMAIN_TB * 1048576 / $DAILY_GB" | bc 2>/dev/null)
echo "예상 잔여 수명: 약 ${REMAIN_DAYS} 일"
I/O 패턴 분석과 최적화
F2FS의 I/O 통계를 분석하여 워크로드 특성을 파악하고 최적화 방향을 결정합니다:
# iostat 활성화 (마운트 옵션에 iostat 추가)
mount -o remount,iostat /dev/sda1 /data
# I/O 통계 확인
cat /sys/kernel/debug/f2fs/sda1/iostat_info
# 주요 관찰 항목:
# - app_dio_read / app_dio_write : Direct I/O
# - app_bio_read / app_bio_write : Buffered I/O
# - fs_gc_data_io / fs_gc_node_io : GC I/O
# - fs_cp_data_io / fs_cp_node_io : CP I/O
# GC I/O 비율이 높으면 → OVP 증가 또는 gc_urgent 조정
# CP I/O 비율이 높으면 → checkpoint_merge 활성화
# DIO 비율이 높으면 → 버퍼 캐시 효율 점검
# 세그먼트 사용 패턴 확인
cat /sys/kernel/debug/f2fs/status | grep -E "segment|utilization"
# 높은 utilization(>80%)은 FG_GC 빈발의 원인
일반적인 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| 쓰기 지연 스파이크 | FG_GC 발생 | OVP 증가, IDLE 시 gc_urgent=2 |
| 마운트 시간 길어짐 | Roll-forward 복구 | 정상 unmount 보장, fsck 수행 |
| 공간 부족 (df에는 여유) | 예약 블록/OVP | reserve_root 조정 |
| 높은 CPU 사용 | 압축/해제 부하 | LZ4 선택 또는 압축 비활성화 |
| fsync 느림 | 매번 CP 트리거 | checkpoint_merge, fsync_mode 조정 |
| 디스크 수명 급감 | 높은 WAF | active_logs=6, noatime, 압축 |
| extent cache miss 높음 | 단편화 심각 | defrag.f2fs 실행 |
| OOM 발생 | F2FS 캐시 과다 | ram_thresh 감소 (기본 10%) |
벤치마크 비교 참고
| 테스트 | F2FS (기본) | F2FS (최적화) | ext4 | 비고 |
|---|---|---|---|---|
| 랜덤 4K 쓰기 (IOPS) | ~45,000 | ~52,000 | ~18,000 | F2FS 압도적 우위 |
| 랜덤 4K 읽기 (IOPS) | ~48,000 | ~50,000 | ~47,000 | 비슷 |
| 순차 쓰기 (MB/s) | ~420 | ~445 | ~460 | ext4 약간 우위 |
| 순차 읽기 (MB/s) | ~480 | ~485 | ~490 | 비슷 |
| SQLite insert (TPS) | ~1,200 | ~1,800 | ~600 | F2FS 최적화 큰 효과 |
| SQLite update (TPS) | ~900 | ~1,400 | ~450 | atomic write 효과 |
| 파일 생성 (files/s) | ~8,500 | ~9,200 | ~5,500 | 인라인 데이터 효과 |
| 마운트 시간 (ms) | ~120 | ~100 | ~80 | ext4 약간 빠름 |
fio, dbench, filebench 등으로 직접 측정하세요.
관련 문서
F2FS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.