실험 목록으로 돌아가기
E002완료P0

OLTP 처리량과 지연의 한계

DSQL의 SLO 용량·지연은 각 대조군의 몇 배인가?

결과 요약

이 실험은 같은 주문 업무를 같은 지연 목표(SLO)로 실행할 때 DSQL이 대조군보다 얼마나 많은 요청을 처리하고 지연이 어떻게 다른지 확인했습니다. 업무는 상품 조회 40%, 주문 이력 조회 30%, 주문 생성 20%, 주문 취소 10%로 섞었고, SLO는 읽기 p95 50 ms·p99 100 ms, 쓰기 p95 100 ms·p99 200 ms, 기술적 실패율 0.1% 이하입니다. 실험은 두 번에 나눠 실행했습니다. 2026-09-28에는 네 서비스를 함께 띄워 파일럿과 동시성 탐색을 했지만 DSQL 탐색이 초기화 지연과 예산으로 멈췄고, 2026-09-29에 DSQL만 새로 띄워 셀마다 데이터를 지우지 않는 방식으로 다시 측정했습니다.

연결 수를 고정하고 쉬지 않고 요청을 보내는 방식(closed-loop)에서 DSQL은 연결 64개로 6,680 TPS, 연결 256개로 24,541 TPS를 처리하며 모두 SLO를 지켰습니다. 연결 256개에서도 주문 생성 p99는 35.7 ms로 연결 64개(30.4 ms)와 거의 같았습니다. 같은 방식의 대조군 측정(2026-09-28)에서는 연결 256개에서 세 대조군 모두 쓰기 지연이 SLO를 넘었고, SLO를 지킨 최고 처리량은 RDS Multi-AZ(R1) 5,839 TPS, Aurora Provisioned(A1) 3,375 TPS, Aurora Serverless v2(A2) 11,559 TPS였습니다. 연결 256개 조건에서 DSQL은 A2의 약 2.1배, R1의 약 4.2배, A1의 약 7.3배를 처리했습니다. 이때 부하 발생기 CPU가 80.8%로 포화 기준(85%)에 가까워, DSQL 값은 서비스 한계가 아니라 이번 측정의 하한입니다.

지연의 절대값은 DSQL이 길었습니다. 같은 1,600 TPS에서 DSQL의 주문 생성 p95는 41.0 ms(2026-09-28), A2는 6.8 ms였고, 2026-09-29 측정에서 DSQL 쓰기 p95는 약 28–30 ms였습니다. 요청을 정해진 도착률로 보내는 방식(open-loop)에서는 DSQL이 6,750 TPS까지 SLO를 지켰고 8,100 TPS에서 p99가 200 ms를 넘었습니다. 다만 이 실패는 DSQL의 처리 지연(p95 약 28 ms로 변화 없음)이 아니라 부하 발생기가 프로세스마다 나눠 쓰는 연결을 기다린 시간(대기 p99 약 190 ms) 때문이어서, open-loop 한계는 측정 도구의 연결 풀 한계로 해석합니다. 모든 셀에서 업무 불변식 위반은 0건이었습니다. 계획한 공통 도착률 본 측정과 반복은 하지 못했으므로 서비스 간 차이는 1회 측정 값입니다.

프로덕션 사용 관점

처리량 측면에서 DSQL은 이번 실험에서 가장 크게 확장되었습니다. 연결 256개에서 초당 2만 4천 건 이상의 주문 혼합 업무를 지연 증가 없이 처리했고, 같은 조건에서 기존 서비스는 모두 지연 목표를 넘었습니다. 용량 계획 없이 부하가 커져도 버틴다는 점은 트래픽 변동이 큰 서비스에 유리합니다. 대신 요청 하나의 지연은 Aurora보다 3–6배 길어(쓰기 p95 약 30–40 ms), 요청마다 여러 쿼리를 순서대로 실행하는 화면은 응답 시간이 늘어납니다. 같은 처리량을 내려면 연결과 동시성을 더 많이 써야 하고, 높은 처리량을 계속 유지하면 DPU 요금이 커집니다(E010).

확인할 질문

DSQL의 SLO 용량·지연은 각 대조군의 몇 배인가?

