하드웨어 중급 #5 스토리지 성능 실측 — fio, 큐 깊이, SSD의 속사정

5 분 소요

4편까지 CPU와 메모리를 봤으니 스토리지 차례입니다. 기초 4편에서 IOPS, 처리량, 지연이라는 세 축을 잡았습니다. 이번 글의 질문은 운영의 것입니다. 카탈로그에 “50만 IOPS"라고 적힌 디스크가 내 서비스에서는 왜 5만도 안 나오는가. 답은 측정 조건에 있고, 그래서 이 글은 직접 재는 법부터 시작합니다.

카탈로그 숫자의 조건 #

스토리지 성능 숫자에는 항상 꼬리표가 붙습니다. 블록 크기(4KB인지 128KB인지), 패턴(순차인지 랜덤인지), 읽기/쓰기 비율, 그리고 큐 깊이입니다. 카탈로그의 최대 IOPS는 보통 “4KB 랜덤 읽기, 큐 깊이 수십 이상"이라는, 병렬성이 최대로 걸린 조건의 숫자입니다. 내 워크로드가 그 조건과 다르면 숫자도 다릅니다. 그러니 의미 있는 질문은 “이 디스크는 몇 IOPS인가"가 아니라 내 워크로드 모양에서 몇이 나오는가입니다.

fio — 워크로드 모양대로 재기 #

표준 측정 도구는 fio입니다. 블록 크기, 패턴, 병렬성을 지정해 원하는 모양의 부하를 만들어 줍니다.

terminal
# 4KB 랜덤 읽기, 큐 깊이 1 — 지연이 그대로 드러나는 조건
fio --name=randread --filename=/data/fio.test --size=4G \
    --rw=randread --bs=4k --iodepth=1 --runtime=60 --time_based \
    --ioengine=libaio --direct=1
결과에서 볼 세 줄
read: IOPS=11.2k, BW=43.8MiB/s
 lat (usec): avg=88.1, ...
 lat percentiles : 99.00th=[ 180], 99.90th=[ 420]

읽는 법 두 가지만 짚습니다.

  • --direct=1이 핵심입니다. 페이지 캐시를 우회해 디스크 자체를 재는 옵션입니다. 이게 없으면 3편에서 본 페이지 캐시가 끼어들어 메모리 속도를 재게 됩니다.
  • 평균보다 백분위 지연을 봅니다. 평균 88µs는 좋아 보여도 99.9분위가 수 ms라면, 천 번에 한 번은 느린 I/O가 섞인다는 뜻입니다. 사용자 체감과 데이터베이스의 꼬리 지연을 만드는 쪽은 백분위입니다.

주의할 점은 fio가 실제 부하라는 것입니다. 운영 중인 디스크에 쓰기 테스트를 돌리면 서비스 I/O 와 경합하고, --filename을 잘못 주면 데이터를 덮어쓸 수 있습니다. 측정은 전용 파일과 한가한 시간, 가능하면 동급의 비운영 장비에서 합니다.

큐 깊이 — IOPS와 지연의 거래 #

큐 깊이(queue depth, 디스크에 동시에 걸어 두는 미완료 I/O 요청 수)는 카탈로그 숫자와 체감의 간극을 설명하는 열쇠입니다. SSD, 특히 NVMe는 내부가 병렬 구조라서 요청을 여러 개 걸어 줘야 전체 성능이 나옵니다.

큐 깊이IOPS평균 지연의미
111k0.09ms한 번에 하나. 지연은 최소, 처리량은 일부만
870k0.11ms병렬성이 살아나기 시작
32200k0.16ms처리량 상승, 지연도 상승
128350k0.36ms카탈로그 근처. 지연은 4배

(수치는 측정 예시이고 장비마다 다릅니다.) 패턴이 보입니다. 큐를 쌓을수록 IOPS는 오르고 지연도 오릅니다. 1편의 사용률과 포화가 여기서 그대로 재현됩니다. 큐 깊이가 곧 포화의 양이기 때문입니다. 그래서 “카탈로그 IOPS가 안 나온다"는 불만의 절반은 큐 깊이 1짜리 직렬 워크로드(예: 단일 스레드로 fsync를 반복하는 DB 커밋)를 카탈로그 조건과 비교한 것입니다. 그런 워크로드의 성능은 IOPS가 아니라 큐 깊이 1의 지연이 결정합니다.

SSD의 속사정 — 쓰기 증폭과 TRIM #

