물리 서버 NVMe 성능은 어떻게 검증할까: fio 파일 테스트

물리 서버 NVMe 성능은 어떻게 검증할까: fio 파일 테스트

물리 서버 NVMe 성능은 어떻게 검증할까: fio 파일 테스트

새 서버에서 데이터베이스나 파일 서비스를 운영할 예정이라면, 대용량 파일 하나를 복사해 얻은 속도만으로 스토리지를 검증하기에는 부족합니다. 파일이 캐시에 적중했을 수 있고, 쓰기 데이터가 아직 영구 저장되지 않았을 수도 있으며, 백그라운드 작업이 결과에 영향을 줄 수도 있습니다. 더 신뢰할 수 있는 방법은 테스트 파일, 블록 크기, 동시 처리 조건을 고정한 뒤 처리량과 지연을 각각 확인하고, 재측정에 활용할 수 있는 원본 기록을 남기는 것입니다.

CloudUDP의 물리 서버에는 1TB NVMe 2개가 기본으로 탑재되며, 각 드라이브가 직접 연결되어 있고 하드웨어 RAID는 없습니다. 아래에서는 Debian 13을 기준으로, I/O 부하를 설정할 수 있는 테스트 도구인 fio를 사용해 지정한 파일 시스템을 테스트합니다. 원시 장치에는 쓰지 않으며, 테스트 결과를 실제 서비스 성능에 대한 보장으로 해석하지 않습니다.

1. 테스트 범위와 실제 저장 위치부터 확인하기

파일 단위 테스트는 ‘파일 시스템에서 스토리지까지’의 경로를 측정하는 것이지, 드라이브 자체의 최대 성능만 따로 측정하는 것은 아닙니다. 암호화, 볼륨 관리, 미러링, 마운트 옵션 모두 결과에 영향을 줄 수 있습니다. 두 드라이브가 각각 직접 연결되어 있다고 해서 특정 디렉터리가 두 드라이브를 동시에 사용하는 것도 아닙니다.

먼저 도구를 설치하고 대상 디렉터리를 확인합니다. 아래에서는 /srv를 테스트 위치 후보로 사용합니다. 이 디렉터리가 대상 파일 시스템에 속하지 않는다면 실제 마운트 디렉터리로 바꿔야 합니다.

sudo apt-get install fio
findmnt -T /srv -o TARGET,SOURCE,FSTYPE,OPTIONS
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
df -h /srv
df -i /srv
fio --version

사용 가능한 공간, 마운트 상태, 디렉터리 권한을 확인합니다. 드라이브의 표기 용량만 보고 판단해서는 안 됩니다. 파일 시스템의 오버헤드와 기존 데이터도 공간을 차지합니다. 테스트 파일 전체를 쓴 뒤에도 서비스 데이터가 늘어날 공간을 남겨 두어야 합니다.

쓰기 테스트에 /dev/nvme… 같은 장치 경로를 지정하지 마세요. 별도 파일을 사용하면 실수로 기존 데이터를 덮어쓸 위험은 줄일 수 있지만, 리소스 경합까지 없앨 수는 없습니다. 가능하면 서비스 운영을 시작하기 전에 실행하고, 서비스 지연이 비정상적으로 늘어나거나 여유 공간이 사전에 정한 한계에 가까워지거나 I/O 오류가 발생하면 즉시 중단하세요.

2. 전용 디렉터리를 만들고 테스트 파일 전체를 준비하기

모든 명령은 동일한 Bash 세션에서 실행합니다. 관리자 권한으로 현재 작업 계정만 접근할 수 있는 임시 디렉터리를 만들고, 테스트 파일과 결과를 모두 그 안에 저장해 기존 서비스 파일에 영향을 주지 않도록 합니다.

TESTDIR=$(sudo mktemp -d /srv/fio-acceptance.XXXXXX)
sudo chown "$(id -u):$(id -g)" "$TESTDIR"
chmod 700 "$TESTDIR"
export TESTDIR
printf '%s\n' "$TESTDIR"
findmnt -T "$TESTDIR"

이 예제에서는 1GiB 파일을 사용합니다. 이는 절차를 확인하기 위한 출발점일 뿐, 스토리지 전체 용량이나 장시간 정상 상태에서의 성능을 대표하지 않습니다. fio에서 size=1G는 이진 단위로 해석되므로 장치의 표기 용량과 같은 단위로 간주해 계산하면 안 됩니다. 공간이 충분한지 확인한 다음, 먼저 순차 쓰기로 파일 전체를 채웁니다.