연결 256개 고정 동시성 기준으로 답할 수 있습니다: DSQL의 SLO 통과 처리량은 A2의 약 2.1배 이상, R1의 약 4.2배 이상, A1의 약 7.3배 이상입니다. 지연은 반대로 DSQL이 3–6배 깁니다. 공통 도착률(0.5/1.0/1.2 × Qref) 본 측정은 하지 않았습니다.

실행 조건

  • 리전과 일시: 서울(ap-northeast-2). 1차(네 구성) 2026-09-28 03:34–12:07 UTC, 2차(DSQL 단독) 2026-09-29 08:11–11:10 UTC.
  • 구성:
    • D1: Aurora DSQL 단일 리전, IAM 토큰 인증, 퍼블릭 엔드포인트.
    • R1: RDS for PostgreSQL 16, db.r6g.xlarge Multi-AZ, gp3 400 GiB.
    • A1: Aurora PostgreSQL 16 Provisioned, db.r6g.xlarge writer 1 + 다른 AZ reader 1, Aurora Standard.
    • A2: Aurora PostgreSQL 16 Serverless v2, writer 1 + reader 1, 각 4–32 ACU, Aurora Standard.
  • 데이터: 고객 100만, 상품 20만, 주문 1,100만, 주문 품목 2,750만, 원장 1,100만 행(약 5 GiB). D1의 적재 행 수는 DB에서 직접 세어 설계값과 일치함을 확인했습니다.
  • 업무와 판정: 위 혼합 업무, 명시적 REPEATABLE READ, 재시도 정책 공통. 기술적 실패율 0.1% 이하와 SLO를 모두 지켜야 통과입니다. 셀마다 재고·주문·원장 불변식을 검사했습니다.
  • 부하 발생기: 구성마다 Spot 러너 1대. 파일럿은 c7g.4xlarge, 탐색은 Spot 용량 부족과 회수로 네 구성 모두 c6g.4xlarge를 썼습니다. 발생기 CPU는 최대 69.7%(A2 연결 256)로 포화 기준(85%) 아래였습니다.
  • 셀 시간: 파일럿 워밍업 60 s + 측정 120 s, 탐색과 2차 측정 워밍업 120 s + 측정 300 s, 각 1회.
  • 2차 측정(DSQL 단독): 새 DSQL 클러스터에 같은 데이터를 적재하고, 셀이 만든 행을 지우지 않고 쌓았습니다. 정합성은 셀마다 그 셀이 만든 행만 골라 검사했습니다(셀마다 따로 받은 ID 범위와 업무 ID 접두어). 셀마다 주문이 약 1%씩 늘어납니다. 러너는 c6g.4xlarge로 시작했다가 Spot 회수로 m7g.4xlarge로 교체했고, 연결 64·256과 5,400 TPS 이상 셀은 m7g.4xlarge에서 실행했습니다. 대조군은 2026-09-28 결과를 그대로 비교에 썼습니다.
  • 계획과 달라진 점:
    • 본 측정(0.5/1.0/1.2 × Qref)과 경계 탐색은 실행하지 못했습니다.
    • 비용 상한을 USD 50에서 60으로 올렸습니다(2026-09-28 사용자 결정).
    • 대조군과 DSQL의 탐색은 서로 다른 날, 다른 러너 사양(c6g.4xlarge 대 m7g.4xlarge)과 다른 러너 위치에서 실행했습니다. DSQL의 RTT는 1차 2.7–5.1 ms, 2차 1.6–1.8 ms였고 쓰기 지연도 2차가 약 30% 짧았습니다.
    • 비용 상한: 1차 USD 60(2026-09-28 사용자 결정), 2차 USD 20(2026-09-29 사용자 결정).

성능 결과

고정 도착률 파일럿 (open-loop)

p95 / p99, 단위 ms. 모든 셀에서 실패율 0, 불변식 위반 0건, SLO 통과.

구성 도착률 상품 조회 주문 이력 주문 생성 주문 취소 RTT
D1 100 10.5 / 12.3 14.3 / 16.1 47.6 / 55.2 50.0 / 59.2 2.8
D1 400 10.3 / 11.5 13.2 / 14.3 47.6 / 53.6 48.1 / 54.2 3.0
D1 1600 9.1 / 10.6 11.7 / 13.3 41.0 / 46.2 41.4 / 47.6 5.1
A2 100 3.6 / 4.5 9.9 / 11.8 13.1 / 16.2 7.6 / 9.4 0.3
A2 400 2.4 / 2.8 3.9 / 5.2 9.3 / 11.4 9.0 / 10.9 0.1
A2 1600 2.2 / 2.7 2.6 / 3.2 6.8 / 7.9 6.9 / 8.1 0.1

