전에 F2FS 메타데이터 구조를 분석하면서 512MB 볼륨은 오버프로비저닝(GC 예약 공간) 비율이 자동으로 10%까지 붙는다는 걸 실측했다. “그럼 -o 값을 직접 낮게 지정하면 그만큼 실사용 공간이 늘어나겠지”라고 생각하기 쉬운데, 실제로 mkfs.f2fs 소스를 읽고 값을 하나씩 바꿔 가며 재보니 그 직관이 항상 맞지는 않았다. 오히려 값을 너무 낮추면 실사용 공간이 줄어드는 구간이 있고, 그 글에서는 다루지 않았던 -i 옵션으로 구조적 예약(NAT) 자체를 줄이는 별도의 축도 있다. 이 글에서는 두 레버를 실제로 조작해 보고 진짜 최적점이 어디인지, 그 대가는 무엇인지 정리한다.
오버프로비저닝을 낮추면 무조건 늘어날까
1GB 루프백 이미지에 -o 값을 10%부터 1%까지 바꿔가며 포맷하고, dump.f2fs로 실제 예약된 세그먼트 수를 확인했다.
for o in 10 5 3 2 1; do
truncate -s 1G otest_$o.bin
mkfs.f2fs -f -o $o otest_$o.bin
dump.f2fs -d 1 otest_$o.bin | grep -E "rsvd_segment_count|overprov_segment_count"
done| -o 지정값 | overprov_segment_count | 실사용 가능(main 502세그 기준) |
|---|---|---|
| 10% | 50 | 904 MB |
| 5% (auto가 고른 5.37%와 근접) | 29 | 946 MB |
| 3% | 42 | 920 MB |
| 2% | 59 | 886 MB |
| 1% | 109 | 786 MB |
1%까지 낮췄더니 오히려 예약 세그먼트가 109개로 가장 많이 잡혔다. 원인은 f2fs-tools의 계산식 자체에 있다.
c.reserved_segments =
(2 * (100 / c.overprovision + 1) + 6)
* c.segs_per_sec;100 / c.overprovision 항 때문에 지정 비율이 작을수록 예약 세그먼트가 반비례로 커진다. GC가 언제든 최소한의 여유를 보장받으려면, 버퍼 비율 자체가 작을수록 절대량으로는 더 많은 안전 마진이 필요하다는 논리다. 그래서 get_best_overprovision()이 자동으로 고르는 값(1GB에서 5.37%)은 임의의 기본값이 아니라, 실사용 공간을 최대화하는 지점을 실제로 탐색한 결과다 — 위 표에서도 5% 근방이 가장 넓은 실사용 공간(946MB)을 낸다. -o를 손으로 더 낮춘다고 항상 이득은 아니라는 뜻이다.
-i: NAT 예약을 줄이는 또 다른 축
mkfs.f2fs -i는 “extended node bitmap”을 켜고 NAT 크기를 계산할 때 “메인 영역 블록의 100%가 전부 노드가 될 수 있다”는 기본 가정 대신 20% 비율로 계산한다. 그 글에서 “구조적 예약분은 재포맷 없이 줄일 수 없다”고 썼는데, 정확히는 “기본 옵션으로는” 줄일 수 없는 것이고 -i라는 mkfs 시점 레버가 따로 있었다.
for sz in 512M 1G 2G 4G 8G; do
truncate -s $sz d_$sz.bin; truncate -s $sz i_$sz.bin
mkfs.f2fs -f d_$sz.bin
mkfs.f2fs -f -i i_$sz.bin
dump.f2fs -d 1 d_$sz.bin | grep segment_count_nat
dump.f2fs -d 1 i_$sz.bin | grep segment_count_nat
done| 볼륨 크기 | NAT 세그먼트(기본) | NAT 세그먼트(-i) | 실사용 공간 증가분 |
|---|---|---|---|
| 512 MB | 2 | 2 | +0 MB (이미 최소치) |
| 1 GB | 4 | 2 | +4 MB (+0.42%) |
| 2 GB | 6 | 2 | +6 MB (+0.29%) |
| 4 GB | 10 | 2 | +16 MB (+0.39%) |
| 8 GB | 18 | 2 | +30 MB (+0.37%) |
테스트한 모든 크기에서 -i는 NAT을 최소치인 세그먼트 2개로 밀어붙인다. 다만 볼륨 대비 증가폭은 0.3~0.4% 수준이라, 앞서 본 오버프로비저닝(수 % 단위)에 비하면 훨씬 작은 레버다.
-i의 실제 비용: inode 상한
NAT을 줄인다는 건 노드(inode 포함) ID를 담을 수 있는 슬롯 자체가 줄어든다는 뜻이다. 실제로 마운트해서 df -i로 확인했다.
sudo modprobe f2fs
sudo mount -o loop d_8G.bin mnt_d
sudo mount -o loop i_8G.bin mnt_i
df -i mnt_d mnt_iFilesystem Inodes IUsed IFree IUse% Mounted on
/dev/loop6 2046464 2 2046462 1% mnt_d (기본, 8G)
/dev/loop24 232957 1 232956 1% mnt_i (-i, 8G)
| 볼륨 크기 | inode 상한(기본) | inode 상한(-i) | 감소율 |
|---|---|---|---|
| 512 MB | 114,176 | 114,176 | 0% |
| 1 GB | 243,200 | 232,957 | -4.2% |
| 2 GB | 499,200 | 232,957 | -53.3% |
| 4 GB | 1,013,248 | 232,957 | -77.0% |
| 8 GB | 2,046,464 | 232,957 | -88.6% |
기본은 볼륨 크기에 비례해 inode 상한이 계속 늘어나지만, -i는 약 233,000개에서 고정된 상한으로 굳는다. 8GB 볼륨에서 얻는 건 0.37%의 여유 공간(30MB)인데, 잃는 건 inode 상한의 88.6%다. 작은 파일이 많은 워크로드(패키지 캐시, 사진 라이브러리, 소스 트리 등)라면 데이터 공간은 남아도 inode부터 고갈될 수 있다.
실전: 최적 조합 찾기
-i를 켠 상태에서도 -o를 auto보다 손으로 조정하면 같은 함정에 빠지는지 1GB에서 다시 확인했다.
| -i + -o | overprov_segment_count | 실사용 가능(main 504세그 기준) |
|---|---|---|
| auto (5.36%) | 27 | 954 MB |
| 8% | 40 | 928 MB |
| 6% | 30 | 948 MB |
| 5% | 29 | 950 MB |
| 4% | 34 | 940 MB |
-i를 더해도 auto 지점이 여전히 가장 넓은 실사용 공간을 낸다. 결국 실전 절차는 “무조건 낮추기”가 아니라 다음 순서에 가깝다.
- 워크로드의 파일 개수가 많지 않다고 확신되면
-i를 추가한다 (수백 MB급 볼륨에서는 그마저도 효과가 없으니dump.f2fs로 먼저 확인). -o는 값을 지정하지 않고 auto에 맡기거나, 굳이 조정하려면 auto 근방 ±1~2%p만dump.f2fs로 실측 비교하며 좁힌다. 절대로 “낮을수록 좋다”는 가정으로 1% 같은 극단값을 바로 쓰지 않는다.
주의사항
reserved_segments = (2 * (100/overprovision + 1) + 6) * segs_per_sec공식은 f2fs-tools 1.16.0 기준이다. 버전이 다르면 계수가 바뀔 수 있으니mkfs/f2fs_format.c를 직접 확인하는 게 안전하다.-i의 inode 상한(약 233,000)은 이번 실측 범위(512MB~8GB)에서 관찰한 값이다. 실제로 233,000개의 파일을 다 채워 ENOSPC를 재현하지는 않았다 — 시간상df -i로 상한을 직접 확인하는 선에서 그쳤다.-i는 mkfs 시점 전용 옵션이라 이미 포맷된 볼륨에 사후 적용할 방법이 없다. 적용하려면 재포맷하고 데이터를 복원해야 한다.
마무리
F2FS 실사용 공간을 최대화하는 방법은 “오버프로비저닝을 낮춘다”는 한 문장으로 끝나지 않았다. reserved_segments 계산식이 비율에 반비례하기 때문에 지나치게 낮추면 오히려 손해를 본다는 걸 소스 코드와 실측 둘 다로 확인했고, get_best_overprovision()의 자동값이 실제로 최적점에 가깝다는 것도 확인했다. -i로 NAT 예약을 더 줄일 수 있지만 그 대가(inode 상한 최대 88.6% 감소)가 얻는 것(0.3~0.4%)보다 훨씬 크므로, 파일 개수가 적고 예측 가능한 워크로드가 아니라면 함부로 쓸 옵션은 아니다.