동시성 이슈 해결방안 보고서 - youngy1212/Concert GitHub Wiki

콘서트 예약 시스템 동시성 이슈 및 해결 방안 분석 보고서

목적

본 보고서는 콘서트 예약 시스템에 발생할 수 있는 동시성 이슈를 파악하고, 이에 적합한 해결 방법을 도입하기 위한 목적으로 작성하였습니다.

실험 조건

  1. 로컬 서버
  2. k6 부하 시스템 사용
  3. mySql 도커 및 분산락을 위한 Redis 도커 사용

테스트 실행 방법

API 지속적 호출을 통한 부하 테스트 수행

  • 부하 조건 : 약 100명의 유저가 각자 10번씩 요청하여 총 1000건의 요청을 발생시킴
  • 테스트 반복 : 데이터 신뢰성을 위해 최소 3회 이상 테스트를 수행하고, 평균 값을 사용하여 비교 분석
  • 외래 변수 제거 : 토큰 검증은 제외하고 테스트를 진행
  • 변수 통제 : 콘서트는 1개, 좌석은 50개로 설정

예측 값

  1. 락 없음 : 성능 면에서 가장 우수할 것이라 생각되지만, DB에 직접적으로 바로 접근하여 DB 부하가 클 것으로 예상
  2. 분산락 : 네트워크를 한번 거쳐, 락 없음 보다는 느릴 수 있겠지만 캐시 특성상 거의 차이가 없이 빠를 것 으로 예상
  3. 비관락 : 락으로 인한 대기시간으로 성능저하가 클 것으로 예상. 특히 락을 잡고 있는 동안은 접근할 수 없기 때문에 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 지표를 중심으로 비교 분석하였습니다.

1. HTTP 요청 지속 시간 (http_req_duration)

조건 p90 p95
락 없음 60.34ms 244.56ms
분산락 71.07ms 288.63ms
비관락 343.02ms 1.51s

분석:

  • 락 없음이 분산락보다 p90에서 약 11ms, p95에서 약 44ms 더 낮은 지연 시간을 보였습니다.
  • 비관락은 두 방식보다 현저히 높은 지연 시간을 기록하여 성능 저하가 두드러졌습니다.
    • 특히 p95에서 두 락과 비교하여, 비관락은 1.51초로 약 5배 이상의 차이를 보였습니다.

2. 예상된 응답 시간 (expected_response)

조건 p90 p95
락 없음 202.67ms 212.71ms
분산락 244.47ms 256.44ms
비관락 1.65s 1.71s

분석:

  • 락 없음이 분산락보다 응답 시간이 약 40~44ms 더 빠르게 나타났습니다.
  • p95에서 비관락은 1.71초로 6배 이상의 차이를 보였습니다.

3. HTTP 요청 차단 시간 (http_req_blocked)

조건 p90 p95
락 없음 1.24ms 2.71ms
분산락 1.08ms 1.35ms
비관락 3.47ms 7.72ms

분석:

  • 분산락이 락 없음보다 차단 시간이 약간 더 짧았습니다.
  • 비관락은 차단 시간이 가장 길어, p95에서 가장 빠른 분산락과 비교하여 5배 이상의 차이를 보였습니다.

4. 가용 사용자 수 (vus) 분석

조건 가용 사용자 수 (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건 부하 기준)

Image

조건 메모리 사용량 CPU 사용률 (%)
분산락 380MB 16~17
락 없음 390MB 15~19
  • 추가 분석:
    • 5000건의 부하 조건 하에서 락 없음분산락 간의 메모리 및 CPU 사용량은 거의 유사하게 측정되었습니다.
    • 이는 분산락과 락 없음 사이에 크게 성능차이가 발생하지 않음을 시사합니다.

결론

  • 전체적인 성능 측면에서는 락 없음 >= 분산락 >>>> 비관락의 성능을 보였습니다.
  • 하지만 중요한 지표인 TPS를 기준으로 하였을 때 락 없음과, 분산락의 경우 그 차이가 없다고 판단됩니다.
  • 비관락은 성능 저하가 두드러졌으며, 특히 TPS 감소와 VUS의 감소로 인해 시스템 안정성이 떨어져 조건에서 제외하였습니다.
  • 5,000건 부하 테스트에서도 락 없음과 분산락 간의 메모리 사용량과 CPU 사용률의 차이는 미미하였습니다.
  • 이는 분산락을 적용하더라도 데이터베이스(DB)에 추가적인 부하를 크게 유발하지 않음을 의미합니다.