A2의 writer는 1600 TPS에서 12 ACU를 사용했습니다. D1은 요청 시도 1회당 약 0.0375 DPU를 소모했습니다.

동시성 고정 탐색 (closed-loop)

성공 처리량(TPS)과 SLO 판정. 모든 셀에서 실패율 0, 불변식 위반 0건. 대조군은 2026-09-28, D1 연결 64·256은 2026-09-29 측정입니다.

구성 연결 16 연결 64 연결 256 SLO를 지킨 최고 처리량
D1 1,006 통과(1차) 6,680 통과 24,541 통과 24,541 이상(하한)
R1 3,065 통과 5,839 통과 5,347 초과 5,839
A1 3,375 통과 3,322 초과 4,254 초과 3,375
A2 5,657 통과 11,559 통과 15,115 초과 11,559
  • D1 연결 256의 p95 / p99(ms): 상품 조회 5.6 / 6.4, 주문 이력 8.3 / 9.5, 주문 생성 30.4 / 35.7, 주문 취소 28.9 / 32.9. 연결 64(4.9 / 5.5, 7.6 / 8.4, 27.8 / 30.4, 27.0 / 29.8)와 거의 같아, 연결을 4배로 늘려도 지연이 거의 늘지 않았습니다. 이때 러너 CPU는 80.8%였습니다.
  • D1 연결 256의 시도 처리량은 26,285 TPS로 성공 처리량보다 약 7% 많았습니다. 차이는 커밋 시점 충돌(40001) 뒤 재시도한 시도입니다.
  • A1 연결 64는 주문 이력 조회 p99가 178.7 ms로 읽기 SLO(100 ms)를 넘었습니다.
  • 연결 256에서는 세 대조군 모두 쓰기 p95가 쓰기 SLO(100 ms)를 넘었습니다(주문 생성 p95: R1 163.4 ms, A1 250.7 ms, A2 122.5 ms).
  • D1 연결 16(1차)은 1,006 TPS로 낮았는데, 1차 측정의 D1 지연이 2차보다 길었고(RTT 2.7–5.1 ms) 연결 수가 적어 요청당 지연이 처리량을 제한했기 때문입니다.

도착률 고정 상향 (open-loop, DSQL 단독)

도착률 성공 TPS 판정 주문 생성 p95 / p99 연결 대기 p99
2,400 2,329 통과 - -
3,600 3,493 통과 - -
5,400 5,202 통과 - -
6,750 6,416 통과 27.8 / 101.4 26.5 ms
8,100 7,758 초과 28.1 / 255.7 189.7 ms
  • 8,100 TPS에서 p99가 모든 업무에서 200 ms를 넘었지만, 대기 시간을 뺀 처리 지연(p95 약 28 ms)은 변하지 않았습니다. 러너는 연결 256개를 16개 프로세스에 16개씩 나눠 쓰므로, 도착이 몰린 프로세스에서 요청이 연결을 기다렸습니다. 따라서 open-loop 6,750 TPS는 DSQL의 한계가 아니라 이번 측정 도구의 연결 풀 한계로 해석합니다.
  • 요청 시도 1회당 DPU는 0.029–0.032로(2,400 TPS 0.032, 26,285 TPS 0.029), 파일럿 추정 0.0375보다 약간 낮았습니다.

