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

트랜잭션 경합과 데이터 정합성

DSQL의 경합·재시도는 정합성·지연·구현량에 어떤 영향을 주는가?

결과 요약

이 실험은 같은 행을 여러 요청이 동시에 갱신할 때 DSQL과 대조군이 정합성을 지키는지, 그리고 충돌과 재시도가 지연과 실패율에 어떤 영향을 주는지 확인했습니다. 주문 생성과 계좌 이체 업무를 동시성 16/64/256, 균등 접근과 인기 키 집중 접근으로 나눠 실행하고, 부하 셀마다 재고·잔액·중복 처리 불변식을 검사했습니다. 66개 부하 셀 전부에서 불변식 위반은 0건이었습니다. 네 서비스 모두 조건부 갱신, 업무 ID 영수증, 재시도, 커밋 여부 확인을 구현하면 이중 처리나 커밋 유실 없이 동작했습니다.

경합을 처리하는 방식은 크게 달랐습니다. DSQL은 잠금 대기 없이 커밋 시점에 충돌(40001)을 알립니다. 그래서 상위 1% 키에 요청의 80%가 몰리는 집중 경합에서 커밋에 성공한 요청의 p99는 110 ms 이하로 짧았지만, 동시성 256의 충돌률은 67–71%였고 최대 3회 재시도 뒤에도 39%가 최종 실패했습니다. 대조군은 행 잠금으로 요청을 줄 세웠고, 동시성 64와 256의 집중 경합에서는 대기가 2초 마감을 넘겨 성공 처리량이 24–512 TPS로 떨어지고 최종 실패율이 18–63%가 되었습니다. DSQL은 READ COMMITTED를 지원하지 않으므로 재시도 구현이 필수이며, 인기 상품처럼 경합이 몰리는 업무는 재시도 예산과 경합을 분산하는 스키마 설계를 함께 검토해야 합니다.

비용 상한(당시 USD 5, 이후 50으로 상향) 때문에 대조군을 버스터블 소형 인스턴스로 줄였고, 셀은 25초씩 한 번만 실행했습니다. 따라서 처리량 차이는 서비스 비교 근거로 쓰지 않으며, 고정 동시성 방식이라 요청이 밀리는 동안의 지연이 빠질 수 있습니다. 계획 규모의 용량과 지연 비교는 E002에서 요청률을 고정하는 방식으로 다시 측정합니다. 사용량으로 계산한 추정 비용은 약 USD 3.4였고, Cost Explorer로 확인한 실제 청구액은 약 USD 2.07입니다(DSQL 월 무료 사용량 차감이 주된 차이).

프로덕션 사용 관점

재시도와 업무 ID 영수증(멱등 처리)을 구현하면 DSQL도 경합 상황에서 이중 처리나 커밋 유실 없이 정합성을 지켰으므로, 정합성 자체는 프로덕션 도입의 걸림돌이 아니었습니다. 경합이 분산된 업무에서는 충돌률이 기존 서비스와 비슷했고 재시도로 대부분 해소되었습니다. 그러나 인기 상품 재고처럼 소수 행에 쓰기가 몰리면 재시도 뒤에도 실패가 크게 남으므로(동시성 256에서 39%), 이런 업무는 재고 행 분할 같은 스키마 재설계 없이는 그대로 옮기기 어렵습니다. 트랜잭션당 3,000행 한도 때문에 대량 삭제·정리 작업을 나눠야 했고, 서버 오류도 한 번 관측되어 재시도가 필수입니다. 소형 대조군·1회 반복의 결과라 용량 판단은 E002에서 합니다.

확인할 질문

