F2FS 오버프로비저닝·NAT 예약 최적화로 실사용 공간 확보하기

전에 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%50904 MB
5% (auto가 고른 5.37%와 근접)29946 MB
3%42920 MB
2%59886 MB
1%109786 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 MB22+0 MB (이미 최소치)
1 GB42+4 MB (+0.42%)
2 GB62+6 MB (+0.29%)
4 GB102+16 MB (+0.39%)
8 GB182+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_i
Filesystem   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 MB114,176114,1760%
1 GB243,200232,957-4.2%
2 GB499,200232,957-53.3%
4 GB1,013,248232,957-77.0%
8 GB2,046,464232,957-88.6%

기본은 볼륨 크기에 비례해 inode 상한이 계속 늘어나지만, -i는 약 233,000개에서 고정된 상한으로 굳는다. 8GB 볼륨에서 얻는 건 0.37%의 여유 공간(30MB)인데, 잃는 건 inode 상한의 88.6%다. 작은 파일이 많은 워크로드(패키지 캐시, 사진 라이브러리, 소스 트리 등)라면 데이터 공간은 남아도 inode부터 고갈될 수 있다.

실전: 최적 조합 찾기

-i를 켠 상태에서도 -o를 auto보다 손으로 조정하면 같은 함정에 빠지는지 1GB에서 다시 확인했다.

-i + -ooverprov_segment_count실사용 가능(main 504세그 기준)
auto (5.36%)27954 MB
8%40928 MB
6%30948 MB
5%29950 MB
4%34940 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%)보다 훨씬 크므로, 파일 개수가 적고 예측 가능한 워크로드가 아니라면 함부로 쓸 옵션은 아니다.

참고

답글 남기기