Home RDS IOPS 의 두 천장
Post
Cancel

RDS IOPS 의 두 천장

RDS IOPS 의 두 천장

운영하던 DB 하나가 특정 시간대에 눈에 띄게 느려지는 일이 있었다. 평소엔 멀쩡한데 배치가 몰리거나 트래픽이 튀는 구간에 응답이 쭉 늘어지고, 심하면 커넥션이 밀렸다. CPU도 메모리도 여유가 있는데 느렸다. 결국 병목은 디스크 I/O, 정확히는 IOPS였다.

IOPS를 올렸더니 안정됐는데, 그 과정에서 몇 가지를 새로 알게 됐다. IOPS가 정확히 뭔지, 왜 클라우드에선 디스크 성능에 제한이 걸려있는지, 그리고 gp3의 400GB라는 숨은 임계까지. 이 글은 그 정리다.

증상 - CPU는 노는데 느리다

CloudWatch 지표를 보니 그림이 명확했다.

  • ReadIOPS + WriteIOPS 합이 3,000 근처에 딱 붙어서 천장을 친다
  • DiskQueueDepth(디스크 대기 큐)가 평소 0~1이다가 그 구간에 확 치솟는다
  • ReadLatency / WriteLatency(지연)가 같이 급등한다
  • 반면 CPUUtilization 은 한가하다

이게 전형적인 IOPS 병목이다. 처리할 디스크 요청은 밀려드는데 디스크가 초당 받아줄 수 있는 양(IOPS)이 상한에 걸리니, 못 받은 요청이 큐에 쌓이고, 큐에서 기다리는 시간이 곧 지연으로 나타난다.

flowchart TD
    R[요청 급증] --> C{IOPS 상한 3,000 도달}
    C --> Q[디스크 큐에 요청이 쌓임]
    Q --> L[대기 시간 = 지연 급등]
    L --> S[응답 느려짐 · 커넥션 밀림]
    S -.->|악순환| R

IOPS 가 뭐냐 - 큐와 지연의 관계

IOPS(Input/Output Operations Per Second)는 말 그대로 초당 처리하는 디스크 읽기/쓰기 횟수다. 근데 이걸 큐·지연과 엮어서 보면 병목이 왜 생기는지가 확 와닿는다. 대략 이런 관계다.

1
IOPS = 큐 깊이(동시에 처리 중인 요청 수) ÷ 지연(요청 하나 처리 시간)

예를 들어 한 요청에 1ms 걸리는 디스크가 동시에 32개를 처리하면 초당 32,000번 처리한다. 여기서 핵심은, 디스크가 받아줄 수 있는 IOPS는 정해져 있는데 요청이 그걸 넘으면, 남는 요청은 큐에서 기다릴 수밖에 없고 그 대기가 지연으로 잡힌다는 거다. 그래서 IOPS가 모자라면 큐가 쌓이고 지연이 튄다. 우리가 본 증상 그대로다.

한 가지 더, IOPS와 처리량(throughput, MB/s)은 다른 축이다. DB는 보통 작은 블록(PostgreSQL 8KB, InnoDB 16KB 식이다)을 무수히 읽고 쓰니 IOPS가 중요하고, 반대로 큰 파일을 죽 읽는 작업(백업, 스트리밍)은 처리량이 중요하다. 우리처럼 OLTP성 DB가 느리면 십중팔구 IOPS 쪽이다.

왜 클라우드엔 디스크 성능에 제한이 있나

여기서 온프레미스만 다뤄본 사람은 좀 의아할 수 있다. 서버에 SSD 꽂으면 그 SSD의 물리 성능이 그냥 나오는데, 왜 클라우드는 IOPS에 상한이 있고 그걸 “올린다” 는 개념이 있을까.

이유는 클라우드 DB의 디스크가 물리적으로 붙어있는 게 아니라 네트워크 너머에 있기 때문이다. 온프레미스 SSD는 서버에 PCIe로 직결돼서, 디스크를 사는 순간 그 물리 성능이 고정으로 나온다. 반면 RDS가 쓰는 EBS는 인스턴스와 분리된 네트워크 스토리지다. 수많은 스토리지 서버를 묶은 풀에서 볼륨을 떼어주고, 모든 I/O가 네트워크를 타고 오간다.