DSQL의 경합·재시도는 정합성·지연·구현량에 어떤 영향을 주는가? 같은 주문·이체 업무를 D1(Aurora DSQL)과 대조군 3종(R1 RDS PostgreSQL Multi-AZ, A1 Aurora Provisioned, A2 Aurora Serverless v2)에서 실행했습니다. 두 가지를 확인했습니다.

  • 격리 수준 시나리오: 두 연결의 실행 순서를 barrier로 고정해 이상 현상 5종을 재현하고, 각 서비스가 이를 막는지 봤습니다.
  • 경합 부하: 동시성과 접근 분포를 바꿔 가며 부하를 걸고, 셀마다 업무 불변식을 검사했습니다.

실행 조건

  • 측정 일시: 2026-09-25 22:50 UTC – 2026-09-26 01:57 UTC, 리전 ap-northeast-2, 실행 prefix e004-20260925t221414z-e5fd.
  • 코드 커밋: 부하 셀은 9474ed6에서 실행했습니다. 앞서 e3b71f9로 실행한 D1 셀 5개도 결과에 포함했습니다. 두 커밋 사이에는 러너 코드가 바뀌지 않았고, 운영 도구의 기록 방식만 바뀌었습니다. 재현 절차는 실험 README에 있습니다.
  • 비교 구성: 비용 상한 때문에 계획보다 축소했습니다.
ID 실제 구성 계획 구성과의 차이
D1 DSQL 단일 리전 클러스터, IAM 토큰, verify-full TLS, 공개 엔드포인트 없음
R1 RDS PostgreSQL 16.15, db.t4g.medium Multi-AZ, gp3 20 GiB, 비공개 엔드포인트 클래스(버스터블 2 vCPU)·스토리지 축소
A1 Aurora PostgreSQL 16.15, db.t4g.medium writer 1대, I/O-Optimized reader 없음, 소형 클래스, Standard 대신 I/O-Optimized
A2 Aurora Serverless v2 16.15, writer 1대, 0.5–4 ACU, I/O-Optimized reader 없음, ACU 범위 축소
  • 부하 발생기: 같은 VPC 안의 EC2 c7g.4xlarge Spot 인스턴스(16 vCPU)이고, 접근은 SSM으로만 했습니다. 도구 버전은 Python 3.11.16, psycopg 3.3.6(libpq 18), boto3 1.43입니다. 측정 구간의 발생기 CPU 사용률은 최대 75%였고, 포화로 무효 처리한 셀은 없습니다.
  • RTT 중앙값: D1 2.6 ms, R1 0.7 ms, A1 0.2 ms, A2 0.16 ms. D1만 공개 엔드포인트를 거치므로 RTT가 가장 깁니다.
  • 업무: 주문 생성 70%(조건부 재고 차감, 주문·항목·영수증 저장)와 계좌 이체 30%(잔액 확인 후 두 계좌 갱신)입니다. SQL은 네 서비스에서 동일합니다. 업무 ID 영수증으로 중복 처리를 막고, 커밋 여부가 불확실할 때 이 영수증을 조회해 판정합니다.
  • 조건 행렬:
    • REPEATABLE READ(네 서비스 공통): 균등·집중 분포 × 동시성 16/64/256 × 재시도 없음/최대 3회.
    • READ COMMITTED: 대조군만 실행했습니다(대조군의 기본 격리 수준, 재시도 최대 3회). D1은 기본 격리 수준이 REPEATABLE READ이므로 공통 행렬 결과를 그대로 씁니다.
    • 집중 분포는 상위 1% 키에 요청의 80%를 보냅니다.
    • 재시도는 40001/40P01에만 적용하고, 총 마감 2초에 지수 backoff(10 ms 기준)와 full jitter를 씁니다.
  • 셀과 반복: 셀은 워밍업 5초 + 측정 20초이고, 반복은 1회입니다. 동시성을 고정하고 응답이 오면 바로 다음 요청을 보내는 방식(closed-loop)입니다.
  • 계획과 달라진 점:
    • 계획은 워밍업 10분 + 수집 20분을 3회 반복하는 것이었습니다.
    • D1 파일럿에서 DSQL이 시도 1회당 약 0.095 DPU를 소모했습니다. 계획 조건으로 실행하면 D1 DPU 비용만 약 USD 16.5로 추정되어, 사용자 결정으로 반복 횟수와 셀 시간을 줄였습니다.
    • 파일럿에서 c7g.2xlarge가 동시성 256에서 포화되어 러너를 c7g.4xlarge로 바꿨습니다.
    • 실행 도중 Spot 인스턴스가 회수되어(23:19 UTC, 용량 부족) 러너를 교체하고, D1의 남은 셀부터 재개했습니다.
    • D1 셀 1개(D1-RR-hot-c64-retry3)는 DSQL 서버 오류(InternalError_: server unavailable, 22:57 UTC)로 실패해 한 번 다시 실행했습니다. 첫 시도 기록도 보관했습니다.