개발·운영 편의성

  • 대량 삭제가 느림: 셀이 만든 행을 되돌리는 초기화에서, D1은 약 13만 건의 영수증과 관련 행(주문·품목·원장·재고 복원 합계 약 60만 행 변경)을 처리하는 데 6–7분이 걸렸습니다. 처음 구현은 배치마다 대상 조회를 다시 실행해 27분 제한을 넘겼고, 대상을 한 번만 조회하도록 고친 뒤에도 연결 64 셀의 초기화는 제한 시간 안에 끝나지 않았습니다. 대조군은 같은 초기화가 셀 시간에 문제를 주지 않았습니다.
  • 외래 키 역방향 인덱스: 주문을 지울 때 원장의 order_id에 인덱스가 없어 매 삭제가 원장 전체를 훑었고, D1은 트랜잭션 300초 한도(ProgramLimitExceeded)에 걸렸습니다. 이 문제는 A2에서도 24분 걸린 삭제로 나타났습니다. 인덱스를 추가해 해결했습니다.
  • 비동기 인덱스: DSQL의 CREATE INDEX ASYNC로 만든 인덱스는 빌드가 끝나기 전부터 pg_indexes에 보였습니다. pg_index.indisvalid가 참이 될 때까지 기다리도록 도구를 고쳤습니다.
  • 일시 오류: 적재 중 XX000 server unavailable이 발생해 재시도 대상에 추가했습니다. 트랜잭션당 3,000행 한도에 맞춰 적재와 삭제를 나눴습니다.
  • 연결 관리: DSQL 연결은 1시간 한도가 있어 50분마다 다시 연결했고, IAM 토큰은 10분 캐시했습니다.
  • 동시 토큰 서명: 2차 측정에서 연결 256개가 동시에 IAM 토큰을 서명하려다 인스턴스 자격 증명 조회가 실패(NoCredentialsError)했습니다. 프로세스마다 서명을 한 번만 하도록 잠금을 걸고, 일시적 자격 증명 오류를 재시도하도록 고쳤습니다.
  • 초기화 대신 누적: DSQL의 대량 삭제가 느린 문제를 피하려고 2차 측정은 셀이 만든 행을 지우지 않았습니다. 처음에는 Spot 회수로 중단된 시도가 남긴 행을 재시도가 자기 행으로 세는 문제가 있어, 시도마다 고유한 ID 범위를 쓰도록 고쳤습니다.

비용

1차 측정(네 구성, 2026-09-28): 추정 비용은 실행 중 하네스가 사용량과 단가로 계산한 값이고, 실제 청구액은 2026-09-29에 Cost Explorer(서울 리전, 2026-09-28 UTC, 사용 유형별)로 확인한 값입니다. 실험은 모두 2026-09-28 UTC 안에 실행되었습니다. 실험 전날에도 발생하던 계정 상시 요금(로드 밸런서, S3, 기존 공인 IPv4 등 하루 약 USD 2.9)은 제외했습니다. 조회 시점의 Cost Explorer 값은 월 마감 전 추정 상태입니다.

항목 추정 비용 실제 청구액 사용량과 차이
D1 DPU 약 $10.7 $11.49 1,148,488 DPU. 하네스의 마지막 측정 뒤 사용량(중단한 연결 64 셀, 삭제 전후)이 추정에 빠졌습니다.
A2 컴퓨팅 약 $31.3 $23.99 119.9 ACU-시간. 추정은 측정되지 않은 구간을 최대 ACU로 계산해 높았습니다.
Aurora I/O (A1+A2) 약 $4.7 $6.53 2,720만 I/O(Aurora Standard). 마지막 측정 뒤 I/O가 추정에 빠졌습니다.
A1 인스턴스 약 $2.9 $2.43 db.r6g.xlarge 3.90 인스턴스-시간(writer+reader)
R1 약 $2.7 $2.12 Multi-AZ 1.72 시간 + gp3 스토리지
러너·기타 약 $0.9 $2.03 Spot 러너 13.95 시간($1.68), AZ 간 전송, 공인 IPv4, EBS
합계 약 $53 $48.57 추정의 약 92%. 상한 USD 60, 가드 USD 54 이내