그래서 클라우드에선 디스크 성능이 “물리적으로 정해진 것” 이 아니라 “얼마나 할당(프로비저닝)받았느냐” 가 된다. 성능을 돈 주고 사는 구조인 셈이다. 대신 네트워크 대역폭이라는 또 다른 천장이 생겨서, IOPS를 아무리 올려도 네트워크가 병목이 되면 거기서 막힌다. 물리 직결이 아닌 대가다.

gp2 냐 gp3 냐 io2 냐

RDS 스토리지 타입에 따라 IOPS를 정하는 방식이 다르다. 정리하면 이렇다.

  • gp2: 용량당 3 IOPS/GB라 용량과 IOPS가 묶여있다. 소용량은 burst로 버틴다. 최대 64,000.
  • gp3: baseline 3,000 IOPS / 125MB/s가 크기와 무관하게 깔린다. 그 위로 더 사려면 400GB부터다(미만은 3,000/125 고정). 최대 64,000.
  • io2: 순수 프로비저닝이라 용량과 무관하게 산다(IOPS:용량 비율 상한은 있다). 최대 256,000. 대신 비싸다.

옛날 기본값이던 gp2는 용량에 IOPS가 묶여있다. 100GB면 300 IOPS(3×100), 1TB면 3,000 IOPS 식이라, IOPS가 필요하면 안 쓰는 용량을 억지로 늘려야 했다(소용량은 burst로 잠깐 버티지만 지속되면 바닥난다). gp3는 이 종속을 풀어서 baseline 3,000을 크기와 무관하게 준다. 대신 그 위로 더 올리는 데는 400GB라는 조건이 붙는다(바로 아래에서 다룬다). 최대치는 둘 다 64,000인데, gp2로 그걸 보려면 용량이 21TB를 넘어야 한다(64,000 ÷ 3 ≈ 21,300GB). io2는 훨씬 높은 성능이 필요할 때(수만~수십만 IOPS) 쓰는데 비싸다. 우리는 gp3였다.

gp3 의 400GB 숨은 임계

그런데 gp3에서 IOPS를 올리려다 재밌는 걸 발견했다. gp3 baseline은 크기와 무관하게 3,000 IOPS인데, 용량이 400GB에 닿는 순간 baseline이 12,000 IOPS / 500MB/s로 자동으로 4배가 된다. IOPS를 따로 사지 않아도 그렇다.

이유는 볼륨 striping이다. 400GB부터 RDS가 내부적으로 볼륨을 4개로 쪼개 나란히 붙이는데(striping), 4개가 병렬로 I/O를 받으니 baseline이 4배가 되는 것이다. (PostgreSQL·MySQL·MariaDB·Db2 기준 400GB, Oracle은 200GB다. SQL Server만 striping을 안 해서 임계 자체가 없고, 대신 크기와 상관없이 IOPS를 프로비저닝할 수 있다.) 참고로 이 400GB 볼륨 분할은 gp3 전용 규칙이 아니라 RDS SSD 스토리지 공통이다. gp2도 같은 지점에서 burst IOPS가 3,000에서 12,000으로 뛴다.

flowchart LR
    subgraph SM [400GB 미만]
        direction LR
        A1[단일 볼륨] --> A2[baseline 3,000 IOPS]
    end
    subgraph LG [400GB 이상]
        direction LR
        B1[4개 볼륨 striping] --> B2[baseline 12,000 IOPS · 자동 4배]
    end