성능 결과

격리 수준 시나리오

두 트랜잭션의 실행 순서를 고정하고 결과를 기록했습니다. 결과는 “현상 발생(anomaly)”, “오류로 차단(SQLSTATE)”, “대기로 차단” 중 하나입니다. 괄호 안은 Hermitage 식 표기입니다. D1은 BEGIN ISOLATION LEVEL READ COMMITTED와 SERIALIZABLE을 거부했으므로(0A000) 해당 행은 적용 불가입니다.

시나리오 격리 수준 D1 R1 · A1 · A2 (세 구성 동일)
lost update (P4) READ COMMITTED 적용 불가 현상 발생
lost update (P4) REPEATABLE READ 오류로 차단 (40001) 오류로 차단 (40001)
lost update (P4) SERIALIZABLE 적용 불가 오류로 차단 (40001)
write skew (G2-item) READ COMMITTED 적용 불가 현상 발생
write skew (G2-item) REPEATABLE READ 오류로 차단 (40001) 현상 발생
write skew (G2-item) SERIALIZABLE 적용 불가 오류로 차단 (40001)
같은 업무 ID 이중 주문 READ COMMITTED 적용 불가 대기 후 차단 (23505)
같은 업무 ID 이중 주문 REPEATABLE READ 오류로 차단 (40001) 대기 후 차단 (23505)
같은 업무 ID 이중 주문 SERIALIZABLE 적용 불가 대기 후 차단 (40001)
교차 갱신(교착) 세 수준 커밋 시 차단 (40001, RR) 교착 감지 (40P01)
SELECT FOR UPDATE 조건부 차감 READ COMMITTED 적용 불가 대기 후 순차 처리
SELECT FOR UPDATE 조건부 차감 REPEATABLE READ / SERIALIZABLE 커밋 시 차단 (40001, RR) 대기 후 차단 (40001)
  • 대조군 세 구성: PostgreSQL 문서에 적힌 격리 수준별 동작과 모두 일치했습니다. 예를 들어 REPEATABLE READ에서는 write skew를 허용했고, 교착은 40P01로 끝났습니다.
  • D1의 두 가지 차이:
    1. 잠금 대기가 없습니다. 같은 행을 두 트랜잭션이 갱신하면, 먼저 커밋한 쪽이 성공하고 나중에 커밋한 쪽이 40001로 실패했습니다. FOR UPDATE를 걸어도 다른 트랜잭션이 대기하지 않았습니다.
    2. 이번 write skew 시나리오를 REPEATABLE READ에서 40001로 막았습니다. PostgreSQL의 REPEATABLE READ는 같은 시나리오를 허용했습니다.
  • 해석의 한계: 2번은 한 번 관측한 결과입니다. 이것이 DSQL 동시성 제어가 항상 보장하는 동작인지는 공식 문서로 확인하지 않았습니다. 따라서 “DSQL이 write skew를 방지한다”고 일반화하지 않습니다.

경합 부하

