스토리지 성능 벤치마킹: fio, sysbench, 실측 기반 용량 계획
벤치마크의 목적은 숫자 자랑이 아니라 운영 결정을 줄이는 것이다
스토리지 벤치마크를 할 때 가장 흔한 실수는 “이 디스크는 몇 IOPS까지 나오나?”만 묻는 것이다. 숫자 자체는 필요하지만, 운영자가 실제로 내려야 하는 결정은 조금 다르다.
- 이 DB 인스턴스가 피크 시간의
p99지연을 버틸 수 있는가? - 백업, compaction, rebuild, checkpoint가 겹쳐도 쓰기 지연이 튀지 않는가?
- 현재 여유 용량이 3개월 뒤 데이터 증가율을 감당하는가?
- 클라우드 볼륨의 provisioned IOPS와 throughput이 과하거나 부족하지 않은가?
- 장애 대응을 위해 같은 workload를 다른 장비에서 재현할 수 있는가?
fio와 sysbench는 이런 질문에 답하기 위한 도구다. fio 공식 문서는 fio를 여러 thread/process가 특정 I/O 동작을 수행하도록 만드는 유연한 workload generator라고 설명한다. AWS EBS 문서도 EBS 볼륨을 benchmark할 때 실제 애플리케이션과 비슷한 workload를 시뮬레이션하라고 권한다. 즉 벤치마크는 “최대치 측정”이 아니라 운영 workload를 안전하게 축소 재현하는 실험에 가깝다.
가장 먼저 정해야 할 것은 테스트 명령이 아니라 가설이다.
가설: OLTP DB의 주된 병목은 8KiB random read/write이고,
피크 시간에는 read 70%, write 30%, 동시 outstanding I/O 128개 수준이다.
검증: fio randrw, bs=8k, rwmixread=70, numjobs*iodepth=128로 10분 이상 측정한다.
판정: p99 write latency가 SLO보다 낮고, iostat await/aqu-sz가 지속 상승하지 않아야 한다.이런 식으로 가설을 먼저 쓰면 결과 해석이 단순해진다. 반대로 가설 없이 명령만 여러 개 돌리면 “어떤 숫자가 좋은 숫자인지”를 나중에 다시 추측하게 된다.
IOPS, throughput, latency는 서로 바꿔 쓸 수 없다
스토리지 성능을 볼 때 최소 세 축을 분리해야 한다.
| 축 | 주로 보는 지표 | 대표 workload | 운영 질문 |
|---|---|---|---|
| IOPS | 초당 I/O 요청 수 | 4KiB/8KiB random read/write | 작은 요청을 몇 개까지 처리하는가? |
| Throughput | MiB/s, GiB/s | 1MiB sequential read/write | 백업, scan, restore가 얼마나 빨리 끝나는가? |
| Latency | 평균, p95, p99, p99.9 | OLTP write, fsync, WAL | 사용자가 체감하는 tail 지연이 안전한가? |
IOPS와 throughput은 block size로 연결된다. 단순화하면 다음 관계가 있다.
throughput = IOPS × block_size4KiB random I/O 50,000 IOPS는 대략 195MiB/s다. 하지만 1MiB sequential I/O 1,000 IOPS는 약 1,000MiB/s다. 그래서 “IOPS가 높다”는 말은 block size와 access pattern 없이 의미가 부족하다.
더 위험한 것은 latency를 평균으로만 보는 일이다. DB와 메시지 큐, 로그 저장소는 평균보다 tail latency에 더 민감하다. 평균 지연이 1ms여도 p99가 80ms라면 사용자는 간헐적 멈춤을 경험할 수 있다. fio 출력에서 clat percentiles를 보는 이유가 여기에 있다.
fio는 workload를 정확히 쓰는 도구다
fio의 장점은 option이 많다는 점이 아니라, workload의 모양을 명확히 기록할 수 있다는 점이다. 운영자가 최소로 이해해야 할 option은 다음과 같다.
| fio option | 의미 | 운영 해석 |
|---|---|---|
--rw | read/write 패턴 | read, write, randread, randwrite, randrw |
--bs | I/O block size | DB page, WAL write, backup chunk 크기와 맞춘다 |
--direct=1 | page cache 우회 | OS cache가 아니라 device/filesystem에 가까운 성능 측정 |
--ioengine | I/O 제출 방식 | Linux에서는 libaio, 최신 환경에서는 io_uring도 검토 |
--iodepth | 한 job의 outstanding I/O 수 | queue depth를 올려 포화 지점 확인 |
--numjobs | 병렬 job 수 | thread/process 동시성 재현 |
--runtime + --time_based | 정해진 시간 동안 반복 | 짧은 파일을 다 읽고 끝나는 상황 방지 |
--size | 테스트 파일 크기 | 메모리보다 충분히 크게 잡아 cache 착시 완화 |
--rwmixread | mixed workload의 read 비율 | 예: read 70%, write 30% |
--group_reporting | job 결과 집계 | 병렬 job의 합산 결과를 보기 쉽게 함 |
1단계: 테스트 대상과 파괴 위험을 분리한다
AWS EBS 벤치마크 문서는 일부 절차가 기존 데이터를 파괴할 수 있으므로 production volume이 아니라 테스트용으로 만든 volume에서 수행하라고 경고한다. 이 원칙은 온프레미스도 같다.
- raw block device에 직접 쓰는 benchmark는 데이터를 지운다.
- filesystem 위 파일로 테스트해도 해당 경로의 공간과 I/O를 크게 소모한다.
- 운영 DB와 같은 volume에서 벤치마크를 돌리면 실제 서비스 지연을 만들 수 있다.
- 클라우드에서는 benchmark용 instance, volume, IOPS 설정 비용도 정리해야 한다.
운영 표준은 보통 다음 순서가 안전하다.
# 1. 테스트 장비/볼륨 식별
lsblk
findmnt
# 2. 운영 mount가 아닌지 확인한 뒤 테스트 디렉터리 사용
mkdir -p /mnt/bench
# 3. 테스트 파일은 명시적으로 이름을 붙인다
TEST_FILE=/mnt/bench/fio-testfile2단계: random I/O와 sequential I/O를 나눠 측정한다
OLTP 계열 DB는 8KiB 또는 16KiB page 접근이 많고, 로그·백업·scan은 큰 sequential I/O가 많다. 둘을 하나의 숫자로 합치면 안 된다.
# 8KiB random read/write 70:30, DB page 접근에 가까운 프로파일
fio --name=oltp-randrw \
--filename=/mnt/bench/fio-testfile \
--size=20G --direct=1 --ioengine=libaio \
--rw=randrw --rwmixread=70 --bs=8k \
--numjobs=4 --iodepth=32 \
--runtime=600 --time_based --group_reporting# 1MiB sequential read, 백업/scan/restore 대역폭 확인
fio --name=seq-read \
--filename=/mnt/bench/fio-testfile \
--size=20G --direct=1 --ioengine=libaio \
--rw=read --bs=1m \
--numjobs=1 --iodepth=32 \
--runtime=300 --time_based --group_reporting# 1MiB sequential write, 적재/restore/대량 export 성격 확인
fio --name=seq-write \
--filename=/mnt/bench/fio-testfile \
--size=20G --direct=1 --ioengine=libaio \
--rw=write --bs=1m \
--numjobs=1 --iodepth=32 \
--runtime=300 --time_based --group_reporting3단계: fsync 비용은 별도 측정한다
DB commit latency는 단순 write bandwidth와 다르다. WAL이나 redo log는 작은 write 후 durability를 요구한다. fio에서는 --fsync=N 같은 option으로 주기적 flush를 섞을 수 있다.
# 예: 작은 sequential write를 100번마다 fsync
fio --name=log-fsync \
--filename=/mnt/bench/fio-logfile \
--size=10G --direct=1 --ioengine=libaio \
--rw=write --bs=16k --iodepth=1 --numjobs=1 \
--fsync=100 \
--runtime=300 --time_based --group_reporting이 결과는 “로그성 쓰기 workload에서 commit tail latency가 어디까지 튀는가?”를 보는 데 더 가깝다. 데이터 파일 random I/O와 섞어서 평균을 내면 안 된다.
fio 결과는 세 줄만 보지 말고 분포와 포화 지점을 본다
fio 결과에서 IOPS, BW, clat 평균만 보면 절반만 본 것이다. 운영자가 확인할 항목은 다음 순서가 좋다.
read: IOPS=..., BW=...
write: IOPS=..., BW=...
clat percentiles: 50th, 95th, 99th, 99.9th
latency distribution
cpu usage
IO depths
issued/completed counts해석할 때는 다음 질문을 붙인다.
- p99가 SLO 안에 있는가? 평균보다 p95/p99가 먼저다.
- iodepth를 올렸을 때 IOPS는 늘지만 latency가 폭발하는가? 그 지점이 practical saturation point다.
- read와 write 중 어느 쪽 tail이 나쁜가? write만 나쁘면 flush, journal, cache, device writeback 정책을 의심한다.
- CPU가 병목인가? 압축, 암호화, checksum, filesystem overhead 때문에 storage가 아니라 CPU가 먼저 찰 수 있다.
- 측정 시간이 충분한가? 너무 짧은 테스트는 cache, SSD SLC cache, cloud burst credit 착시를 만들 수 있다.
특히 SSD와 클라우드 볼륨에서는 초기 몇 초의 숫자와 장시간 steady state가 다를 수 있다. 짧은 30초 테스트로 “충분하다”고 결론 내리기보다, 운영 피크와 batch window를 반영해 5분, 10분, 30분 구간을 나누어 보는 편이 안전하다.
iostat은 벤치마크가 아니라 병목 위치를 알려주는 계기판이다
벤치마크를 돌릴 때 fio 결과만 저장하면 원인 분석이 어렵다. 동시에 iostat -x를 켜 두면 device 입장에서 어떤 일이 벌어졌는지 볼 수 있다. Debian iostat man page는 첫 report가 부팅 이후 누적값이고, 이후 report는 직전 interval 기준이라고 설명한다. 그래서 benchmark 중에는 -y로 첫 누적 report를 빼고 보는 편이 좋다.
# benchmark와 별도 터미널에서 실행
iostat -xym 2
# 첫 누적 report를 생략하고 2초 간격으로 20번만 보기
iostat -xym -y 2 20주요 항목은 다음과 같다.
| iostat 항목 | 의미 | 주의점 |
|---|---|---|
r/s, w/s | 초당 read/write 완료 요청 수 | fio IOPS와 큰 방향이 맞아야 한다 |
rMB/s, wMB/s | 초당 read/write 대역폭 | block size와 함께 해석한다 |
r_await, w_await | queue 대기 + 처리 시간을 포함한 평균 지연 | 평균이므로 tail은 fio percentile로 보완한다 |
aqu-sz | 평균 queue length | 지속 상승하면 backlog 가능성 |
%util | device에 I/O가 발행된 시간 비율 | NVMe/RAID처럼 병렬 처리 장치에서는 100%가 곧 한계라는 뜻은 아니다 |
%util 하나만 보고 “디스크가 100%라서 병목”이라고 말하면 위험하다. man page도 %util은 serial하게 요청을 처리하는 장치에서는 100% 근처가 saturation을 의미할 수 있지만, RAID array나 최신 SSD처럼 병렬 처리하는 장치에서는 성능 한계를 정확히 반영하지 않을 수 있다고 설명한다. %util, await, aqu-sz, fio의 p99 latency를 같이 봐야 한다.
sysbench fileio는 단순 비교와 DB 벤치마크 연결에 유용하다
sysbench는 LuaJIT 기반의 scriptable multi-threaded benchmark 도구이고, README는 bundled benchmark로 fileio, cpu, memory, oltp_*를 제공한다고 설명한다. storage만 정밀하게 보려면 fio가 더 유연하지만, sysbench는 다음 상황에서 편하다.
- 파일시스템 단위의 간단한 비교가 필요하다.
- 같은 도구로 MySQL/PostgreSQL OLTP benchmark까지 이어가고 싶다.
- 팀 runbook에 “prepare → run → cleanup” 패턴을 단순하게 넣고 싶다.
기본 흐름은 세 단계다.
# 1. 테스트 파일 생성
sysbench fileio \
--file-total-size=20G \
--file-num=128 \
prepare
# 2. random read/write 실행
sysbench fileio \
--file-total-size=20G \
--file-test-mode=rndrw \
--file-extra-flags=direct \
--threads=8 \
--time=300 \
--events=0 \
run
# 3. 테스트 파일 제거
sysbench fileio \
--file-total-size=20G \
cleanupsysbench 결과에서는 throughput, read/write IOPS, latency percentiles를 확인한다. 다만 세부 workload shaping은 fio만큼 정밀하지 않다. 예를 들어 block size, fsync 빈도, async backlog, read/write ratio를 조정할 수는 있지만, 여러 job을 섞거나 offset을 나누는 복잡한 프로파일은 fio job file이 더 적합하다.
실무 기준으로는 이렇게 나누면 된다.
| 상황 | 추천 도구 |
|---|---|
| 새 볼륨의 raw 성능, queue depth별 포화 지점 | fio |
| DB page size와 fsync workload 재현 | fio |
| 간단한 파일시스템 비교 | sysbench fileio |
| MySQL/PostgreSQL application-level benchmark와 연결 | sysbench OLTP + 별도 fio baseline |
| 클라우드 IOPS/throughput 구매량 검증 | fio + cloud monitoring |
용량 계획은 “현재 평균 사용량”이 아니라 growth와 headroom이다
스토리지 용량 계획은 성능 벤치마크와 연결되어야 한다. 디스크가 70% 찼다는 사실보다 중요한 것은 다음이다.
- 하루 데이터 증가량이 얼마인가?
- index, WAL/binlog, snapshot, backup staging까지 포함했는가?
- compaction, vacuum, rebuild, restore 중 임시 공간이 얼마나 필요한가?
- 용량이 찰수록 write amplification이나 GC 때문에 latency가 나빠지는가?
- cloud volume의 IOPS/throughput 한도와 instance 한도 중 무엇이 먼저 찼는가?
가장 단순한 예측식은 다음과 같다.
days_to_threshold = (threshold_bytes - current_used_bytes) / daily_growth_bytes하지만 운영에서는 보통 headroom을 둔다.
usable_planning_capacity = total_capacity × 0.75
required_capacity_90d = current_used
+ daily_growth × 90
+ temporary_work_space
+ backup_or_snapshot_staging여기서 0.75는 예시일 뿐이다. workload에 따라 70%, 80%, 85%가 될 수 있다. 중요한 점은 “100%까지 쓸 수 있다”고 계획하지 않는 것이다. DB와 파일시스템은 여유 공간이 줄어들수록 vacuum, compaction, checkpoint, snapshot, restore 같은 운영 작업이 더 위험해진다.
성능 headroom도 같이 계산한다
용량만 여유 있어도 성능이 먼저 찰 수 있다. 예를 들어 현재 피크가 12,000 write IOPS이고 benchmark상 p99 latency가 안전한 한계가 18,000 write IOPS라면 남은 headroom은 6,000 IOPS다. 하지만 백업, 인덱스 생성, batch 적재가 동시에 4,000 IOPS를 더 쓰면 장애 여유는 2,000 IOPS뿐이다.
운영 표준에는 다음 항목을 같이 기록하는 것이 좋다.
| 항목 | 예시 |
|---|---|
| 측정 날짜 | 2026-07-08 |
| 장비/볼륨 | instance type, volume type, RAID/filesystem |
| workload | randrw, bs=8k, read 70%, write 30% |
| 동시성 | numjobs=4, iodepth=32 |
| 결과 | IOPS, MiB/s, p95, p99, p99.9 |
| 계기판 | iostat await/aqu-sz, cloud volume metrics |
| 판정 | 현재 피크 대비 headroom, 다음 증설 기준 |
벤치마크 runbook은 재현 가능해야 한다
팀에서 쓸 수 있는 최소 runbook은 다음 형태가 좋다.
1. 사전 확인
date -Is
uname -a
lsblk
findmnt
fio --version
sysbench --version || true
iostat -V || true확인할 것:
- 테스트 대상이 production 데이터가 아닌가?
- 테스트 파일 크기가 메모리보다 충분히 큰가?
- 같은 volume에서 다른 batch나 backup이 돌고 있지 않은가?
- cloud volume의 burst credit, throughput cap, instance cap을 알고 있는가?
- 테스트 후 파일과 cloud resource를 삭제할 계획이 있는가?
2. fio job file로 기록
명령 한 줄보다 job file이 재현성이 좋다.
# storage-oltp-randrw.fio
[global]
filename=/mnt/bench/fio-testfile
size=20G
direct=1
ioengine=libaio
time_based
runtime=600
group_reporting
randrepeat=0
[oltp-randrw-8k-70-30]
rw=randrw
rwmixread=70
bs=8k
numjobs=4
iodepth=32실행은 단순하다.
fio storage-oltp-randrw.fio | tee fio-oltp-randrw-$(date +%Y%m%dT%H%M%S).log3. 동시에 OS 지표 저장
iostat -xym -y 2 300 | tee iostat-$(date +%Y%m%dT%H%M%S).log4. 판정 문장까지 남긴다
벤치마크 로그만 남기면 다음 사람이 다시 해석해야 한다. 반드시 사람이 읽을 판정 문장을 붙인다.
판정:
- 8KiB randrw 70:30에서 numjobs*iodepth=128까지 p99 write latency는 목표 20ms 이내였다.
- iodepth를 256으로 올리면 IOPS 증가는 작고 p99가 3배 증가했다.
- 현재 피크는 benchmark 안전 한계의 약 55%다.
- 다음 증설 검토 기준은 피크 write IOPS 70% 초과 또는 p99 write latency 15ms 3일 연속 초과다.이 판정이 있어야 benchmark가 capacity planning으로 이어진다.
흔한 함정
1. page cache를 디스크 성능으로 착각한다
direct=0으로 작은 파일을 반복 읽으면 OS page cache 성능을 측정할 수 있다. 그것도 의미 있는 테스트일 수 있지만, “스토리지 장치 성능”이라고 부르면 안 된다. 목적이 device/filesystem 성능이면 --direct=1과 충분히 큰 --size를 검토한다.
2. 너무 작은 테스트 파일을 쓴다
메모리보다 작은 파일, SSD cache에만 들어가는 짧은 workload는 실제 피크를 반영하지 못한다. 운영 데이터 크기와 working set을 고려해 파일 크기를 정한다.
3. 평균 latency만 본다
평균 2ms, p99 120ms인 장치는 사용자가 보기에는 “가끔 멈추는 장치”다. fio percentile과 application latency를 같이 본다.
4. benchmark를 production volume에서 돌린다
벤치마크는 그 자체가 장애 유발 workload다. 운영 volume에서 돌려야 한다면 read-only, 낮은 rate limit, maintenance window, rollback plan을 명시해야 한다.
5. cloud 한도를 빠뜨린다
클라우드 block storage는 volume 한도, instance EBS bandwidth, network path, burst credit이 함께 작동한다. fio 결과가 낮다고 곧바로 “디스크가 느리다”고 판단하지 말고 instance와 volume의 조합을 같이 확인한다.
운영 체크리스트
- [ ] benchmark 목적을 먼저 썼는가? 최대치, 회귀 검증, 증설 판단, 장애 재현 중 무엇인가?
- [ ] production 데이터를 덮어쓰지 않는가?
- [ ]
fiojob file 또는 명령을 저장했는가? - [ ]
bs,rw,rwmixread,iodepth,numjobs,runtime,size,direct를 기록했는가? - [ ]
iostat -x또는 cloud monitoring을 같이 저장했는가? - [ ] 평균이 아니라 p95/p99/p99.9를 봤는가?
- [ ] cache, burst, warm-up, steady state를 구분했는가?
- [ ] 결과를 capacity planning 문장으로 바꿨는가?
- [ ] 테스트 파일, 임시 volume, cloud resource를 정리했는가?
References
- fio documentation, “fio - Flexible I/O tester”: https://fio.readthedocs.io/en/latest/fio_doc.html
- AWS EBS User Guide, “Benchmark Amazon EBS volumes”: https://docs.aws.amazon.com/ebs/latest/userguide/benchmark_procedures.html
- AWS EBS User Guide, “Amazon EBS volume performance”: https://docs.aws.amazon.com/ebs/latest/userguide/ebs-performance.html
- Debian manpages,
iostat(1)fromsysstat: https://manpages.debian.org/testing/sysstat/iostat.1.en.html - sysbench README,
akopytov/sysbench: https://github.com/akopytov/sysbench/blob/master/README.md