새소식

Java

[Spring] MySQL 과 Redis 의 Lock 성능비교 with k6s

  • -
728x90
성능 고도화를 위해 Redis 의 분산락을 사용하여 쿠폰 발급에 대한 성능을 최적화 해봤습니다. 성능을 보다 깊게 파보며 MySQL 과 Redis 락이 많은 트래픽에서 어디까지 성능을 낼 수 있는 지에 대한 검증을 이 글을 통해서 풀어보겠습니다!

 

이전에 선착순 이벤트에서 다뤘던 두가지 락에 대해서 직접 성능 테스트를 해보고 "왜 분산락을 사용했는지", "DB락보다 성능이 어떻게 얼마나 좋은지" 등에 대한 것들을 공유하며 깊은 이해를 해보려고 합니다!

이에 대해 자세히 궁금하신 분은 이전의 포스트를 읽고 오시는 것을 추천드립니다!

 

 

[Spring] 선착순 이벤트를 위한 모든 것 with Redis

선착순 쿠폰 발급 시스템을 진행하며 다뤘던 내용들이 너무나도 유용했고 재밌었기에 이를 공유하고 싶다는 욕심이 생겨 이 글을 작성한다. 들어가기 앞서, 간만에 글을 작성하려고 하니 너무

seung-seok.tistory.com

 

[Spring] 선착순 이벤트를 위한 모든 것 with Redis 고도화!

이전에 작성했던 선착순 이벤트의 동시성까지는 제어해냈다. 하지만 많은 트래픽에서도 고성능을 내기 위한 최적화, 그 이야기   [Spring] 선착순 이벤트를 위한 모든 것 with Redis선착순 쿠폰 발

seung-seok.tistory.com

 

 

우선 이번 시간에는 locust 가 아닌 k6s 를 통해서 테스트를 진행해보려고 합니다. 

 

Locust 는 파이썬 기반 스크립트 작성을 해야 하며, 로컬 환경에서 약간의 과도한 리소스를 소모했습니다. k6s 는 JavaScript 기반의 스크립트 작성이 직관적이고 간단합니다. 또한  단일 프로세스로도 높은 동시 사용자를 안정적으로 시뮬레이션 할 수 있다고 판단했습니다.

 

동시에 RPS, 응답 시간, 실패율과 같은 주요 성능 지표를 더 직관적으로 확인할 수 있으며, stages 옵션으로 점진적 부하 테스트, 테스트 결과를 HTML 리포트로 저장 및 Slack 알림과 같은 추가적인 기능을 지원합니다.

 

설치 또한 간편합니다. 저는 Mac 에 homebrew 로 간편하게 설치할 수 있었습니다.

brew install k6

 

코드 또한 Javascript 기반으로 굉장히 간편합니다. 저는 기본적인 기능만 사용해서 테스트를 진행했습니다.

import http from 'k6/http';
import { sleep } from 'k6';

export let options = {
  vus: 10000,            // 동시 사용자 수
  duration: '30s',      // 테스트 지속 시간
};

export default function () {
  const payload = JSON.stringify({
    userId: Math.floor(Math.random() * 10000000) + 1,
    couponId: 1,
  });

  const headers = {
    'Content-Type': 'application/json',
  };

  http.post('http://localhost:8080/v1/issue', payload, { headers });
  sleep(1);  // 사용자 요청 간 간격
}

 

 

  • vus: 10000: 10,000명의 사용자가 동시에 동작
  • duration: 30s: 각 사용자는 30초 동안 default function을 루프 실행함

즉, 30초 동안 10,000명이 요청을 보내는 부하를 시뮬레이션입니다.

 

k6s 는 아래의 명령어를 통해 보다 간편한 테스트가 가능합니다.

k6 run script.js

 

 

해당 테스트는 로컬 환경에서의 테스트입니다.