아래 표에서 쓰는 지표의 정의는 다음과 같습니다.

  • 성공 TPS: 측정 20초 동안 커밋에 성공한 업무 수 ÷ 20.
  • 원시 충돌률: 전체 시도 중 40001/40P01로 실패한 비율.
  • 최종 실패율: 재시도와 마감까지 반영한 뒤 끝내 실패한 업무의 비율. 품절·잔액 부족 같은 업무상 거절은 0건이었습니다.
  • p99 전체: 재시도와 대기를 포함해 업무 하나가 끝날 때까지 걸린 시간입니다. 실패한 업무도 포함합니다. 2초 마감에 걸린 업무는 끝날 때까지 연결 교체와 영수증 조회 시간이 더해지므로, 2초를 넘는 값이 나올 수 있습니다.
  • p99 커밋 성공: 커밋에 성공한 업무만 모은 p99입니다.

모든 셀이 반복 1회라 편차를 알 수 없습니다. 각 값은 한 번 측정한 결과입니다.

REPEATABLE READ · 재시도 없음

분포 동시성 구성 성공 TPS 원시 충돌률 최종 실패율 p50 (ms) p99 전체 (ms) p99 커밋 성공 (ms)
균등 16 D1 841 0.6% 0.6% 19 28 28
균등 16 R1 1,500 0.5% 0.5% 10 23 23
균등 16 A1 855 0.6% 0.6% 18 44 44
균등 16 A2 990 0.5% 0.5% 11 59 59
균등 64 D1 2,363 2.5% 2.5% 26 39 39
균등 64 R1 1,745 2.6% 2.6% 34 80 81
균등 64 A1 1,050 2.1% 2.1% 56 152 152
균등 64 A2 922 2.4% 2.4% 69 186 186
균등 256 D1 8,494 8.6% 8.6% 27 41 41
균등 256 R1 1,412 9.1% 9.1% 159 341 345
균등 256 A1 1,165 8.4% 8.4% 192 400 400
균등 256 A2 0 0.0% 100.0% 7,098 8,449 -
집중 16 D1 711 20.8% 20.8% 18 25 25
집중 16 R1 890 21.6% 21.6% 10 25 22
집중 16 A1 518 19.8% 19.8% 15 81 44
집중 16 A2 608 20.1% 20.1% 9 73 65
집중 64 D1 1,402 43.7% 43.7% 25 36 35
집중 64 R1 423 43.5% 44.0% 27 1,020 208
집중 64 A1 274 39.6% 41.0% 43 4,536 335
집중 64 A2 302 42.1% 42.2% 43 1,020 678
집중 256 D1 4,479 66.5% 66.5% 19 31 28
집중 256 R1 512 62.1% 63.1% 81 1,852 639
집중 256 A1 56 17.3% 42.5% 109 16,211 373
집중 256 A2 37 1.5% 30.7% 64 18,267 208

REPEATABLE READ · 재시도 최대 3회

분포 동시성 구성 성공 TPS 원시 충돌률 최종 실패율 p50 (ms) p99 전체 (ms) p99 커밋 성공 (ms)
균등 16 D1 841 0.6% 0.0% 19 39 39
균등 16 R1 1,447 0.6% 0.0% 10 30 30
균등 16 A1 869 0.5% 0.0% 16 54 54
균등 16 A2 989 0.5% 0.0% 10 62 62
균등 64 D1 2,268 2.5% 0.0% 28 65 65
균등 64 R1 1,760 2.5% 0.0% 33 94 94
균등 64 A1 1,082 2.1% 0.0% 54 162 162
균등 64 A2 933 2.6% 0.0% 70 197 197
균등 256 D1 8,230 9.8% 0.4% 28 96 92
균등 256 R1 1,404 10.2% 0.4% 165 442 433
균등 256 A1 700 7.6% 2.3% 173 12,641 545
균등 256 A2 120 1.8% 0.0% 98 325 325
집중 16 D1 421 27.2% 4.9% 27 109 104
집중 16 R1 534 27.1% 5.0% 10 970 62
집중 16 A1 343 25.9% 5.2% 15 990 120
집중 16 A2 374 23.9% 4.5% 11 990 144
집중 64 D1 1,102 50.4% 18.0% 29 110 105
집중 64 R1 187 48.8% 19.9% 34 3,502 1,172
집중 64 A1 190 47.0% 18.0% 51 4,581 1,104
집중 64 A2 96 40.4% 21.9% 68 5,011 1,533
집중 256 D1 3,273 71.0% 38.9% 49 94 86
집중 256 R1 56 4.3% 27.7% 92 11,218 239
집중 256 A1 39 3.9% 30.6% 112 13,286 325
집중 256 A2 24 1.3% 38.6% 86 16,537 322