2차 측정(DSQL 단독, 2026-09-29): CloudWatch TotalDPU 합계 1,784,023 DPU(약 $17.84)와 Spot 러너 약 $0.73으로 약 $18.6을 추정했고, 2026-09-30에 Cost Explorer로 확인한 실제 청구액은 $18.54입니다(DSQL DPU $17.84, Spot 러너 $0.70). 같은 날 실행한 E005·E006·E007·E009 실행과 DSQL DPU가 한 줄로 합쳐 청구되어 클러스터별 CloudWatch DPU로 나눴는데, 두 실행의 CloudWatch 합계(1,818,111 DPU)가 청구된 DPU(1,818,110)와 일치했습니다. 이 가운데 데이터 적재가 517,021 DPU(약 $5.17), 상향·closed-loop 측정이 1,093,524 DPU(약 $10.94)이고, 나머지는 같은 클러스터에서 실행한 E003·E008·E012입니다. 두 실행이 함께 쓴 AZ 간 전송·EBS·공인 IPv4 약 $0.65는 어느 한쪽에 나누지 않았습니다.

  • 이 달의 DSQL 무료 사용량(10만 DPU)은 E004에서 모두 썼으므로, D1 DPU는 무료 사용량 차감 없이 청구됩니다.
  • A2는 탐색에서 15,000 TPS 부하를 받으며 ACU가 크게 늘었고, 파일럿 뒤 결정을 기다리는 동안의 유휴 시간도 포함되어 가장 큰 비용(전체의 약 49%)이 되었습니다.
  • Aurora Standard의 I/O 요금이 $6.53으로, A1 인스턴스 요금보다 컸습니다. 부하가 큰 실험에서는 I/O-Optimized가 더 저렴할 수 있습니다(이번에는 비교하지 않았습니다).

결론과 한계

  • 처리량: 연결 256개 고정 동시성에서 DSQL은 24,541 TPS 이상을 SLO 안에서 처리했고, 대조군의 SLO 통과 최고 처리량(R1 5,839, A1 3,375, A2 11,559 TPS)보다 2.1–7.3배 많았습니다. DSQL 값은 부하 발생기 한계에 가까운 하한입니다.
  • 지연: DSQL의 쓰기 p95는 약 28–41 ms로 Aurora Serverless v2(약 7–9 ms)보다 3–6배 길었지만, 부하가 커져도 거의 늘지 않았습니다.
  • 측정하지 못한 것: 공통 도착률(0.5/1.0/1.2 × Qref) 본 측정, 경계 탐색, 반복 간 편차, 러너 여러 대로 DSQL의 실제 상한.
  • 한계: 모든 셀은 1회 실행입니다. DSQL과 대조군의 탐색이 다른 날, 다른 러너 사양으로 실행되었습니다. DSQL은 퍼블릭 엔드포인트, 대조군은 VPC 내부 경로입니다. 2차 측정은 셀마다 데이터가 약 1%씩 늘어나는 조건입니다.

정리 기록

  • 생성 리소스:
    • BATCH: VPC, 서브넷 2개, IGW, 보안 그룹 2개, DB 서브넷 그룹, IAM 역할·인스턴스 프로파일.
    • 구성별: D1 클러스터, R1 인스턴스, A1·A2 클러스터와 writer·reader, RDS 관리 시크릿 3개.
    • Spot 러너: 교체를 포함해 여러 대. Spot 회수 2회(06:45 UTC에 2대, 08:54–08:59 UTC에 시작한 3대)와 용량 부족으로 교체했습니다.
  • A2 재생성: 파일럿 뒤 비용을 줄이려고 A2를 삭제했다가 본 단계에서 다시 만들어 적재했습니다.
  • 삭제 완료 시각 (UTC): R1·A1·A2 10:57:45, D1 12:06:31, BATCH 12:06:45. 삭제에 실패한 리소스는 없습니다.
  • 검증 (12:07:44 UTC): e002.py verify의 remaining_count=0입니다. 첫 검증(12:06:55)에서는 종료된 러너의 Spot 요청 1건이 active 상태로 남아 1이 나왔고, 요청을 취소한 뒤 다시 검증했습니다. 정리 코드가 Spot 요청을 직접 취소하지 않는 점은 수정 과제입니다.
  • 2차 측정(2026-09-29, e002-20260929t081139z-4dce): BATCH 네트워크·IAM, DSQL 클러스터 1개, Spot 러너 2대(c6g.4xlarge는 08:33 UTC Spot 회수, 교체 m7g.4xlarge)를 만들었습니다. 같은 클러스터에서 E003·E008·E012를 실행한 뒤 11:08–11:10 UTC에 삭제했고, e002.py verify의 remaining_count=0(11:10:30 UTC)을 확인했습니다. 러너 종료 시 Spot 요청도 함께 취소하도록 고친 뒤라 남은 Spot 요청은 없었습니다. 수동 교차 확인으로 DSQL 클러스터 0개, 실험 태그 VPC 0개, 실험 IAM 역할 0개, 열린 Spot 요청 0개를 확인했습니다.