우리 상황이 딱 여기 걸렸다. 원래 100GB짜리였는데, IOPS를 올리려고 보니 아예 방법이 없었다. RDS gp3는 400GB 미만이면 IOPS를 못 올리게 막아둔다. 3,000/125가 유일한 스펙이라, 콘솔에서 IOPS 입력칸 자체가 안 열린다. 즉 gp3 안에 머무는 한 IOPS를 올리는 길은 용량을 400GB로 키우는 것뿐이다. (gp3를 벗어나면 io2로 바꾸는 수가 있다. io2는 100GB에서도 IOPS를 따로 살 수 있다. 대신 비싸서 우리 규모엔 과했다.) 그래서 400GB로 올렸더니, IOPS를 따로 프로비저닝하지 않고도 striping 덕에 baseline이 12,000으로 뛰었다. IOPS만 올리고 싶었는데 용량을 올려야 했던 게, 알고 보니 강제이자 이득이었다. 400GB에 닿는 순간 성능 4배가 딸려왔으니까.

그래서 380GB에서 “IOPS 좀 올리고 싶은데” 하고 있으면 손댈 데가 없어 답답한데, 400GB만 채우면 4배가 그냥 딸려온다. “용량은 남는데 왜 굳이 400GB까지 올려” 싶어도, gp3에선 용량이 곧 IOPS로 들어가는 문인 셈이다.

올리는 것 자체가 공짜는 아니다

첫째, 이 변경은 가벼운 조작이 아니다. 볼륨이 1개에서 4개로 바뀌는 거라 RDS가 Elastic Volumes로 슥 늘려주는 게 아니라, 새 볼륨을 만들어 데이터를 통째로 옮긴다. AWS 문서가 이걸 따로 경고한다. 이 작업은 기존 볼륨과 새 볼륨 양쪽의 IOPS와 처리량을 상당히 잡아먹는다. I/O 지연이 크게 오르고, 몇 시간이 걸릴 수 있다. 그동안 인스턴스는 Modifying 상태에 머문다. 그래서 AWS는 이 작업을 피크 시간대를 피해 잡으라고 권한다. 대안도 같이 준다 — 읽기 복제본을 만들어 거기서 용량을 올린 다음 승격시키면, 프라이머리는 그동안 멀쩡하다. 우리는 운 좋게 한가한 시간에 작업을 걸었는데, 알고 한 게 아니었다. I/O 때문에 느린 DB를 들고 낮에 눌렀으면 몇 시간 동안 더 느려질 뻔했다. 덧붙여 스토리지 autoscaling을 켜뒀다면 시점을 고를 수조차 없다. autoscaling이 400GB를 넘기는 순간 같은 작업이 그대로 돈다.

둘째, 용량 요금이 늘린 만큼 오르고, 무르기가 번거롭다. 무료인 건 baseline 성능 상향분이지 용량이 아니다. 우리는 100GB에서 400GB로 갔으니 스토리지 요금이 4배가 됐다. 그리고 RDS는 modify-db-instance 로는 스토리지를 줄이지 못한다. 굳이 줄이려면 blue/green으로 용량이 더 작은 green을 만들어 승격시켜야 한다. 못 되돌리는 건 아닌데, 올릴 때만큼 간단하지도 않다. io2로 갈아타거나 인스턴스를 키우는 것보다는 쌌지만, 이건 비교를 해보고 고를 일이지 공짜라서 고를 일이 아니었다.

그 대가를 치르고 나서, 문제의 시간대를 다시 지켜봤다. DiskQueueDepth 가 더는 안 치솟고 0~1 언저리를 유지했고, ReadLatency/WriteLatency 도 평평해졌다. 3,000에 눌려 평평하던 IOPS 그래프가 실수요만큼 올라갔다 내려오는 모양으로 바뀌었다. CPU가 놀면서 느리던 그 현상이 사라졌다.

끝난 줄 알았는데 온 경고

이걸로 끝인 줄 알았는데, 얼마 뒤 AWS가 권장사항 알림을 하나 보냈다. 항목 이름은 RDS instances are under-provisioned for system IOPS capacity, 요지는 “이 인스턴스는 프로비저닝된 IOPS를 지원할 수 없으니, 워크로드를 줄여 IOPS를 낮추거나 인스턴스를 스케일업하라” 였다. 스토리지는 분명 12,000으로 올려놨는데 인스턴스가 못 따라온다는 얘기였다.