READ COMMITTED · 재시도 최대 3회

분포 동시성 구성 성공 TPS 원시 충돌률 최종 실패율 p50 (ms) p99 전체 (ms) p99 커밋 성공 (ms)
균등 16 R1 1,488 0.0% 0.0% 10 25 25
균등 16 A1 872 0.0% 0.0% 17 42 42
균등 16 A2 1,003 0.0% 0.0% 10 61 61
균등 64 R1 1,893 0.0% 0.0% 32 86 86
균등 64 A1 1,173 0.0% 0.0% 53 119 119
균등 64 A2 985 0.0% 0.0% 68 181 181
균등 256 R1 1,543 0.0% 0.0% 160 355 355
균등 256 A1 1,213 0.0% 0.0% 203 442 442
균등 256 A2 595 0.0% 0.0% 425 852 852
집중 16 R1 177 0.6% 1.1% 11 2,129 1,040
집중 16 A1 236 0.5% 0.9% 16 1,269 1,040
집중 16 A2 152 0.7% 0.9% 10 2,006 1,728
집중 64 R1 60 2.3% 18.1% 41 4,491 1,798
집중 64 A1 61 2.7% 23.3% 52 3,868 1,890
집중 64 A2 52 2.5% 24.5% 62 4,026 1,834
집중 256 R1 60 0.0% 28.5% 92 11,674 199
집중 256 A1 38 0.0% 31.0% 100 13,553 318
집중 256 A2 0 0.0% 100.0% 9,286 9,693 -

관측 요약

  • 정합성: 66개 셀 전부에서 불변식 위반이 0건이었습니다. 검사한 불변식은 재고 음수 없음, 재고 변동과 판매량 일치, 잔액 총합 보존, 업무 ID별 효과 1회, 부분 반영 없음, 확인된 커밋 유실 없음입니다. 네 서비스 모두 이 구현(조건부 갱신, 영수증, 재시도, 커밋 여부 확인)으로 업무 불변식을 지켰습니다.
  • 균등 분포: 원시 충돌률은 서비스와 관계없이 동시성 16에서 약 0.5%, 64에서 약 2–3%, 256에서 약 8–10%로 비슷했습니다(A2의 동시성 256은 아래 설명대로 제외). 네 서비스가 모두 REPEATABLE READ로 같은 행 충돌을 거부하기 때문입니다. 최대 3회 재시도를 적용하면 최종 실패율이 0–2.3%로 내려갔습니다. 가장 높은 2.3%는 A1 동시성 256으로, 2초 마감에 걸린 이체 때문입니다.
  • 집중 분포에서 D1:
    • 원시 충돌률이 동시성 16/64/256에서 21%, 44%, 67%(재시도 없음)로 올랐습니다.
    • 최대 3회 재시도로 최종 실패율은 4.9%, 18%, 39%로 줄었지만 0은 아니었습니다.
    • 잠금 대기가 없어 커밋에 성공한 요청의 p99는 110 ms 이하였습니다.
  • 집중 분포에서 대조군:
    • 행 잠금 대기열이 생겼습니다. R1 동시성 256에서 잠금 대기 세션이 최대 206개였습니다.
    • REPEATABLE READ에서 동시성 64와 256일 때는 2초 마감에 걸린 이체가 늘어, 성공 TPS가 24–512로 떨어지고 최종 실패율이 18–63%가 되었습니다.
    • READ COMMITTED에서는 충돌 오류가 거의 없었지만, 같은 잠금 대기 때문에 동시성 64/256의 최종 실패율이 18–31%였습니다(A2 동시성 256 제외).
  • A2 동시성 256 셀 두 개: 성공이 0건이었습니다(REPEATABLE READ·균등·재시도 없음, READ COMMITTED·집중). 실패 사유는 모두 연결 타임아웃(ConnectionTimeout)이었습니다. 워밍업이 5초뿐이어서, ACU가 낮은 상태에서 새 연결 256개가 한꺼번에 몰린 결과입니다. 이 결과만으로는 A2의 용량 한계인지 짧은 워밍업 때문인지 구분할 수 없어 결론에 쓰지 않습니다.
  • 처리량 비교를 하지 않는 이유: 균등 분포 동시성 256에서 D1은 약 8,500 TPS를 냈습니다. 대조군은 t4g.medium(버스터블 2 vCPU)이라 약 1,200–1,500 TPS였습니다. 이 차이는 서비스 비교가 아니라 축소 구성 때문이므로, 처리량 배율로 해석하지 않습니다. 계획 규모의 용량 비교는 E002에서 합니다.

