동시성 이슈 해결방안 보고서 - youngy1212/Concert GitHub Wiki
콘서트 예약 시스템 동시성 이슈 및 해결 방안 분석 보고서
목적
본 보고서는 콘서트 예약 시스템에 발생할 수 있는 동시성 이슈를 파악하고, 이에 적합한 해결 방법을 도입하기 위한 목적으로 작성하였습니다.
실험 조건
- 로컬 서버
- k6 부하 시스템 사용
- mySql 도커 및 분산락을 위한 Redis 도커 사용
테스트 실행 방법
API 지속적 호출을 통한 부하 테스트 수행
- 부하 조건 : 약 100명의 유저가 각자 10번씩 요청하여 총 1000건의 요청을 발생시킴
- 테스트 반복 : 데이터 신뢰성을 위해 최소 3회 이상 테스트를 수행하고, 평균 값을 사용하여 비교 분석
- 외래 변수 제거 : 토큰 검증은 제외하고 테스트를 진행
- 변수 통제 : 콘서트는 1개, 좌석은 50개로 설정
예측 값
- 락 없음 : 성능 면에서 가장 우수할 것이라 생각되지만, DB에 직접적으로 바로 접근하여 DB 부하가 클 것으로 예상
- 분산락 : 네트워크를 한번 거쳐, 락 없음 보다는 느릴 수 있겠지만 캐시 특성상 거의 차이가 없이 빠를 것 으로 예상
- 비관락 : 락으로 인한 대기시간으로 성능저하가 클 것으로 예상. 특히 락을 잡고 있는 동안은 접근할 수 없기 때문에 tps가 낮을 것으로 예상
테스트 결과
락 없음 : DB 유니크 값 사용
default ✓ [======================================] 100 VUs 00m10.6s/10m0s 1000/1000 shared iters
✗ is status 200
↳ 5% — ✓ 50 / ✗ 950
✓ response body exists
checks.........................: 52.50% ✓ 1050 ✗ 950
data_received..................: 203 kB 19 kB/s
data_sent......................: 226 kB 21 kB/s
http_req_blocked...............: avg=729.03µs min=0s med=517.32µs max=10.71ms p(90)=1.24ms p(95)=2.71ms
http_req_connecting............: avg=461.14µs min=0s med=488.63µs max=3.29ms p(90)=1.002ms p(95)=1.41ms
http_req_duration..............: avg=40.33ms min=7.78ms med=17.55ms max=385.08ms p(90)=60.34ms p(95)=244.56ms
{ expected_response:true }....: avg=113.45ms min=15.08ms med=115.56ms max=226.53ms p(90)=202.67ms p(95)=212.71ms
http_req_failed................: 95.00% ✓ 950 ✗ 50
http_req_receiving.............: avg=169.22µs min=0s med=0s max=1.70ms p(90)=541.24µs p(95)=643.39µs
http_req_sending...............: avg=107.74µs min=0s med=0s max=5.38ms p(90)=505.15µs p(95)=550.98µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=40.05ms min=7.75ms med=17.27ms max=383.37ms p(90)=60.19ms p(95)=244.12ms
http_reqs......................: 1000 94.1133/s
iteration_duration.............: avg=1.04s min=1s med=1.0167s max=1.3833s p(90)=1.0633s p(95)=1.2433s
iterations.....................: 1000 94.1133/s
vus............................: 100 min=100 max=100
vus_max........................: 100 min=100 max=100
분산락 사용
default ✓ [======================================] 100 VUs 00m10.6s/10m0s 1000/1000 shared iters
✗ is status 200
↳ 5% — ✓ 50 / ✗ 950
✓ response body exists
checks.........................: 52.50% ✓ 1050 ✗ 950
data_received..................: 203 kB 19 kB/s
data_sent......................: 226 kB 21 kB/s
http_req_blocked...............: avg=554.30µs min=0s med=514.60µs max=6.34ms p(90)=1.08ms p(95)=1.35ms
http_req_connecting............: avg=434.22µs min=0s med=445.63µs max=4.87ms p(90)=902.56µs p(95)=1.17ms
http_req_duration..............: avg=46.57ms min=7.83ms med=20.70ms max=424.40ms p(90)=71.07ms p(95)=288.63ms
{ expected_response:true }....: avg=144.47ms min=15.75ms med=145.82ms max=268.54ms p(90)=244.47ms p(95)=256.44ms
http_req_failed................: 95.00% ✓ 950 ✗ 50
http_req_receiving.............: avg=147.30µs min=0s med=0s max=1.70ms p(90)=532.65µs p(95)=621.97µs
http_req_sending...............: avg=78.73µs min=0s med=0s max=1.89ms p(90)=504.71µs p(95)=519.33µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=46.34ms min=7.83ms med=20.47ms max=424.34ms p(90)=70.48ms p(95)=288.45ms
http_reqs......................: 1000 93.49374/s
iteration_duration.............: avg=1.0467s min=1s med=1.02s max=1.4267s p(90)=1.07s p(95)=1.2867s
iterations.....................: 1000 93.49374/s
vus............................: 100 min=100 max=100
vus_max........................: 100 min=100 max=100
비관락 사용
default ✓ [============================] 100 VUs 00m11.8s/10m0s 1000/1000 shared iters
✗ is status 200
↳ 5% — ✓ 50 / ✗ 950
✓ response body exists
data_received..................: 203 kB 16 kB/s
data_sent......................: 226 kB 18 kB/s
http_req_blocked...............: avg=1.71ms min=0s med=0.945ms max=32.72ms p(90)=3.47ms p(95)=7.72ms
http_req_connecting............: avg=1.43ms min=0s med=0.65ms max=24.39ms p(90)=2.96ms p(95)=6.80ms
http_req_duration..............: avg=203.91ms min=12.63ms med=45.28ms max=1.87s p(90)=343.02ms p(95)=1.51s
{ expected_response:true }...: avg=1.02s min=40.49ms med=1.20s max=1.87s p(90)=1.65s p(95)=1.71s
http_req_failed................: 95.00% ✓ 950 ✗ 50
http_req_receiving.............: avg=222.67µs min=0s med=0s max=1.94ms p(90)=620.71µs p(95)=763.56µs
http_req_sending...............: avg=145.32µs min=0s med=0s max=1.55ms p(90)=553.82µs p(95)=641.04µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=203.54ms min=12.59ms med=44.80ms max=1.87s p(90)=342.62ms p(95)=1.51s
http_reqs......................: 1000 79.51/s
iteration_duration.............: avg=1.21s min=1.01s med=1.05s max=2.88s p(90)=1.35s p(95)=2.54s
iterations.....................: 1000 79.51/s
vus............................: 61 min=61 max=100
vus_max........................: 100 min=100 max=100
테스트 결과 비교
테스트 결과는 이상 데이터 5%를 제외한 p90 및 p95 지표를 중심으로 비교 분석하였습니다.
http_req_duration
)
1. HTTP 요청 지속 시간 (조건 | p90 | p95 |
---|---|---|
락 없음 | 60.34ms | 244.56ms |
분산락 | 71.07ms | 288.63ms |
비관락 | 343.02ms | 1.51s |
분석:
- 락 없음이 분산락보다 p90에서 약 11ms, p95에서 약 44ms 더 낮은 지연 시간을 보였습니다.
- 비관락은 두 방식보다 현저히 높은 지연 시간을 기록하여 성능 저하가 두드러졌습니다.
- 특히 p95에서 두 락과 비교하여, 비관락은 1.51초로 약 5배 이상의 차이를 보였습니다.
expected_response
)
2. 예상된 응답 시간 (조건 | p90 | p95 |
---|---|---|
락 없음 | 202.67ms | 212.71ms |
분산락 | 244.47ms | 256.44ms |
비관락 | 1.65s | 1.71s |
분석:
- 락 없음이 분산락보다 응답 시간이 약 40~44ms 더 빠르게 나타났습니다.
- p95에서 비관락은 1.71초로 6배 이상의 차이를 보였습니다.
http_req_blocked
)
3. HTTP 요청 차단 시간 (조건 | p90 | p95 |
---|---|---|
락 없음 | 1.24ms | 2.71ms |
분산락 | 1.08ms | 1.35ms |
비관락 | 3.47ms | 7.72ms |
분석:
- 분산락이 락 없음보다 차단 시간이 약간 더 짧았습니다.
- 비관락은 차단 시간이 가장 길어, p95에서 가장 빠른 분산락과 비교하여 5배 이상의 차이를 보였습니다.
vus
) 분석
4. 가용 사용자 수 (조건 | 가용 사용자 수 (vus ) |
---|---|
락 없음 | 100명 (유지) |
분산락 | 100명 (유지) |
비관락 | 최소 61명까지 감소 |
분석:
- 비관락은 사용자 수가 61명 으로 감소하여 약 39%의 감소율을 보였습니다.
5. TPS(Transactions Per Second) 분석
해당 테스트에서는 트랜잭션이 하나기 때문에 http_reqs 수치로 대체하였습니다.
조건 | TPS |
---|---|
락 없음 | 94.11/s |
분산락 | 93.49/s |
비관락 | 79.51/s |
분석:
- 락 없음과 분산락의 TPS는 거의 동일하다고 판단됩니다.
- 비관락의 TPS는 다른 두 방법에 비해 낮았습니다.
6. DB 부하 정도 비교 추가 테스트 (5000건 부하 기준)
조건 | 메모리 사용량 | CPU 사용률 (%) |
---|---|---|
분산락 | 380MB | 16~17 |
락 없음 | 390MB | 15~19 |
- 추가 분석:
- 5000건의 부하 조건 하에서 락 없음과 분산락 간의 메모리 및 CPU 사용량은 거의 유사하게 측정되었습니다.
- 이는 분산락과 락 없음 사이에 크게 성능차이가 발생하지 않음을 시사합니다.
결론
- 전체적인 성능 측면에서는 락 없음 >= 분산락 >>>> 비관락의 성능을 보였습니다.
- 하지만 중요한 지표인 TPS를 기준으로 하였을 때 락 없음과, 분산락의 경우 그 차이가 없다고 판단됩니다.
- 비관락은 성능 저하가 두드러졌으며, 특히 TPS 감소와 VUS의 감소로 인해 시스템 안정성이 떨어져 조건에서 제외하였습니다.
- 5,000건 부하 테스트에서도 락 없음과 분산락 간의 메모리 사용량과 CPU 사용률의 차이는 미미하였습니다.
- 이는 분산락을 적용하더라도 데이터베이스(DB)에 추가적인 부하를 크게 유발하지 않음을 의미합니다.
따라서 분산락을 통해 동시성 처리를 한다면 DB 부하를 줄이면서 효율적인 요청 처리가 가능할 것으로 보입니다.
예외 사항
- 낙관락:
- 낙관락의 경우 INSERT시 잡을 Lock 없어 작동하지 않습니다. 현 시점에서는 적용이 어려워 제외되었습니다.
고찰
테스트 방법의 적절성
- 목적 부합성
- 동시성 이슈를 평가하기 위한 테스트로서, 다양한 락 전략의 성능과 안정성을 비교하는 방법은 목적에 적절합니다.
- 부하 조건의 타당성
- 100명의 사용자와 1,000건의 요청은 현실적인 시나리오를 재현하기 충분하며, 반복 테스트를 통해 결과의 신뢰성을 높였습니다.
- 변수 통제
- 외부 변수의 영향을 최소화하여 락 전략 자체의 성능 차이를 명확히 비교할 수 있었습니다.
예측값과 결과의 일치성
- 예측한 대로 락 없음이 가장 빠른 성능을 보였습니다.
- 분산락은 약간의 성능 저하에도 불구하고 안정적인 동시성 처리가 가능함을 확인하였습니다.
- 비관락은 예상대로 성능 저하가 심각하여 실제 서비스 적용에 부적합함을 알 수 있었습니다.
- DB 부하의 경우 예측값과 다르게, 락 없음이 가장 우수하며 분산락과의 차이가 미비함을 알 수 있습니다.
한계점 인식
- 테스트 환경
- 본 테스트는 로컬 서버 환경에서 수행되었으며, 실제 운영 환경과는 차이가 있을 수 있습니다.
- 부하 패턴의 다양성 부족
- 1000건의 부하 조건을 기준으로 테스트를 진행하였으나, 실제 운영 시에는 더 다양한 부하 패턴이 존재할 수 있습니다.
- 잠금 메커니즘의 다양성 부족
- 본 보고서에서는 락 없음, 분산락, 비관락, 낙관락만 비교하였으며, 다른 방법에 대한 분석은 이루어지지 않았습니다.
결론
테스트 수행 방법은 동시성 이슈를 식별하고 해결 방안을 평가하는 본 연구의 목적에 부합하며, 실험 설계와 결과 분석이 적절하게 이루어졌습니다. 특히, 분산락을 활용한 동시성 처리는 데이터의 일관성을 유지하면서도 성능 저하를 최소화할 수 있는 현실적인 대안임을 확인하였습니다.
기타 발생할 수 있는 동시성 문제
-
예약 후 결제 확정 프로세스
- 시나리오
- 사용자가 좌석을 예약한 후, 결제를 확정하는 단계에서 예약에 발생하는 동시성 문제.
- 이미 예약이 완료된 상태에서는 동일한 좌석에 대한 추가 요청이 발생할 가능성이 낮을 것으로 예상됨.
- 동시성 제어 방식 : 비관적 락 (Pessimistic Locking)
- 선택 이유 : 이 프로세스는 최대한 많은 성공을 보장해야 하므로, 동시에 요청이 발생할 경우 가능한 모든 요청이 성공적으로 처리되도록 해야 함.
- 성능 :
- 데이터 일관성이 높으며, 결제 후 좌석 확정이라는 과정의 안정성을 보정함.
- 읽기 작업 시에도 락을 적용하기 때문에 성능 저하가 발생할 수 있음.
- 시나리오
-
포인트 충전 기능
- 시나리오
- 사용자가 자신의 포인트를 충전하는 과정에서 발생할 수 있는 동시성 문제.
- 포인트 충전 요청은 본인의 포인트에 한정되므로 동시 요청이 적을 것으로 예상됨.
- 동시성 제어 방식 : 낙관적 락 (Optimistic Locking)
- 선택 이유: 포인트 충전은 동시 요청이 적고, 한번만 성공해도 무방하므로, 일부 충전 요청이 실패하더라도 큰 문제가 되지 않음.
- 성능 :
- 락 사용을 최소화하여 성능 저하를 방지함.
- 시나리오
부하 테스트에 대한 고려
해당 두 동시성 문제에 대해서는 예상되는 트래픽이 많지 않기 때문에 부하 테스트를 진행하지 않았습니다.