알고 보니 IOPS 천장이 하나가 아니었다. 지금까지 얘기한 12,000은 스토리지(gp3) 쪽 천장이다. 그런데 그 스토리지가 붙은 인스턴스 자체에도, EBS로 밀어낼 수 있는 IOPS 한도가 따로 있다. 실제로 나오는 IOPS는 이 둘 중 낮은 쪽에서 막힌다. 스토리지를 12,000으로 올려도 인스턴스가 그만큼 못 밀어내면 소용이 없는 거다.

t4g 는 EBS 도 burst 다

우리 인스턴스는 db.t4g.xlarge였다. t 계열은 버스터블(burstable) 인스턴스로 유명한데, 보통 CPU 얘기로만 안다. 평소엔 baseline만 쓰다가 부하가 잠깐 튈 때 burst로 올라가는 그거다. 근데 잘 안 알려진 게, 이 인스턴스는 EBS 성능도 baseline과 burst로 나뉜다는 점이다. (CPU 크레딧과는 별개로 관리되는 값이다.)

db.t4g.xlarge의 EBS 스펙은 이렇다.

  • baseline IOPS: 4,000 (지속적으로 보장되는 값)
  • 최대 IOPS: 15,700 (짧게 burst로만 가능)

baseline만 보면 인스턴스가 스토리지 성능을 한참 못 받쳐준다. 스토리지는 12,000을 주는데 인스턴스 baseline이 4,000이고, 처리량도 스토리지 500MB/s에 인스턴스 baseline이 87MB/s다. “아 그래서 병목이구나, 스케일업해야겠네” 싶었다. 근데 burst 크레딧을 실측해보고 생각이 바뀌었다.

t 인스턴스엔 burst 잔량을 보여주는 지표가 두 개 있다. EBSIOBalance%(IOPS 크레딧)와 EBSByteBalance%(처리량 크레딧)다. 크레딧이 남아있으면 baseline을 넘겨도 burst로 낼 수 있고, 0에 가까우면 baseline으로 주저앉는다. 지난 7일 최저값을 떠봤다.

절반은 맞고 절반은 틀렸다. IOPS 크레딧은 최저 97%, 처리량 크레딧은 최저 6%였다. IOPS는 피크가 10,947이었어도 그 순간이 워낙 짧아 크레딧을 거의 안 썼다. 인스턴스가 스토리지를 못 받쳐준다는 진단 자체는 맞았는데, 축이 IOPS가 아니라 처리량이었던 거다. 정작 바닥을 친 건 처리량 크레딧이고, 그것도 대량 조회가 돌던 특정 하루만 6%까지 갔지 나머지 6일은 멀쩡했다.

여기서 교훈이 나왔다. 경고가 지목한 축은 IOPS였는데, 실제로 바닥을 친 건 처리량이었다. 방향은 맞고 항목이 틀린 셈이다. AWS 권장사항에는 IOPS용(under-provisioned for system IOPS capacity)과 처리량용(under-provisioned for throughput capacity)이 따로 있는데, 우리가 받은 건 IOPS 쪽이었다. 그 경고가 권한 건 “워크로드를 튜닝해 IOPS 수요를 낮춰라” 아니면 “인스턴스를 키워라” 둘이다. 앞엣것은 병목을 없애는 게 아니라 피하는 쪽이고(400GB gp3에선 12,000이 하한이라 프로비저닝을 줄이는 선택지는 애초에 없다), 뒤엣것을 이름만 보고 급하게 따랐다면 IOPS 스펙만 보고 인스턴스를 골랐다가 정작 처리량은 그대로였을 수 있다. 어느 쪽이든 크레딧 지표를 직접 안 떠봤으면 엉뚱한 데 돈을 썼을 거다.

여기서 이 97%를 잘못 읽기 쉬우니 한 번 짚고 간다. “용량의 97%가 논다” 는 뜻이 아니다. 피크 10,947은 스토리지 12,000의 91%를 쓴다. 크레딧이 안 닳았다는 건 baseline 4,000을 넘긴 시간이 짧았다는 뜻이지, 여유가 많다는 뜻이 아니다. 분모가 다른 숫자다.