개발·운영 편의성

  • 준비 시간 (생성 요청부터 사용 가능까지, 1회 측정): D1 42초, A1 5분 30초, A2 6분 37초, R1 12분 8초(Multi-AZ).
  • 삭제 시간 (실행 종료부터 삭제 확인까지): D1 약 2분, R1 약 3분, A1 약 11분, A2 약 11분.
  • DSQL 행 수정 한도의 영향: DSQL은 트랜잭션당 수정 행 수가 3,000행으로 제한됩니다(공식 쿼터, 2026-09-26 확인). 그래서 셀 사이의 초기화(이전 셀의 주문·영수증 삭제)를 1,000행 단위로 나눠 실행해야 했습니다.
    • 그 결과 D1은 셀당 약 3–8분이 걸렸습니다. 셀 12개에 약 58분입니다.
    • 대조군은 같은 코드로 셀당 약 1분이 걸렸습니다. R1은 셀 18개에 16분이었습니다.
    • 대량 삭제·정리 작업이 DSQL에서 운영 부담이 된다는 관측입니다.
  • DSQL에서 추가로 처리해야 했던 것:
    • 트랜잭션을 시작한 뒤에 만든 테이블은 그 트랜잭션에서 보이지 않았습니다(42P01). 그래서 시나리오 도구가 테이블을 먼저 만들도록 고쳤습니다.
    • SHOW max_connections가 20을 반환했습니다. 공식 쿼터는 클러스터당 연결 1만 개이므로, 이 값으로 동시성을 제한하지 않도록 도구를 고쳤습니다.
    • DSQL 서버 오류가 한 번 발생해 셀을 다시 실행했습니다.
  • 재시도 구현량: REPEATABLE READ에서는 네 서비스 모두 재시도가 필요했고, 같은 재시도 코드(load.py의 run_op, 커밋 여부 확인 포함 약 60줄)를 썼습니다. 반면 대조군을 기본값인 READ COMMITTED로 쓰면 충돌 오류는 거의 없어서, 재시도 없이도 동작합니다. DSQL은 READ COMMITTED를 지원하지 않으므로 재시도 구현이 필수입니다. 구현에 들인 작업 시간은 측정하지 않았습니다.

비용

단가는 2026-09-25–26에 AWS Price List API로 확인한 서울 리전 On-Demand 가격(USD)입니다. DSQL은 백만 DPU당 USD 10(2026-09-11 게시본)입니다. 아래 표의 추정 비용은 실행 당시 사용량과 단가로 계산한 값이고, 실제 청구액은 2026-09-28에 Cost Explorer(서울 리전, 2026-09-25–26 UTC, 사용 유형별)로 확인한 값입니다. 실험과 무관하게 계정에 상시 발생하던 요금(ELB, S3, VPC 등)은 제외했습니다.

