Java / Spring Engineering Lab · 2026.08

PostgreSQL Concurrency

단일 counter row와 ticket row 모델의 동시성 특성을 공통 저장 경로와 운영 변수를 걷어낸 뒤 같은 요청 경로에서 비교했습니다.

Java 21 · Spring Boot 3 · PostgreSQL 17 공개 재현 실험

70부하 case7 rates × 2 models × 5 repeats
274,999실제 요청HTTP 실패 · dropped 0
0정합성 위반초과 할당 · 중복 점유 0
10.9×500 RPS DB 시간 차이1.071ms / 0.098ms

Question

부하가 올라갈 때 경합은 어디에서 드러나는가

Counter는 한 row를 원자적으로 갱신해 단순하지만 요청이 몰리면 같은 row lock을 기다립니다. Skip Locked는 여러 ticket row를 분산 점유할 수 있지만 100,000개 row의 사전 생성 시간과 저장 공간을 지불합니다.

이전 측정은 예약 결과 INSERT·UPDATE·조회가 섞여 핵심 재고 SQL을 설명하지 못했습니다. 그래서 그 수치를 폐기하고, 요청마다 비교 대상 SQL만 한 번 실행하도록 실험 범위를 다시 만들었습니다.

Controls

비교 조건을 코드와 원본 데이터로 고정

같은 실행 경로

Spring Boot 1개·PostgreSQL 1개, Tomcat 200 threads, HikariCP 50 connections.

같은 부하

50·75·100·150·200·300·500 RPS, 각 20초, 모델별 5회 반복.

교차 순서

RPS마다 모델 실행 순서를 바꾸고 case 사이 재시작·동일 warm-up을 적용.

정합성 검증

k6 성공 수와 DB 할당 수 일치, 초과 할당·중복 점유·불변식 위반 검사.

Result

낮은 부하에서는 단순성, 경합 구간에서는 분산 점유

50–150 RPS에서는 두 방식 모두 충분히 빨랐고 Counter의 핵심 DB 시간이 대체로 더 짧았습니다. 300·500 RPS에서 Counter의 응답 변동 폭이 커지는 패턴은 단일 row 경합과 일치했습니다.

500 RPS 5회 반복에서 각 열을 따로 집계한 중앙값은 HTTP p95 Counter 11.889ms·Skip Locked 8.912ms, 핵심 DB query 평균 Counter 1.071ms·Skip Locked 0.098ms였습니다. 지표별 중앙값은 서로 다른 반복에서 나올 수 있습니다.

5회 범위: HTTP p95 Counter 7.736–715.500ms, Skip Locked 5.339–24.929ms · Counter DB 평균 0.754–16.952ms. 중앙값과 함께 변동 폭을 공개합니다.

실험 범위: 로컬 단일 host의 상대 비교. Skip Locked의 row 사전 생성 비용은 응답 시간 바깥의 trade-off로 분리했습니다.

Public Proof

보고서·70개 원시 결과·검증 코드를 공개합니다.

Reproducible snapshot · 81f93eb

Raw CSV

70개 case의 목표 RPS, 반복 번호, 실제 요청, HTTP p95, 핵심 DB 평균과 정합성 결과.

Experiment report

폐기한 이전 측정, 통제 조건, 중앙값 산식, 튄 값과 해석 한계를 함께 기록.

Integration test

두 할당 모델의 성공 수, 잔여 재고, 중복 점유와 초과 할당 불변식을 코드로 검증.