MySQL  테스트 결과

     data_received..................: 1.0 MB 17 kB/s
     data_sent......................: 2.0 MB 34 kB/s
     http_req_blocked...............: avg=292.95ms min=0s       med=321.19ms max=19.54s   p(90)=443.88ms p(95)=522.21ms
     http_req_connecting............: avg=292.32ms min=0s       med=320.39ms max=19.53s   p(90)=442.35ms p(95)=522.13ms
     http_req_duration..............: avg=18.23s   min=0s       med=12.87s   max=59.14s   p(90)=50.22s   p(95)=55.01s
       { expected_response:true }...: avg=30.68s   min=966.56ms med=31.02s   max=59.14s   p(90)=53.8s    p(95)=57.03s
     http_req_failed................: 43.35% ✓ 4862       ✗ 6353
     http_req_receiving.............: avg=184.66µs min=0s       med=99µs     max=43.29ms  p(90)=259µs    p(95)=444.29µs
     http_req_sending...............: avg=11.97ms  min=0s       med=1.74ms   max=235.29ms p(90)=20.17ms  p(95)=58.32ms
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=18.22s   min=0s       med=12.71s   max=59.13s   p(90)=50.21s   p(95)=55s
     http_reqs......................: 11215  186.168168/s
     iteration_duration.............: avg=25.2s    min=1s       med=26.93s   max=59.61s   p(90)=50.97s   p(95)=54.95s
     iterations.....................: 11099  184.242577/s
     vus............................: 5144   min=5144     max=10000
     vus_max........................: 10000  min=10000    max=10000


running (1m00.2s), 00000/10000 VUs, 11080 complete and 5033 interrupted iterations
default ✓ [======================================] 10000 VUs  30s

 

핵심 지표를 해석해 보겠습니다.

 

http_req_failed : 43.35%

  • 성공: 6,353건 / 실패: 4,862건, 실패율이 거의 절반에 가깝습니다.
  • 거의 2건 중 1건은 실패한 셈이고, 이는 시스템의 병목 또는 과부하 상태를 나타냅니다.
  • 실패 원인은 타임아웃, 연결 실패, DB 락, 큐 대기 등 가능성이 있습니다.
    • 주요 실패 원인은 락 획득 실패와 커넥션 풀의 고갈이였습니다.

http_req_duration (응답 전체 시간): avg = 18.23s, p(90) = 50.22s, max = 59.14s

  • 평균이 18.23초, 90% 지점이 50초 이상, 대부분의 요청이 비정상적으로 오래 걸리는 상황이 발생합니다.
  • 상위 10% 사용자는 거의 1분 이상 대기
  • 이는 실시간 서비스로는 불가능한 수준

vus: 실제 5144 VUs 동시 실행

  • 나머지 4856명은 거의 요청을 제대로 시작하지도 못하고 실패, 설정은 10000명인데, 동시에 5144명만 처리 가능
  • 이는 로컬 머신의 자원(CPU, 메모리, 소켓 수)의 한계를 의미합니다.

이와 같이 테스트 결과가 매우 좋지 않았습니다. 이를 해결하기 위해 DB 커넥션 풀의 개수를 조정해 보겠습니다. 

 

✅ 왜 DB 커넥션 풀이 병목이 될까?

Spring Boot (HikariCP 기준)에서는 기본 maximumPoolSize가 10개로 설정되어 있습니다. 즉, 아무리 1000명이 동시에 요청을 보내도, DB와 실제로 연결할 수 있는 건 10명뿐 → 나머지는 대기 또는 타임아웃 → 실패율이 증가할 수 밖에 없습니다.

 

그렇다면 커넥션 풀의 개수를 150개로 증가시키고 같은 환경에서 테스트를 진행해 보겠습니다.

 

     data_received..................: 4.4 MB 74 kB/s
     data_sent......................: 3.6 MB 61 kB/s
     http_req_blocked...............: avg=107.81ms min=0s    med=5µs    max=929.87ms p(90)=596.35ms p(95)=654.49ms
     http_req_connecting............: avg=106.53ms min=0s    med=0s     max=878.9ms  p(90)=595.85ms p(95)=654.12ms
     http_req_duration..............: avg=10.17s   min=0s    med=12.49s max=22.71s   p(90)=19.12s   p(95)=19.33s
       { expected_response:true }...: avg=14.17s   min=1.18s med=13.68s max=22.71s   p(90)=19.26s   p(95)=19.37s
     http_req_failed................: 28.24% ✓ 8309       ✗ 21105
     http_req_receiving.............: avg=125.55µs min=0s    med=70µs   max=113.19ms p(90)=195µs    p(95)=296µs
     http_req_sending...............: avg=5.36ms   min=0s    med=22µs   max=575.57ms p(90)=5.63ms   p(95)=17.92ms
     http_req_tls_handshaking.......: avg=0s       min=0s    med=0s     max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=10.16s   min=0s    med=12.49s max=22.71s   p(90)=19.12s   p(95)=19.33s
     http_reqs......................: 29414  489.898382/s
     iteration_duration.............: avg=12.98s   min=1s    med=13.83s max=27.01s   p(90)=20.36s   p(95)=26.93s
     iterations.....................: 29414  489.898382/s
     vus............................: 99     min=99       max=10000
     vus_max........................: 10000  min=10000    max=10000