항목 사용량 추정 비용 실제 청구액 근거와 차이
D1 DPU 222,363 DPU (파일럿·시나리오·초기화 포함) 약 $2.22 $1.22 청구된 DPU(222,362)는 CloudWatch TotalDPU 합계와 일치했습니다. 금액 차이는 월 무료 사용량 10만 DPU가 차감되었기 때문입니다.
R1 0.52 시간 약 $0.11 $0.06 청구된 Multi-AZ 사용 시간은 0.29 시간으로, 생성 요청부터 삭제 완료까지 잰 추정 시간보다 짧았습니다.
A1 0.50 시간 약 $0.07 $0.05 청구 시간 0.42 시간. 아래 요율 설명 참고
A2 1.91 ACU-시간 약 $0.50 $0.39 청구 1.86 ACU-시간(CloudWatch ServerlessDatabaseCapacity 적분과 3% 차이). 아래 요율 설명 참고
부하 발생기·네트워크 Spot 러너 4대, 합계 약 3.9 시간 약 $0.45 약 $0.34 청구된 Spot 사용 2.77 시간($0.28), EBS·AZ 간 전송·공인 IPv4 약 $0.06
합계   약 $3.4 약 $2.07 추정의 약 61%
  • 추정이 실제보다 높았던 이유: 금액 차이 약 $1.3 가운데 약 $1.0은 DSQL 월 무료 사용량 때문입니다. 나머지는 추정에 쓴 가동 시간(생성 요청부터 삭제 완료까지)이 실제 과금 시간보다 길었기 때문입니다. 이 달의 DSQL 무료 사용량은 이 실험에서 모두 썼습니다.
  • I/O-Optimized 요율: CloudTrail 기록상 A1과 A2 클러스터는 aurora-iopt1(I/O-Optimized)로 생성되었고 이후 변경되지 않았습니다. 그런데 청구 기록에는 A1 전체와 A2의 1.54 ACU-시간이 Standard 사용 유형과 요율($0.113/시간, $0.20/ACU-시간)로 남았고, I/O-Optimized 요율로 청구된 것은 A2의 0.32 ACU-시간뿐입니다. Standard라면 붙어야 할 I/O 요금은 청구되지 않았습니다. 구성은 I/O-Optimized가 맞으며, 청구 분류가 이렇게 된 원인은 확인하지 못했습니다(조회 시점의 Cost Explorer 값은 추정 상태이며 월 마감 때 바뀔 수 있습니다).

  • DSQL DPU 소모량: D1 파일럿에서 시도 1회당 약 0.095 DPU였습니다. 서울 단가로 환산하면 시도 100만 회당 약 USD 0.95입니다.
  • 최대 처리량 부하의 비용: DSQL은 처리량이 높아서, 쉬지 않고 요청을 보내는 closed-loop 부하에서는 짧은 시간에도 DPU 비용이 빠르게 늘었습니다.
  • 단위 비용을 비교하지 않는 이유: 대조군은 가동 시간 기준으로 과금되고, 이번 셀은 25초로 짧습니다. 그래서 성공 업무당 비용을 서비스끼리 비교하면 의미가 없습니다. 이 비교는 E010에서 같은 도착률로 계산합니다.