fio --name=prepare \
  --filename="$TESTDIR/test.bin" \
  --size=1G --rw=write --bs=1M \
  --ioengine=libaio --direct=1 \
  --iodepth=1 --numjobs=1 --end_fsync=1 \
  --group_reporting --output-format=json \
  --output="$TESTDIR/prepare.json"

종료 상태와 JSON의 오류 필드를 반드시 확인해야 합니다. 직접 I/O가 지원되지 않는다면 먼저 파일 시스템과 마운트 방식을 점검하세요. 아무 설명 없이 캐시 I/O로 바꾼 뒤 결과 비교를 계속해서는 안 됩니다. 준비 단계에서 오류가 발생했다면 이후 읽기 테스트의 결과도 신뢰할 수 없습니다.

direct=1은 일반적으로 파일 데이터의 페이지 캐시를 우회하지만, 모든 장치 캐시를 우회하지는 않습니다. end_fsync=1은 작업 종료 시 동기화를 요청하는 옵션이며, 매번 쓰기를 수행할 때마다 데이터베이스 커밋을 모사한다는 뜻은 아닙니다.

3. 매개변수를 고정하고 랜덤 읽기와 랜덤 쓰기를 따로 테스트하기

먼저 단일 작업으로 랜덤 읽기를 수행하고, 그다음 랜덤 쓰기를 수행합니다. 혼합 부하 때문에 문제가 가려지지 않도록 하기 위해서입니다. 테스트 중에는 데이터 복사나 다른 스토리지 테스트를 동시에 실행하지 마세요.

fio --name=randread \
  --filename="$TESTDIR/test.bin" \
  --size=1G --rw=randread --bs=4k \
  --ioengine=libaio --direct=1 \
  --iodepth=8 --numjobs=1 \
  --time_based --runtime=30 --ramp_time=5 \
  --group_reporting --output-format=json \
  --output="$TESTDIR/randread-q8.json"
fio --name=randwrite \
  --filename="$TESTDIR/test.bin" \
  --size=1G --rw=randwrite --bs=4k \
  --ioengine=libaio --direct=1 \
  --iodepth=8 --numjobs=1 \
  --time_based --runtime=30 --ramp_time=5 \
  --end_fsync=1 --group_reporting \
  --output-format=json \
  --output="$TESTDIR/randwrite-q8.json"

여기서 실행 시간과 워밍업 시간은 테스트 매개변수일 뿐입니다. 짧은 테스트는 장치 캐시, 온도, 백그라운드 정리 작업의 영향을 받을 수 있으므로, 이 결과만으로 장시간 쓰기 성능을 추정해서는 안 됩니다.

낮은 큐 깊이로 비교 테스트 추가하기

읽기 테스트의 iodepth를 1로 바꾸고 출력 파일을 randread-q1.json으로 변경하되, 나머지 매개변수는 그대로 유지합니다. 큐 깊이는 동시에 처리 중인 미완료 I/O 요청을 몇 개까지 허용하는지를 나타냅니다. CPU 코어 수와 같지 않으며, 서비스의 동시 연결 수도 아닙니다.

큐 깊이를 높였을 때 처리량은 늘지만 꼬리 지연이 크게 나빠진다면, 이는 해당 부하에서 처리량과 대기 시간 사이에 절충 관계가 있다는 뜻일 뿐입니다. 이 값을 데이터베이스 연결 풀에 그대로 적용하지 마세요.

4. 처리량, IOPS, 꼬리 지연을 함께 해석하기

IOPS는 초당 완료되는 I/O 작업 수입니다. 블록 크기가 고정되어 있으면 처리량과 일정한 관계가 있지만, 블록 크기가 다른 결과를 IOPS만으로 비교해서는 안 됩니다. fio JSON의 bw는 일반적으로 KiB/s 단위로 표시되므로, 결과를 읽을 때 버전과 필드의 단위를 확인해야 합니다.

read 또는 write 안의 iops, 대역폭 필드, 그리고 clat_ns의 평균값과 백분위수를 중점적으로 확인합니다. 완료 지연은 애플리케이션 측 응답 시간과 완전히 같지는 않습니다. 애플리케이션 내부의 대기와 처리는 이 측정 범위에 포함되지 않습니다.