running (1m00.0s), 00000/10000 VUs, 29414 complete and 99 interrupted iterations
default ✓ [======================================] 10000 VUs  30s

 

평균 응답 속도는 10.17s 로 단축됐지만 P90 과 P95 를 보았을 때 약 20초 가량의 시간이 소요되고 있으며 실패율도 28%로 실무에선 도저히 사용할 수 없는 수치에 가깝습니다.

 

또한 락 획득 실패와, 커넥션 획득 실패에 대한 에러가 동일하게 발생합니다.

이러한 테스트를 통해 대량의 트래픽에서 DB 락은 성능 저하의 주된 병목이 될 수 있다는 것을 제대로 느낄 수 있었습니다 .. !

 

	 data_received..................: 33 MB  551 kB/s
     data_sent......................: 28 MB  467 kB/s
     http_req_blocked...............: avg=51.36ms  min=0s    med=2µs      max=1.82s p(90)=12µs     p(95)=365.45ms
     http_req_connecting............: avg=51.23ms  min=0s    med=0s       max=1.7s  p(90)=0s       p(95)=364.66ms
     http_req_duration..............: avg=319.31ms min=0s    med=113.23ms max=4.28s p(90)=670.86ms p(95)=1.15s
       { expected_response:true }...: avg=327.62ms min=143µs med=122.09ms max=4.28s p(90)=691.19ms p(95)=1.17s
     http_req_failed................: 2.53%  ✓ 4095        ✗ 157459
     http_req_receiving.............: avg=3.37ms   min=0s    med=19µs     max=3.08s p(90)=333µs    p(95)=2.89ms
     http_req_sending...............: avg=5.24ms   min=0s    med=8µs      max=1.84s p(90)=1.36ms   p(95)=17.07ms
     http_req_tls_handshaking.......: avg=0s       min=0s    med=0s       max=0s    p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=310.69ms min=0s    med=111.27ms max=4.24s p(90)=643.87ms p(95)=1.12s
     http_reqs......................: 161554 2692.171052/s
     iteration_duration.............: avg=2.04s    min=1s    med=1.19s    max=27.3s p(90)=2.76s    p(95)=4.3s
     iterations.....................: 161554 2692.171052/s
     vus............................: 100    min=100       max=10000
     vus_max........................: 10000  min=10000     max=10000

 

동일한 환경에서 테스트 한 결과입니다. 평균 응답 속도는 319.ms 로 약 300배 이상 나아진 성능을 확인할 수 있었습니다. 또한 실패율도 2.53%로 거의 미비한 수준의 결과였습니다.

 

2개의 테스트를 표로 정리해보았습니다!

항목 MySQL 락 기반 Redis 분산락 기반
동시 사용자 수 (VUs) 10,000 10,000
테스트 지속 시간 60초 60초
총 요청 수 (http_reqs) 29,414 161,554
RPS (reqs/sec) 490 2,692 ⚡️
평균 응답 시간 (http_req_duration) 10.17s 319ms
p90 응답 시간 19.12s 670ms
p95 응답 시간 19.33s 1.17s
최대 응답 시간 22.71s 4.28s
요청 실패율 (http_req_failed) 28.24% 2.53%
실제 처리된 iteration 수 29,414 161,554 ✅
connection block 평균 107.81ms 51.36ms
처리 안정성 ❌ 락 경합, 타임아웃 잦음 ✅ 높은 처리량 + 낮은 실패율

 

 

동일한 환경에서 세 가지 방식(DB Lock, 커넥션 풀 조정, Redis 분산 락)을 테스트한 결과,

  • RPS는 약 5배
  • 평균 응답 시간은 약 300배
  • 실패율은 14배

이상 차이가 발생했습니다.

 

이처럼 같은 기능을 수행하더라도 어떤 아키텍처를 선택하고, 어떤 방식으로 구성하느냐에 따라시스템의 성능과 안정성은 극적으로 달라질 수 있음을 체감했습니다. 특히 대용량 트래픽 환경에서는 단순한 기술 도입보다, 트래픽 흐름, 락 범위, 자원 분산 구조에 대한 설계적 고민이 필수적이라는 것을 배울 수 있었습니다!

 

여기까지!

 

728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.