따라서 분산락을 통해 동시성 처리를 한다면 DB 부하를 줄이면서 효율적인 요청 처리가 가능할 것으로 보입니다.

예외 사항

  • 낙관락:
    • 낙관락의 경우 INSERT시 잡을 Lock 없어 작동하지 않습니다. 현 시점에서는 적용이 어려워 제외되었습니다.

고찰

테스트 방법의 적절성

  1. 목적 부합성
    • 동시성 이슈를 평가하기 위한 테스트로서, 다양한 락 전략의 성능과 안정성을 비교하는 방법은 목적에 적절합니다.
  2. 부하 조건의 타당성
    • 100명의 사용자와 1,000건의 요청은 현실적인 시나리오를 재현하기 충분하며, 반복 테스트를 통해 결과의 신뢰성을 높였습니다.
  3. 변수 통제
    • 외부 변수의 영향을 최소화하여 락 전략 자체의 성능 차이를 명확히 비교할 수 있었습니다.

예측값과 결과의 일치성

  • 예측한 대로 락 없음이 가장 빠른 성능을 보였습니다.
  • 분산락은 약간의 성능 저하에도 불구하고 안정적인 동시성 처리가 가능함을 확인하였습니다.
  • 비관락은 예상대로 성능 저하가 심각하여 실제 서비스 적용에 부적합함을 알 수 있었습니다.
  • DB 부하의 경우 예측값과 다르게, 락 없음이 가장 우수하며 분산락과의 차이가 미비함을 알 수 있습니다.

한계점 인식

  1. 테스트 환경
    • 본 테스트는 로컬 서버 환경에서 수행되었으며, 실제 운영 환경과는 차이가 있을 수 있습니다.
  2. 부하 패턴의 다양성 부족
    • 1000건의 부하 조건을 기준으로 테스트를 진행하였으나, 실제 운영 시에는 더 다양한 부하 패턴이 존재할 수 있습니다.
  3. 잠금 메커니즘의 다양성 부족
    • 본 보고서에서는 락 없음, 분산락, 비관락, 낙관락만 비교하였으며, 다른 방법에 대한 분석은 이루어지지 않았습니다.

결론

테스트 수행 방법은 동시성 이슈를 식별하고 해결 방안을 평가하는 본 연구의 목적에 부합하며, 실험 설계와 결과 분석이 적절하게 이루어졌습니다. 특히, 분산락을 활용한 동시성 처리는 데이터의 일관성을 유지하면서도 성능 저하를 최소화할 수 있는 현실적인 대안임을 확인하였습니다.


기타 발생할 수 있는 동시성 문제

  1. 예약 후 결제 확정 프로세스

    • 시나리오
      • 사용자가 좌석을 예약한 후, 결제를 확정하는 단계에서 예약에 발생하는 동시성 문제.
      • 이미 예약이 완료된 상태에서는 동일한 좌석에 대한 추가 요청이 발생할 가능성이 낮을 것으로 예상됨.
    • 동시성 제어 방식 : 비관적 락 (Pessimistic Locking)
      • 선택 이유 : 이 프로세스는 최대한 많은 성공을 보장해야 하므로, 동시에 요청이 발생할 경우 가능한 모든 요청이 성공적으로 처리되도록 해야 함.
      • 성능 :
        • 데이터 일관성이 높으며, 결제 후 좌석 확정이라는 과정의 안정성을 보정함.
        • 읽기 작업 시에도 락을 적용하기 때문에 성능 저하가 발생할 수 있음.
  2. 포인트 충전 기능

    • 시나리오
      • 사용자가 자신의 포인트를 충전하는 과정에서 발생할 수 있는 동시성 문제.
      • 포인트 충전 요청은 본인의 포인트에 한정되므로 동시 요청이 적을 것으로 예상됨.
    • 동시성 제어 방식 : 낙관적 락 (Optimistic Locking)
      • 선택 이유: 포인트 충전은 동시 요청이 적고, 한번만 성공해도 무방하므로, 일부 충전 요청이 실패하더라도 큰 문제가 되지 않음.
      • 성능 :
        • 락 사용을 최소화하여 성능 저하를 방지함.

부하 테스트에 대한 고려

해당 두 동시성 문제에 대해서는 예상되는 트래픽이 많지 않기 때문에 부하 테스트를 진행하지 않았습니다.


참고 문헌