결론과 한계

  • 정합성 (질문 1): 네 서비스 모두 경합과 재시도 조건에서 업무 불변식을 지켰습니다. DSQL도 재시도와 커밋 여부 확인을 구현하면 이중 처리나 커밋 유실 없이 동작했습니다.
  • 경합 동작의 차이 (질문 2):
    • DSQL은 잠금 대기 없이 커밋 시점에 충돌을 알립니다. 그래서 집중 경합에서 지연은 짧게 유지되지만 충돌률이 높고, 재시도 뒤에도 실패가 남습니다(집중·동시성 256에서 최종 실패율 39%).
    • 대조군은 잠금 대기로 경합을 흡수하려 하므로, 고경합에서는 대기가 마감을 넘겨 처리량이 급감했습니다.
    • 따라서 인기 상품처럼 경합이 집중되는 업무를 DSQL로 옮길 때는 두 가지를 먼저 검토해야 합니다. 재시도 예산(횟수·마감)과 경합을 분산하는 스키마 설계입니다. 예를 들어 재고를 여러 행으로 나누는 방식이 있습니다.
  • 구현량 (질문 3): DSQL은 READ COMMITTED가 없어 재시도 처리가 필수입니다. 대조군은 READ COMMITTED를 쓰면 재시도를 생략할 수 있습니다.
  • 한계:
    • 반복이 1회이고 셀이 25초로 짧아 편차를 보고할 수 없습니다. 측정 구간에 캐시·ACU가 안정되기 전의 상태가 섞였을 수 있습니다.
    • 대조군은 버스터블 소형 구성이라, 처리량과 지연의 절대값은 계획 구성과 다릅니다.
    • closed-loop 부하이므로 요청 간격이 벌어지는 동안의 지연이 측정에서 빠집니다(coordinated omission). 그래서 p99는 해당 동시성에서의 응답 시간으로만 해석해야 합니다.
    • A2의 동시성 256 결과는 연결 타임아웃 때문에 해석하지 않습니다.
    • 시나리오는 격리 수준당 1회 관측입니다.
    • 외부 참고 자료로, hot-key 스키마에서 DSQL 실패율이 높았다는 사례가 있습니다(Marc Bowes의 TPC-B 글). 이번 결과와 같은 방향이지만, 그 수치는 검증하지 않았습니다.
  • 다음 실험:
    • E002: 요청률을 고정하는 open-loop 방식으로 SLO 용량과 지연을 계획 규모에서 측정합니다.
    • E003: 연결 폭증을 다룹니다. A2의 연결 타임아웃, DSQL의 연결 속도 제한을 포함합니다.

정리 기록

  • 생성 리소스:
    • BATCH: VPC, 서브넷 2개, IGW, 보안 그룹 2개, DB 서브넷 그룹, IAM 역할·인스턴스 프로파일, Spot 러너 4대(파일럿용 c7g.2xlarge 1대, c7g.4xlarge 3대).
    • 구성별: D1 클러스터, R1 인스턴스, A1·A2 클러스터와 writer, RDS 관리 시크릿 3개.
  • 삭제 완료 시각 (UTC): D1 00:26:37, R1 00:58:04, A1 01:31:21, A2 02:07:38, BATCH 02:08:14. 삭제에 실패한 리소스는 없습니다.
  • manifest에 없던 러너 1대: 러너 교체 코드 버그로 c7g.4xlarge 러너 1대(22:45:53 UTC 생성)가 manifest에 기록되지 않은 채 생성되었습니다. 이 러너는 실험 태그로 소유를 확인한 뒤 수동으로 종료했고, 22:48 UTC에 종료가 확인되었습니다. 같은 문제가 다시 생기지 않도록 코드를 고쳤습니다.
  • 검증 (02:09:45 UTC): e004.py verify의 remaining_count=0입니다. 첫 검증(02:08:19)에서는 종료된 러너의 Spot 요청 1건이 아직 active 상태여서 1이 나왔고, 요청이 closed로 바뀐 뒤 다시 검증했습니다.
  • 수동 교차 확인: DSQL 클러스터 0개, e004 접두어 RDS 인스턴스·클러스터·클러스터 스냅샷 0개, IAM 역할 NoSuchEntity, 실험 태그의 EC2 인스턴스·볼륨·ENI·Spot 요청(open/active) 0개.