같은 SSD가 어제는 빠르고 오늘은 느린 일이 있습니다. 원인은 대부분 SSD 내부의 동작에 있습니다.

  • SSD의 플래시는 덮어쓰기가 안 됩니다. 지우고 다시 써야 하는데, 지우는 단위(블록)가 쓰는 단위(페이지)보다 훨씬 큽니다. 그래서 컨트롤러는 빈 곳에 새로 쓰고 옛 데이터를 무효 표시한 뒤, 나중에 유효 페이지만 모아 옮기고 블록을 지우는 청소(가비지 컬렉션)를 돌립니다.
  • 이 과정에서 사용자가 1을 쓸 때 내부적으로는 그 몇 배를 쓰게 되는 현상이 쓰기 증폭(write amplification)입니다. 디스크가 가득 찰수록, 그리고 랜덤 쓰기가 많을수록 청소가 잦아져 증폭이 커지고 쓰기 성능이 떨어집니다.
  • TRIM은 파일 시스템이 “이 영역은 지워진 데이터"라고 SSD에 알려 주는 명령입니다. 컨트롤러가 무효 페이지를 미리 알면 청소가 가벼워집니다. 리눅스에서는 보통 주기적 fstrim(systemd 타이머)으로 돌립니다.

운영 함의는 세 가지입니다. SSD는 가득 채우지 않는 것 자체가 성능 관리이고(여유 공간이 청소장의 작업 공간입니다), TRIM이 실제로 돌고 있는지 확인할 가치가 있고, 쓰기 성능 측정은 충분히 오래 돌려야(짧은 테스트는 청소가 시작되기 전의 반짝 성능을 잽니다) 진짜 숫자가 나옵니다.

클라우드 디스크에서는 #

클라우드 블록 스토리지(EBS 같은)에서는 같은 원리가 다른 모습으로 나타납니다. IOPS와 처리량이 볼륨 유형과 크기, 그리고 인스턴스 자체의 한도로 정해져 있어서, fio로 재면 디스크의 물리 성능이 아니라 계약된 상한이 측정됩니다. 디스크는 빠른데 인스턴스 쪽 한도에 걸리는 경우도 흔하므로, 볼륨과 인스턴스 양쪽의 한도 표를 함께 봐야 합니다. 기초 9편에서 본 “사양표 읽기"가 스토리지에서도 반복되는 셈입니다.

자주 만나는 함정 #

  • 캐시를 끼고 잰다 — direct 없이 재면 페이지 캐시 속도가 나옵니다. 디스크를 잴 때는 --direct=1, 서비스 전체를 잴 때만 캐시 포함으로 잽니다.
  • 평균 지연으로 판단한다 — 꼬리(99분위 이상)가 사용자 체감을 정합니다. 평균이 좋아도 꼬리가 길면 “가끔 느린” 서비스가 됩니다.
  • 빈 SSD에서 잰 숫자를 믿는다 — 새 SSD나 막 비운 SSD는 청소 부담이 없어 한동안 빠릅니다. 운영 점유율 수준으로 채운 상태에서, 충분히 길게 재야 운영 성능입니다.

정리 #

이번 글에서 잡은 그림입니다.

  • 스토리지 숫자는 블록 크기, 패턴, 큐 깊이라는 조건의 함수입니다. fio로 내 워크로드 모양대로 잽니다.
  • 큐 깊이를 쌓으면 IOPS와 지연이 같이 오릅니다. 직렬 워크로드의 성능은 큐 깊이 1의 지연이 정합니다.
  • SSD는 쓰기 증폭과 청소 때문에 채울수록, 그리고 시간이 지날수록 쓰기 성능이 변합니다. 여유 공간과 TRIM이 관리 수단입니다.
  • 클라우드 디스크는 물리 성능이 아니라 계약된 한도가 측정됩니다. 볼륨과 인스턴스 양쪽 한도를 봅니다.

다음 — RAID 운영 #

다음 글인 “하드웨어 중급 #6 RAID 운영의 실제"에서는 디스크 한 장에서 여러 장으로 갑니다. 기초 5편에서 RAID 레벨의 개념을 잡았다면, 이번에는 디스크가 실제로 죽은 다음의 이야기입니다. 리빌드가 왜 위험한 시간인지, 핫스페어와 스크럽이 무엇을 막아 주는지, 그리고 RAID가 백업이 아닌 이유를 운영 사건의 눈으로 다룹니다.

X