diff --git a/docker-compose.yml b/docker-compose.yml index 09e8ba6..8707629 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,8 @@ services: SPRING_DATA_REDIS_HOST: redis SPRING_DATA_REDIS_PORT: 6379 + CACHE_ENABLED: ${CACHE_ENABLED:-true} + depends_on: mysql: condition: service_healthy diff --git a/docs/04-experiment.md b/docs/04-experiment.md index e12a34a..1fdceba 100644 --- a/docs/04-experiment.md +++ b/docs/04-experiment.md @@ -46,7 +46,7 @@ MySQL만 사용하는 초기 리다이렉트 구조에서 VU 증가에 따라 | k6 | 2.1.0 | | Prometheus | 3.13.0 | | Grafana | 13.1.1 | -| CPU / Memory | 미기록 | +| 서버 지표 | 100 VU 실험에서 Prometheus·Grafana로 기록 | ## 6. 부하 테스트 시나리오 @@ -94,23 +94,20 @@ MySQL만 사용하는 초기 리다이렉트 구조에서 VU 증가에 따라 - HikariCP 활성·대기 연결 - MySQL 조회량 -서버 지표는 이번 측정에서 별도로 기록하지 않았다. +CPU, JVM, HikariCP, DB Lookup 지표는 100 VU 비교 실험에서 기록했다. ### Redis 적용 후 추가 지표 - 캐시 Hit 수 - 캐시 Miss 수 - 캐시 Hit Ratio -- Redis 명령 처리량 - MySQL 조회 횟수 변화 ## 8. Warm-up -- Baseline: 별도 Warm-up 미실행 -- Redis: `setup()`에서 리다이렉트를 한 번 호출해 캐시 저장 -- 측정 제외 구간: URL 생성 요청과 캐시 Warm-up 요청 -- 실제 측정 요청: Redis Cache Hit 상태 -- 한계: Baseline과 Redis의 Warm-up 조건이 완전히 동일하지 않아 최종 비교 시 재측정이 필요하다. +- DB Only: 별도 Warm-up 없음 +- Redis: `setup()`에서 최초 조회로 캐시 저장 +- URL 생성과 Redis Warm-up 요청은 측정 구간에서 제외 ## 9. Baseline 결과 @@ -179,52 +176,43 @@ Redis 조회 | 100 | p95 | 32.96ms | 11.93ms | 63.80% 감소 | | 100 | p99 | 49.88ms | 21.52ms | 56.86% 감소 | -### 캐시 동작 확인 +### 100 VU 서버 지표 비교 -100 VU 부하 테스트 후 Micrometer 지표를 확인했다. +캐시 활성화 여부만 변경한 동일한 코드에서 100 VU로 1분간 측정했다. -| 지표 | 결과 | -|---|---:| -| Cache Hit | 955,178 | -| Cache Miss | 1 | -| Cache Hit Ratio | 약 99.9999% | -| DB Lookup | 1 | - -`setup()`에서 수행한 첫 조회는 Cache Miss로 MySQL을 조회했고, -이후 반복 요청은 Redis Cache Hit로 처리됐다. +| 지표 | DB Only | Redis | +|---|---:|---:| +| DB Lookup | 366,248 | 1 | +| Cache Hit Ratio | 0% | 약 100% | +| Process CPU 최대 | 약 74% | 약 49% | +| JVM Heap 최대 | 약 160MiB | 약 192MiB | +| JVM Live Threads 최대 | 약 121 | 약 125 | +| HikariCP Active 최대 | 약 10 | 관찰되지 않음 | +| HikariCP Pending 최대 | 약 85 | 관찰되지 않음 | +| 5xx 오류율 | 0% | 0% | -따라서 부하 테스트 구간에서는 대부분의 요청이 MySQL 조회 없이 처리된 것을 확인했다. +DB Only에서는 k6 iterations와 DB Lookup이 모두 366,248건으로 일치했다. +모든 리다이렉트 요청이 MySQL을 조회한 것이다. -### Redis 100 VU 서버 지표 +HikariCP Active는 기본 최대 연결 수인 10개에 도달했고, +Pending 요청도 약 85개까지 증가했다. +DB 커넥션 대기가 p95와 p99 증가의 주요 원인으로 나타났다. -애플리케이션과 Redis를 초기화한 뒤 100 VU로 1분간 부하 테스트를 수행했다. +Redis 적용 후에는 최초 요청에서만 DB를 조회했고, +이후 요청은 Cache Hit로 처리됐다. +Prometheus 수집 시점에서는 HikariCP 연결 사용과 대기가 관찰되지 않았다. -| 지표 | 결과 | -|---|---:| -| Cache Hit | 859,174 | -| Cache Miss | 1 | -| Cache Hit Ratio | 약 99.9999% | -| DB Lookup | 1 | -| Process CPU 최대 | 약 65% | -| Process CPU 지속 구간 | 약 38~40% | -| JVM Heap 최대 | 약 200MiB | -| JVM Live Threads 최대 | 약 121 | -| HikariCP Pending | 관찰되지 않음 | -| 5xx 오류율 | 0% | +> HikariCP는 애플리케이션이 MySQL 연결을 미리 생성하고 빌려 쓰도록 관리하는 커넥션 풀이다. -최초 리다이렉트 요청에서 Cache Miss와 DB 조회가 각각 1회 발생했고, -이후 반복 요청은 Redis Cache Hit로 처리됐다. +#### Grafana 측정 결과 -Prometheus 수집 시점에서는 HikariCP 활성 및 대기 연결이 관찰되지 않았다. -다만 최초 DB 조회가 수집 간격 사이에 종료됐을 수 있으므로, -HikariCP 사용이 전혀 없었다고 단정하지 않고 DB Lookup Counter를 기준으로 판단했다. +**DB Only** -부하 구간에서 CPU 사용률과 JVM Live Thread 수가 증가했으며, -Heap은 증가 후 GC 수행에 따라 감소하는 흐름을 보였다. +![DB Only 100 VU 성능 지표](images/db-only-100vu-performance.png) -\* HikariCP: Spring Boot가 MySQL 연결을 관리하는 커넥션 풀, 미리 DB 연결을 만들어 두고 빌려 쓰는 방식 +![DB Only 100 VU DB 및 커넥션 풀 지표](images/db-only-100vu-db-pool.png) -#### Grafana 측정 결과 +**Redis** ![Redis 100 VU 성능 지표](images/redis-100vu-performance.png) @@ -232,13 +220,15 @@ Heap은 증가 후 GC 수행에 따라 감소하는 흐름을 보였다. ## 12. 결과 분석 -Redis 적용 후 모든 VU 구간에서 실패율 0%와 Check 성공률 100%를 유지했다. +Redis 적용 후 모든 VU 구간에서 실패율 0%를 유지하면서 처리량이 증가하고 p95와 p99가 감소했다. -20 VU에서는 RPS가 약 65% 증가했고 p95는 약 35% 감소했다. 50 VU와 100 VU에서는 RPS가 두 배 이상으로 증가했으며 p95와 p99도 약 56~64% 감소했다. +100 VU 서버 지표에서 DB Only는 모든 요청마다 MySQL을 조회했다. +HikariCP의 10개 커넥션이 모두 사용됐고 최대 약 85개의 요청이 커넥션을 기다렸다. -따라서 반복 조회가 많은 환경에서는 Redis Cache Hit를 통해 MySQL 조회를 생략하는 방식이 처리량과 응답 지연 개선에 효과적이었다. +Redis 적용 후에는 최초 1회를 제외한 요청이 Cache Hit로 처리됐다. +DB 조회와 커넥션 대기가 제거되면서 응답 지연이 감소하고 처리량이 증가했다. -다만 이번 테스트는 고정 VU 방식이므로 응답이 빨라지면 같은 시간 동안 더 많은 요청을 보내게 된다. 또한 CPU, HikariCP, MySQL 조회량을 기록하지 않았으므로 DB 부하가 실제로 얼마나 감소했는지는 추가 측정이 필요하다. +따라서 반복 조회가 많은 리다이렉트 경로에서는 Redis Cache Aside가 DB 접근과 커넥션 풀 병목을 줄이는 데 효과적이었다. ## 13. Platform Thread와 Virtual Thread 비교 @@ -270,19 +260,24 @@ VIRTUAL_THREADS_ENABLED=true ## 14. 실험 한계 -- 로컬 Docker 환경에서 테스트했다. -- k6, 애플리케이션, MySQL이 같은 장비의 자원을 사용한다. +- 로컬 Docker 환경에서 실행했다. +- k6, 애플리케이션, MySQL, Redis가 같은 장비의 자원을 사용했다. - 조건별 테스트를 한 번씩만 수행했다. - 테스트 시간이 조건별 1분으로 짧다. -- CPU, JVM, HikariCP, MySQL 지표를 기록하지 않았다. -- 동일한 단축 URL 하나만 반복 조회했다. -- 별도의 Warm-up 조건을 적용하지 않았다. +- 하나의 단축 URL만 반복 조회했다. +- Prometheus 수집 간격 사이의 짧은 HikariCP 사용은 그래프에서 누락될 수 있다. +- 고정 VU 방식이므로 응답 시간이 짧을수록 같은 시간에 더 많은 요청을 전송한다. ## 15. 후속 실험 - [x] Redis Cache Aside 적용 -- [x] 동일한 조건으로 Redis 적용 전후 비교 -- [ ] Prometheus와 Grafana 서버 지표 기록 +- [x] Redis 적용 전후 부하 테스트 +- [x] Prometheus·Grafana 서버 지표 비교 +- [x] 캐시 ON/OFF 환경변수 적용 - [ ] 조건별 3회 측정 후 중앙값 비교 - [ ] Platform Thread와 Virtual Thread 비교 -- [ ] 더 높은 VU로 Stress Test 수행 \ No newline at end of file +- [ ] 더 높은 VU로 Stress Test 수행 +- [ ] 단축 코드 생성 전략 비교 + - Sequence ID + Base62 + - Hash + 충돌 처리 + - 분산 ID + Base62 \ No newline at end of file diff --git a/docs/images/db-only-100vu-db-pool.png b/docs/images/db-only-100vu-db-pool.png new file mode 100644 index 0000000..46611a9 Binary files /dev/null and b/docs/images/db-only-100vu-db-pool.png differ diff --git a/docs/images/db-only-100vu-performance.png b/docs/images/db-only-100vu-performance.png new file mode 100644 index 0000000..e9314be Binary files /dev/null and b/docs/images/db-only-100vu-performance.png differ diff --git a/docs/images/redis-100vu-cache-db.png b/docs/images/redis-100vu-cache-db.png index bdbdefd..dd38654 100644 Binary files a/docs/images/redis-100vu-cache-db.png and b/docs/images/redis-100vu-cache-db.png differ diff --git a/docs/images/redis-100vu-performance.png b/docs/images/redis-100vu-performance.png index 90d6a94..8c68000 100644 Binary files a/docs/images/redis-100vu-performance.png and b/docs/images/redis-100vu-performance.png differ diff --git a/results/server-metrics/db-only-100vu-1m.txt b/results/server-metrics/db-only-100vu-1m.txt new file mode 100644 index 0000000..0ab2e2a --- /dev/null +++ b/results/server-metrics/db-only-100vu-1m.txt @@ -0,0 +1,249 @@ + + /\ Grafana /‾‾/ + /\ / \ |\ __ / / + / \/ \ | |/ / / ‾‾\ + / \ | ( | (‾) | + / __________ \ |_|\_\ \_____/ + + + execution: local + script: /scripts/db-only-redirect.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 1m30s max duration (incl. graceful stop): + * default: 100 looping VUs for 1m0s (gracefulStop: 30s) + + +running (0m01.0s), 100/100 VUs, 784 complete and 0 interrupted iterations +default [ 1% ] 100 VUs 0m00.7s/1m0s + +running (0m02.0s), 100/100 VUs, 1685 complete and 0 interrupted iterations +default [ 3% ] 100 VUs 0m01.7s/1m0s + +running (0m03.0s), 100/100 VUs, 3117 complete and 0 interrupted iterations +default [ 5% ] 100 VUs 0m02.7s/1m0s + +running (0m04.0s), 100/100 VUs, 4430 complete and 0 interrupted iterations +default [ 6% ] 100 VUs 0m03.7s/1m0s + +running (0m05.0s), 100/100 VUs, 5893 complete and 0 interrupted iterations +default [ 8% ] 100 VUs 0m04.7s/1m0s + +running (0m06.0s), 100/100 VUs, 8249 complete and 0 interrupted iterations +default [ 10% ] 100 VUs 0m05.7s/1m0s + +running (0m07.0s), 100/100 VUs, 11219 complete and 0 interrupted iterations +default [ 11% ] 100 VUs 0m06.7s/1m0s + +running (0m08.0s), 100/100 VUs, 14027 complete and 0 interrupted iterations +default [ 13% ] 100 VUs 0m07.7s/1m0s + +running (0m09.0s), 100/100 VUs, 17308 complete and 0 interrupted iterations +default [ 15% ] 100 VUs 0m08.7s/1m0s + +running (0m10.0s), 100/100 VUs, 20671 complete and 0 interrupted iterations +default [ 16% ] 100 VUs 0m09.7s/1m0s + +running (0m11.0s), 100/100 VUs, 22981 complete and 0 interrupted iterations +default [ 18% ] 100 VUs 0m10.7s/1m0s + +running (0m12.0s), 100/100 VUs, 25766 complete and 0 interrupted iterations +default [ 20% ] 100 VUs 0m11.7s/1m0s + +running (0m13.0s), 100/100 VUs, 28596 complete and 0 interrupted iterations +default [ 21% ] 100 VUs 0m12.7s/1m0s + +running (0m14.0s), 100/100 VUs, 31594 complete and 0 interrupted iterations +default [ 23% ] 100 VUs 0m13.7s/1m0s + +running (0m15.0s), 100/100 VUs, 34903 complete and 0 interrupted iterations +default [ 25% ] 100 VUs 0m14.7s/1m0s + +running (0m16.0s), 100/100 VUs, 39871 complete and 0 interrupted iterations +default [ 26% ] 100 VUs 0m15.7s/1m0s + +running (0m17.0s), 100/100 VUs, 45424 complete and 0 interrupted iterations +default [ 28% ] 100 VUs 0m16.7s/1m0s + +running (0m18.0s), 100/100 VUs, 50308 complete and 0 interrupted iterations +default [ 30% ] 100 VUs 0m17.7s/1m0s + +running (0m19.0s), 100/100 VUs, 55517 complete and 0 interrupted iterations +default [ 31% ] 100 VUs 0m18.7s/1m0s + +running (0m19.9s), 100/100 VUs, 59767 complete and 0 interrupted iterations +default [ 33% ] 100 VUs 0m19.7s/1m0s + +running (0m20.0s), 100/100 VUs, 60032 complete and 0 interrupted iterations +default [ 33% ] 100 VUs 0m19.7s/1m0s + +running (0m20.0s), 100/100 VUs, 60033 complete and 0 interrupted iterations +default [ 33% ] 100 VUs 0m19.7s/1m0s + +running (0m21.0s), 100/100 VUs, 67809 complete and 0 interrupted iterations +default [ 35% ] 100 VUs 0m20.7s/1m0s + +running (0m22.0s), 100/100 VUs, 73526 complete and 0 interrupted iterations +default [ 36% ] 100 VUs 0m21.7s/1m0s + +running (0m23.0s), 100/100 VUs, 79523 complete and 0 interrupted iterations +default [ 38% ] 100 VUs 0m22.7s/1m0s + +running (0m24.0s), 100/100 VUs, 87065 complete and 0 interrupted iterations +default [ 40% ] 100 VUs 0m23.7s/1m0s + +running (0m25.0s), 100/100 VUs, 93978 complete and 0 interrupted iterations +default [ 41% ] 100 VUs 0m24.7s/1m0s + +running (0m26.0s), 100/100 VUs, 101657 complete and 0 interrupted iterations +default [ 43% ] 100 VUs 0m25.7s/1m0s + +running (0m27.0s), 100/100 VUs, 109286 complete and 0 interrupted iterations +default [ 45% ] 100 VUs 0m26.7s/1m0s + +running (0m28.0s), 100/100 VUs, 116860 complete and 0 interrupted iterations +default [ 46% ] 100 VUs 0m27.7s/1m0s + +running (0m29.0s), 100/100 VUs, 124481 complete and 0 interrupted iterations +default [ 48% ] 100 VUs 0m28.7s/1m0s + +running (0m30.0s), 100/100 VUs, 131398 complete and 0 interrupted iterations +default [ 50% ] 100 VUs 0m29.7s/1m0s + +running (0m31.0s), 100/100 VUs, 139639 complete and 0 interrupted iterations +default [ 51% ] 100 VUs 0m30.7s/1m0s + +running (0m32.0s), 100/100 VUs, 146754 complete and 0 interrupted iterations +default [ 53% ] 100 VUs 0m31.7s/1m0s + +running (0m33.0s), 100/100 VUs, 155116 complete and 0 interrupted iterations +default [ 55% ] 100 VUs 0m32.7s/1m0s + +running (0m34.0s), 100/100 VUs, 163589 complete and 0 interrupted iterations +default [ 56% ] 100 VUs 0m33.7s/1m0s + +running (0m35.0s), 100/100 VUs, 171743 complete and 0 interrupted iterations +default [ 58% ] 100 VUs 0m34.7s/1m0s + +running (0m36.0s), 100/100 VUs, 179279 complete and 0 interrupted iterations +default [ 60% ] 100 VUs 0m35.7s/1m0s + +running (0m37.0s), 100/100 VUs, 185185 complete and 0 interrupted iterations +default [ 61% ] 100 VUs 0m36.7s/1m0s + +running (0m38.0s), 100/100 VUs, 193201 complete and 0 interrupted iterations +default [ 63% ] 100 VUs 0m37.7s/1m0s + +running (0m39.0s), 100/100 VUs, 201049 complete and 0 interrupted iterations +default [ 65% ] 100 VUs 0m38.7s/1m0s + +running (0m40.0s), 100/100 VUs, 207919 complete and 0 interrupted iterations +default [ 66% ] 100 VUs 0m39.7s/1m0s + +running (0m41.0s), 100/100 VUs, 214485 complete and 0 interrupted iterations +default [ 68% ] 100 VUs 0m40.7s/1m0s + +running (0m42.0s), 100/100 VUs, 221382 complete and 0 interrupted iterations +default [ 70% ] 100 VUs 0m41.7s/1m0s + +running (0m43.0s), 100/100 VUs, 228214 complete and 0 interrupted iterations +default [ 71% ] 100 VUs 0m42.7s/1m0s + +running (0m44.0s), 100/100 VUs, 236444 complete and 0 interrupted iterations +default [ 73% ] 100 VUs 0m43.7s/1m0s + +running (0m45.0s), 100/100 VUs, 244894 complete and 0 interrupted iterations +default [ 75% ] 100 VUs 0m44.7s/1m0s + +running (0m46.0s), 100/100 VUs, 253342 complete and 0 interrupted iterations +default [ 76% ] 100 VUs 0m45.7s/1m0s + +running (0m47.0s), 100/100 VUs, 260078 complete and 0 interrupted iterations +default [ 78% ] 100 VUs 0m46.7s/1m0s + +running (0m48.0s), 100/100 VUs, 268147 complete and 0 interrupted iterations +default [ 80% ] 100 VUs 0m47.7s/1m0s + +running (0m49.0s), 100/100 VUs, 274749 complete and 0 interrupted iterations +default [ 81% ] 100 VUs 0m48.7s/1m0s + +running (0m50.0s), 100/100 VUs, 281239 complete and 0 interrupted iterations +default [ 83% ] 100 VUs 0m49.7s/1m0s + +running (0m51.0s), 100/100 VUs, 289545 complete and 0 interrupted iterations +default [ 85% ] 100 VUs 0m50.7s/1m0s + +running (0m52.0s), 100/100 VUs, 298088 complete and 0 interrupted iterations +default [ 86% ] 100 VUs 0m51.7s/1m0s + +running (0m53.0s), 100/100 VUs, 305533 complete and 0 interrupted iterations +default [ 88% ] 100 VUs 0m52.7s/1m0s + +running (0m54.0s), 100/100 VUs, 314059 complete and 0 interrupted iterations +default [ 90% ] 100 VUs 0m53.7s/1m0s + +running (0m55.0s), 100/100 VUs, 322416 complete and 0 interrupted iterations +default [ 91% ] 100 VUs 0m54.7s/1m0s + +running (0m56.0s), 100/100 VUs, 330667 complete and 0 interrupted iterations +default [ 93% ] 100 VUs 0m55.7s/1m0s + +running (0m57.0s), 100/100 VUs, 339257 complete and 0 interrupted iterations +default [ 95% ] 100 VUs 0m56.7s/1m0s + +running (0m58.0s), 100/100 VUs, 347085 complete and 0 interrupted iterations +default [ 96% ] 100 VUs 0m57.7s/1m0s + +running (0m59.0s), 100/100 VUs, 355283 complete and 0 interrupted iterations +default [ 98% ] 100 VUs 0m58.7s/1m0s + +running (1m00.0s), 100/100 VUs, 363698 complete and 0 interrupted iterations +default [ 100% ] 100 VUs 0m59.7s/1m0s + + + █ THRESHOLDS + + checks + ✓ 'rate>0.99' rate=100.00% + + http_req_duration{endpoint:redirect} + ✓ 'p(95)<500' p(95)=48.15ms + + http_req_failed + ✓ 'rate<0.01' rate=0.00% + + + █ TOTAL RESULTS + + checks_total.......: 1098746 18225.179376/s + checks_succeeded...: 100.00% 1098746 out of 1098746 + checks_failed......: 0.00% 0 out of 1098746 + + ✓ 단축 URL 생성 상태는 201이다 + ✓ shortCode가 반환된다 + ✓ 리다이렉트 상태는 302이다 + ✓ Location 헤더가 존재한다 + ✓ 원본 URL이 올바르다 + + HTTP + http_req_duration..............: avg=16.14ms min=463.75µs med=10.74ms p(90)=34.41ms p(95)=48.15ms p(99)=106.78ms max=561.18ms + { endpoint:redirect }........: avg=16.13ms min=463.75µs med=10.74ms p(90)=34.41ms p(95)=48.15ms p(99)=106.78ms max=561.18ms + { expected_response:true }...: avg=16.14ms min=463.75µs med=10.74ms p(90)=34.41ms p(95)=48.15ms p(99)=106.78ms max=561.18ms + http_req_failed................: 0.00% 0 out of 366249 + http_reqs......................: 366249 6075.065321/s + + EXECUTION + iteration_duration.............: avg=16.36ms min=548µs med=10.91ms p(90)=34.74ms p(95)=48.44ms p(99)=107.26ms max=561.34ms + iterations.....................: 366248 6075.048734/s + vus............................: 100 min=100 max=100 + vus_max........................: 100 min=100 max=100 + + NETWORK + data_received..................: 39 MB 651 kB/s + data_sent......................: 26 MB 437 kB/s + + + + +running (1m00.3s), 000/100 VUs, 366248 complete and 0 interrupted iterations +default ✓ [ 100% ] 100 VUs 1m0s diff --git a/results/server-metrics/redis-100vu-1m.txt b/results/server-metrics/redis-100vu-1m.txt new file mode 100644 index 0000000..347299c --- /dev/null +++ b/results/server-metrics/redis-100vu-1m.txt @@ -0,0 +1,243 @@ + + /\ Grafana /‾‾/ + /\ / \ |\ __ / / + / \/ \ | |/ / / ‾‾\ + / \ | ( | (‾) | + / __________ \ |_|\_\ \_____/ + + + execution: local + script: /scripts/redis-redirect.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 1m30s max duration (incl. graceful stop): + * default: 100 looping VUs for 1m0s (gracefulStop: 30s) + + +running (0m01.0s), 100/100 VUs, 1005 complete and 0 interrupted iterations +default [ 1% ] 100 VUs 0m00.7s/1m0s + +running (0m02.0s), 100/100 VUs, 3816 complete and 0 interrupted iterations +default [ 3% ] 100 VUs 0m01.7s/1m0s + +running (0m03.0s), 100/100 VUs, 8277 complete and 0 interrupted iterations +default [ 5% ] 100 VUs 0m02.7s/1m0s + +running (0m04.0s), 100/100 VUs, 13074 complete and 0 interrupted iterations +default [ 6% ] 100 VUs 0m03.7s/1m0s + +running (0m05.0s), 100/100 VUs, 18520 complete and 0 interrupted iterations +default [ 8% ] 100 VUs 0m04.7s/1m0s + +running (0m06.0s), 100/100 VUs, 24119 complete and 0 interrupted iterations +default [ 10% ] 100 VUs 0m05.7s/1m0s + +running (0m07.0s), 100/100 VUs, 31684 complete and 0 interrupted iterations +default [ 11% ] 100 VUs 0m06.7s/1m0s + +running (0m08.0s), 100/100 VUs, 39291 complete and 0 interrupted iterations +default [ 13% ] 100 VUs 0m07.7s/1m0s + +running (0m09.0s), 100/100 VUs, 47389 complete and 0 interrupted iterations +default [ 15% ] 100 VUs 0m08.7s/1m0s + +running (0m10.0s), 100/100 VUs, 54511 complete and 0 interrupted iterations +default [ 16% ] 100 VUs 0m09.8s/1m0s + +running (0m11.0s), 100/100 VUs, 62714 complete and 0 interrupted iterations +default [ 18% ] 100 VUs 0m10.7s/1m0s + +running (0m12.0s), 100/100 VUs, 69030 complete and 0 interrupted iterations +default [ 20% ] 100 VUs 0m11.7s/1m0s + +running (0m13.0s), 100/100 VUs, 80045 complete and 0 interrupted iterations +default [ 21% ] 100 VUs 0m12.7s/1m0s + +running (0m14.0s), 100/100 VUs, 95700 complete and 0 interrupted iterations +default [ 23% ] 100 VUs 0m13.8s/1m0s + +running (0m15.0s), 100/100 VUs, 107595 complete and 0 interrupted iterations +default [ 25% ] 100 VUs 0m14.7s/1m0s + +running (0m16.0s), 100/100 VUs, 117221 complete and 0 interrupted iterations +default [ 26% ] 100 VUs 0m15.7s/1m0s + +running (0m17.0s), 100/100 VUs, 131460 complete and 0 interrupted iterations +default [ 28% ] 100 VUs 0m16.7s/1m0s + +running (0m18.0s), 100/100 VUs, 147333 complete and 0 interrupted iterations +default [ 30% ] 100 VUs 0m17.7s/1m0s + +running (0m19.0s), 100/100 VUs, 162286 complete and 0 interrupted iterations +default [ 31% ] 100 VUs 0m18.7s/1m0s + +running (0m20.0s), 100/100 VUs, 176164 complete and 0 interrupted iterations +default [ 33% ] 100 VUs 0m19.7s/1m0s + +running (0m21.0s), 100/100 VUs, 185386 complete and 0 interrupted iterations +default [ 35% ] 100 VUs 0m20.7s/1m0s + +running (0m22.0s), 100/100 VUs, 196370 complete and 0 interrupted iterations +default [ 36% ] 100 VUs 0m21.7s/1m0s + +running (0m23.0s), 100/100 VUs, 208677 complete and 0 interrupted iterations +default [ 38% ] 100 VUs 0m22.7s/1m0s + +running (0m24.0s), 100/100 VUs, 221011 complete and 0 interrupted iterations +default [ 40% ] 100 VUs 0m23.7s/1m0s + +running (0m25.0s), 100/100 VUs, 235202 complete and 0 interrupted iterations +default [ 41% ] 100 VUs 0m24.7s/1m0s + +running (0m26.0s), 100/100 VUs, 250884 complete and 0 interrupted iterations +default [ 43% ] 100 VUs 0m25.7s/1m0s + +running (0m27.0s), 100/100 VUs, 269014 complete and 0 interrupted iterations +default [ 45% ] 100 VUs 0m26.7s/1m0s + +running (0m28.0s), 100/100 VUs, 285684 complete and 0 interrupted iterations +default [ 46% ] 100 VUs 0m27.7s/1m0s + +running (0m29.0s), 100/100 VUs, 299860 complete and 0 interrupted iterations +default [ 48% ] 100 VUs 0m28.7s/1m0s + +running (0m30.0s), 100/100 VUs, 313120 complete and 0 interrupted iterations +default [ 50% ] 100 VUs 0m29.7s/1m0s + +running (0m31.0s), 100/100 VUs, 327749 complete and 0 interrupted iterations +default [ 51% ] 100 VUs 0m30.7s/1m0s + +running (0m32.0s), 100/100 VUs, 344858 complete and 0 interrupted iterations +default [ 53% ] 100 VUs 0m31.7s/1m0s + +running (0m33.0s), 100/100 VUs, 362120 complete and 0 interrupted iterations +default [ 55% ] 100 VUs 0m32.7s/1m0s + +running (0m34.0s), 100/100 VUs, 380528 complete and 0 interrupted iterations +default [ 56% ] 100 VUs 0m33.7s/1m0s + +running (0m35.0s), 100/100 VUs, 399850 complete and 0 interrupted iterations +default [ 58% ] 100 VUs 0m34.7s/1m0s + +running (0m36.0s), 100/100 VUs, 419275 complete and 0 interrupted iterations +default [ 60% ] 100 VUs 0m35.7s/1m0s + +running (0m37.0s), 100/100 VUs, 438723 complete and 0 interrupted iterations +default [ 61% ] 100 VUs 0m36.7s/1m0s + +running (0m38.0s), 100/100 VUs, 458403 complete and 0 interrupted iterations +default [ 63% ] 100 VUs 0m37.7s/1m0s + +running (0m39.0s), 100/100 VUs, 476362 complete and 0 interrupted iterations +default [ 65% ] 100 VUs 0m38.7s/1m0s + +running (0m40.0s), 100/100 VUs, 495524 complete and 0 interrupted iterations +default [ 66% ] 100 VUs 0m39.7s/1m0s + +running (0m41.0s), 100/100 VUs, 507840 complete and 0 interrupted iterations +default [ 68% ] 100 VUs 0m40.7s/1m0s + +running (0m42.0s), 100/100 VUs, 524958 complete and 0 interrupted iterations +default [ 70% ] 100 VUs 0m41.7s/1m0s + +running (0m43.0s), 100/100 VUs, 544878 complete and 0 interrupted iterations +default [ 71% ] 100 VUs 0m42.7s/1m0s + +running (0m44.0s), 100/100 VUs, 564211 complete and 0 interrupted iterations +default [ 73% ] 100 VUs 0m43.7s/1m0s + +running (0m45.0s), 100/100 VUs, 583160 complete and 0 interrupted iterations +default [ 75% ] 100 VUs 0m44.7s/1m0s + +running (0m46.0s), 100/100 VUs, 603935 complete and 0 interrupted iterations +default [ 76% ] 100 VUs 0m45.7s/1m0s + +running (0m47.0s), 100/100 VUs, 624147 complete and 0 interrupted iterations +default [ 78% ] 100 VUs 0m46.7s/1m0s + +running (0m48.0s), 100/100 VUs, 644234 complete and 0 interrupted iterations +default [ 80% ] 100 VUs 0m47.7s/1m0s + +running (0m49.0s), 100/100 VUs, 663155 complete and 0 interrupted iterations +default [ 81% ] 100 VUs 0m48.7s/1m0s + +running (0m50.0s), 100/100 VUs, 682048 complete and 0 interrupted iterations +default [ 83% ] 100 VUs 0m49.7s/1m0s + +running (0m51.0s), 100/100 VUs, 699547 complete and 0 interrupted iterations +default [ 85% ] 100 VUs 0m50.7s/1m0s + +running (0m52.0s), 100/100 VUs, 718857 complete and 0 interrupted iterations +default [ 86% ] 100 VUs 0m51.7s/1m0s + +running (0m53.0s), 100/100 VUs, 738948 complete and 0 interrupted iterations +default [ 88% ] 100 VUs 0m52.7s/1m0s + +running (0m54.0s), 100/100 VUs, 757778 complete and 0 interrupted iterations +default [ 90% ] 100 VUs 0m53.7s/1m0s + +running (0m55.0s), 100/100 VUs, 772926 complete and 0 interrupted iterations +default [ 91% ] 100 VUs 0m54.7s/1m0s + +running (0m56.0s), 100/100 VUs, 787881 complete and 0 interrupted iterations +default [ 93% ] 100 VUs 0m55.7s/1m0s + +running (0m57.0s), 100/100 VUs, 803120 complete and 0 interrupted iterations +default [ 95% ] 100 VUs 0m56.7s/1m0s + +running (0m58.0s), 100/100 VUs, 821236 complete and 0 interrupted iterations +default [ 96% ] 100 VUs 0m57.7s/1m0s + +running (0m59.0s), 100/100 VUs, 839933 complete and 0 interrupted iterations +default [ 98% ] 100 VUs 0m58.7s/1m0s + +running (1m00.0s), 100/100 VUs, 853770 complete and 0 interrupted iterations +default [ 100% ] 100 VUs 0m59.7s/1m0s + + + █ THRESHOLDS + + checks + ✓ 'rate>0.99' rate=100.00% + + http_req_duration{endpoint:redirect} + ✓ 'p(95)<500' p(95)=16.5ms + + http_req_failed + ✓ 'rate<0.01' rate=0.00% + + + █ TOTAL RESULTS + + checks_total.......: 2576081 42761.190907/s + checks_succeeded...: 100.00% 2576081 out of 2576081 + checks_failed......: 0.00% 0 out of 2576081 + + ✓ 단축 URL 생성 상태는 201이다 + ✓ shortCode가 반환된다 + ✓ 리다이렉트 상태는 302이다 + ✓ Location 헤더가 존재한다 + ✓ 원본 URL이 올바르다 + + HTTP + http_req_duration..............: avg=6.75ms min=298.37µs med=5.07ms p(90)=11.76ms p(95)=16.5ms p(99)=32.8ms max=190.37ms + { endpoint:redirect }........: avg=6.75ms min=298.37µs med=5.07ms p(90)=11.76ms p(95)=16.5ms p(99)=32.79ms max=190.37ms + { expected_response:true }...: avg=6.75ms min=298.37µs med=5.07ms p(90)=11.76ms p(95)=16.5ms p(99)=32.8ms max=190.37ms + http_req_failed................: 0.00% 0 out of 858695 + http_reqs......................: 858695 14253.752435/s + + EXECUTION + iteration_duration.............: avg=6.96ms min=384.95µs med=5.23ms p(90)=12.18ms p(95)=17.04ms p(99)=33.25ms max=190.49ms + iterations.....................: 858693 14253.719236/s + vus............................: 100 min=100 max=100 + vus_max........................: 100 min=100 max=100 + + NETWORK + data_received..................: 92 MB 1.5 MB/s + data_sent......................: 62 MB 1.0 MB/s + + + + +running (1m00.2s), 000/100 VUs, 858693 complete and 0 interrupted iterations +default ✓ [ 100% ] 100 VUs 1m0s diff --git a/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java b/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java index 50702db..7fda9fc 100644 --- a/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java +++ b/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java @@ -2,6 +2,7 @@ import com.backendsystemdesignlab.urlshortener.metrics.RedirectMetrics; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @@ -9,7 +10,6 @@ import java.util.Optional; @Component -@RequiredArgsConstructor public class ShortUrlCache { private static final String KEY_PREFIX = "short-url:"; @@ -17,8 +17,24 @@ public class ShortUrlCache { private final StringRedisTemplate redisTemplate; private final RedirectMetrics redirectMetrics; + private final boolean cacheEnabled; + + public ShortUrlCache( + StringRedisTemplate redisTemplate, + RedirectMetrics redirectMetrics, + @Value("${app.cache.enabled:true}") boolean cacheEnabled + ) { + this.redisTemplate = redisTemplate; + this.redirectMetrics = redirectMetrics; + this.cacheEnabled = cacheEnabled; + } public Optional find(String shortCode) { + // DB Only 실험에서는 Redis 조회X + if (!cacheEnabled) { + return Optional.empty(); + } + String longUrl = redisTemplate.opsForValue() .get(KEY_PREFIX + shortCode); @@ -32,6 +48,10 @@ public Optional find(String shortCode) { } public void save(String shortCode, String longUrl) { + if (!cacheEnabled) { + return; + } + redisTemplate.opsForValue() .set(KEY_PREFIX + shortCode, longUrl, TTL); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 26cb324..3e21191 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,4 +49,8 @@ management: distribution: percentiles-histogram: - http.server.requests: true \ No newline at end of file + http.server.requests: true + +app: + cache: + enabled: ${CACHE_ENABLED:true} \ No newline at end of file