그래서 결론은 “지금은 아무것도 안 한다” 였다. IOPS 쪽은 지속 초과가 없으니 당장 손댈 데가 없고, 400GB gp3에선 12,000이 딸려오는 하한이라 줄이고 싶어도 못 줄인다. 남은 건 처리량인데, 7일 중 하루 6%까지 떨어진 걸 봤을 뿐이라 이게 매주 오는 패턴인지 그날 한 번인지 아직 모른다. 관측 1회로 주기를 정할 수는 없으니 EBSByteBalance% 에 알람을 걸고 몇 주 더 보기로 했다.

나중에 인스턴스를 옮긴다면 사이즈를 잘 봐야 한다. “r6g나 m6g로 가면 되지” 가 함정인데, r6g.large·m6g.large 는 vCPU도 절반이고 처리량 baseline이 78.75MB/s로 지금 쓰는 t4g.xlarge(87MB/s)보다 오히려 낮다. 비용은 오르는데 크레딧은 더 빨리 마른다. 개선되려면 최소 xlarge(148.5MB/s)여야 하고, 아예 burst에 안 기대려면 4xlarge 이상으로 가야 한다. EBS baseline·burst가 갈리는 건 t 계열만의 얘기가 아니라서, m·r 계열도 작은 사이즈는 똑같이 크레딧으로 버틴다. 인스턴스 클래스를 고를 땐 vCPU·메모리뿐 아니라 EBS 표까지 봐야 한다는 걸 이번에 배웠다.

정리

  • CPU·메모리는 여유인데 느리면 디스크 IOPS 병목을 의심한다. DiskQueueDepth 급등 + ReadLatency/WriteLatency 급등 + IOPS가 천장에 붙는 조합이 신호다.
  • IOPS는 초당 I/O 횟수고, IOPS = 큐깊이 / 지연 이다. IOPS가 모자라면 큐가 쌓이고 그 대기가 지연으로 나타난다. (작은 블록 DB는 IOPS, 큰 순차 작업은 처리량)
  • 클라우드 DB 디스크(EBS)는 네트워크 스토리지라, 물리 SSD와 달리 성능을 프로비저닝(구매)한다. 대신 네트워크가 또 다른 천장이다.
  • gp2는 용량당 IOPS(종속), gp3는 baseline 3,000 + 400GB 이상에서 독립 조절, io2는 초고성능. gp3의 baseline은 400GB에 닿으면 striping으로 12,000까지 자동 4배가 된다.
  • IOPS가 모자라면 무작정 프로비저닝하기 전에 용량이 400GB 근처인지부터 본다. 임계 하나로 4배가 갈린다. 대신 그 확장은 볼륨을 4개로 재구성하는 몇 시간짜리 I/O 집약 작업이고, 용량 요금은 늘린 만큼 오른다. 피크를 피하거나, 읽기 복제본에서 하고 승격한다.
  • IOPS 천장은 스토리지와 인스턴스 두 곳에 있고, 실제로 지속되는 성능은 둘 중 낮은 쪽이다. 인스턴스 burst는 크레딧으로 버티는데, EBSIOBalance%·EBSByteBalance% 가 한 번이라도 바닥에 가까워지면 무시할 게 아니라 알람을 걸 신호다.
  • AWS 권장사항은 경고 이름(IOPS)과 한계에 더 가까웠던 축(처리량)이 다를 수 있다. 곧이곧대로 스케일업하지 말고, 크레딧 지표를 직접 보고 판단한다. 이름만 보고 움직이면 엉뚱한 데 돈 쓴다.

온프레미스에서 디스크는 사면 끝인 물리 부품이었는데, 클라우드에선 용량·IOPS·처리량이 따로 도는 손잡이 세트가 됐다. 편해진 만큼 400GB 임계 같은 규칙을 알아야 헛돈을 안 쓴다. 결국 성능 튜닝은 “어느 손잡이가 지금 병목이냐” 를 정확히 짚는 일이더라.

This post is licensed under CC BY 4.0 by the author.