P99는 약 99%의 요청이 해당 값 이하의 지연으로 완료된다는 뜻이지, 최대 지연을 뜻하지 않습니다. 평균값은 비슷한데 P99가 상승했다면 일부 요청에서 대기 시간이 길어졌을 수 있습니다. 실제 큐 깊이와 반복 테스트 결과를 함께 살펴 판단해야 합니다.

검증 기록에는 최소한 다음 항목을 포함해야 합니다.

fio의 실제 I/O 깊이 분포도 확인해야 합니다. 8로 설정했다고 해서 실행 내내 그 깊이에 도달하는 것은 아닙니다. 엔진이나 파일 시스템의 동작이 다르면 설정값이 같아도 결과를 비교하기 어려울 수 있습니다.

5. 재측정, 보관, 정리까지 완료하기

각 부하를 반복해서 실행하고, 출력 파일 이름을 매번 다르게 지정해 이전 결과를 덮어쓰지 않도록 합니다. 가장 높은 수치만 고르지 말고 변동 범위를 비교하세요. 테스트 파일을 키우거나 실행 시간을 늘리기 전에는 공간을 다시 확인하고, 추가 쓰기량과 리소스 경합을 명확히 파악해야 합니다.

두 드라이브를 각각 검증해야 한다면 각 파일 시스템에 속한 디렉터리를 선택한 뒤, 같은 매개변수로 하나씩 순서대로 테스트합니다. 두 디렉터리가 실제로 같은 볼륨에 있다면 디렉터리만 바꿨다고 해서 두 번째 드라이브를 독립적으로 검증한 것이 되지는 않습니다.

읽기·쓰기 테스트에서 오류가 발생하면 먼저 원본 출력과 관련 커널 기록을 보존한 뒤 파일 시스템과 장치 연결 경로를 점검합니다. 오류는 없지만 결과의 변동이 크다면, 같은 서버의 다른 작업이나 테스트 조건의 변화부터 배제하세요. 곧바로 하드웨어 이상으로 단정해서는 안 됩니다.

테스트를 마치면 JSON, 마운트 정보, 명령 기록을 관리되는 보관 위치로 복사합니다. 보관한 파일을 읽을 수 있는지 확인한 다음, 테스트 디렉터리를 다시 확인하고 이번에 생성한 파일만 삭제합니다.

printf '%s\n' "$TESTDIR"
ls -lh -- "$TESTDIR"
rm -- "$TESTDIR/test.bin"

남은 결과 파일도 보관되었는지 확인한 뒤 하나씩 정리하고, 디렉터리가 비면 rmdir로 삭제합니다. 장치 마운트 지점을 재귀적으로 삭제하는 명령은 사용하지 마세요. 최종 결론은 ‘이 매개변수와 부하 조건에서 이러한 결과를 얻었다’로 작성해야 하며, ‘이 드라이브는 항상 특정 속도를 낸다’로 작성해서는 안 됩니다. 파일 단위 기준선은 뚜렷한 편차를 발견하는 데 쓰이며, 애플리케이션 운영을 시작하기 전에는 실제 읽기·쓰기 패턴으로 다시 검증해야 합니다.

자주 묻는 질문

파일 기반 fio 테스트는 운영 서비스에 영향을 주지 않나요?

그렇지 않습니다. 독립 파일은 기존 파일을 직접 덮어쓰는 일을 피하지만 디스크, CPU, 파일 시스템 자원 경쟁은 남습니다. 서비스 시작 전에 실행하거나 운영 중 영향과 중단 조건을 먼저 정해야 합니다.

direct=1은 모든 캐시를 우회하고 데이터베이스 커밋을 재현하나요?

아닙니다. 직접 I/O는 보통 파일 데이터의 페이지 캐시를 우회하지만 모든 장치 캐시를 우회하지는 않습니다. 데이터베이스의 영구 커밋과도 다르므로 애플리케이션과 동기 쓰기 의미를 별도로 검증해야 합니다.

EPYC 7543P 단독 사용 물리 서버, 월 $454부터

물리 서버 월 단위 임대

결제 전에 구성 항목과 이번 결제 금액을 확인할 수 있습니다. 모든 가격은 미국 달러로 결제됩니다.