From a435168b224baedf33f721535039e3d08d93bb1d Mon Sep 17 00:00:00 2001 From: LocKey Date: Fri, 31 Jul 2026 17:02:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=EC=BA=90=EC=8B=9C=EC=99=80=20DB=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=A7=80=ED=91=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../urlshortener/cache/ShortUrlCache.java | 16 +++++--- .../urlshortener/metrics/RedirectMetrics.java | 37 +++++++++++++++++++ .../urlshortener/service/RedirectService.java | 4 ++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/backendsystemdesignlab/urlshortener/metrics/RedirectMetrics.java diff --git a/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java b/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java index 51f65a9..50702db 100644 --- a/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java +++ b/src/main/java/com/backendsystemdesignlab/urlshortener/cache/ShortUrlCache.java @@ -1,5 +1,7 @@ package com.backendsystemdesignlab.urlshortener.cache; +import com.backendsystemdesignlab.urlshortener.metrics.RedirectMetrics; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @@ -7,22 +9,26 @@ import java.util.Optional; @Component +@RequiredArgsConstructor public class ShortUrlCache { private static final String KEY_PREFIX = "short-url:"; private static final Duration TTL = Duration.ofHours(1); private final StringRedisTemplate redisTemplate; - - public ShortUrlCache(StringRedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; - } + private final RedirectMetrics redirectMetrics; public Optional find(String shortCode) { String longUrl = redisTemplate.opsForValue() .get(KEY_PREFIX + shortCode); - return Optional.ofNullable(longUrl); + if (longUrl != null) { + redirectMetrics.recordCacheHit(); + return Optional.of(longUrl); + } + + redirectMetrics.recordCacheMiss(); + return Optional.empty(); } public void save(String shortCode, String longUrl) { diff --git a/src/main/java/com/backendsystemdesignlab/urlshortener/metrics/RedirectMetrics.java b/src/main/java/com/backendsystemdesignlab/urlshortener/metrics/RedirectMetrics.java new file mode 100644 index 0000000..78762c8 --- /dev/null +++ b/src/main/java/com/backendsystemdesignlab/urlshortener/metrics/RedirectMetrics.java @@ -0,0 +1,37 @@ +package com.backendsystemdesignlab.urlshortener.metrics; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.stereotype.Component; + +@Component +public class RedirectMetrics { + + private final Counter cacheHitCounter; + private final Counter cacheMissCounter; + private final Counter dbLookupCounter; + + public RedirectMetrics(MeterRegistry meterRegistry) { + this.cacheHitCounter = Counter.builder("short_url.cache.hit") + .description("Redis cache hit count") + .register(meterRegistry); + + this.cacheMissCounter = Counter.builder("short_url.cache.miss") + .description("Redis cache miss count") + .register(meterRegistry); + + this.dbLookupCounter = Counter.builder("short_url.db.lookup") + .description("Short URL database lookup count") + .register(meterRegistry); + } + + public void recordCacheHit() { + cacheHitCounter.increment(); + } + public void recordCacheMiss() { + cacheMissCounter.increment(); + } + public void recordDbLookup() { + dbLookupCounter.increment(); + } +} diff --git a/src/main/java/com/backendsystemdesignlab/urlshortener/service/RedirectService.java b/src/main/java/com/backendsystemdesignlab/urlshortener/service/RedirectService.java index 5bd689e..79ad3d9 100644 --- a/src/main/java/com/backendsystemdesignlab/urlshortener/service/RedirectService.java +++ b/src/main/java/com/backendsystemdesignlab/urlshortener/service/RedirectService.java @@ -3,6 +3,7 @@ import com.backendsystemdesignlab.urlshortener.cache.ShortUrlCache; import com.backendsystemdesignlab.urlshortener.encoding.Base62Encoder; import com.backendsystemdesignlab.urlshortener.exception.ShortUrlNotFoundException; +import com.backendsystemdesignlab.urlshortener.metrics.RedirectMetrics; import com.backendsystemdesignlab.urlshortener.url.domain.ShortUrl; import com.backendsystemdesignlab.urlshortener.url.repository.ShortUrlRepository; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ public class RedirectService { private final ShortUrlRepository shortUrlRepository; private final ShortUrlCache shortUrlCache; private final Base62Encoder base62Encoder; + private final RedirectMetrics redirectMetrics; public String findLongUrl(String shortCode) { return shortUrlCache.find(shortCode) @@ -26,6 +28,8 @@ public String findLongUrl(String shortCode) { private String findFromDatabase(String shortCode) { long id = base62Encoder.decode(shortCode); + redirectMetrics.recordDbLookup(); + ShortUrl shortUrl = shortUrlRepository.findById(id) .orElseThrow(() -> new ShortUrlNotFoundException(shortCode)); From 78253134920f6a9d1651cee86adea02340d5909f Mon Sep 17 00:00:00 2001 From: LocKey Date: Fri, 31 Jul 2026 17:07:37 +0900 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20Redis=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=20=EC=A7=80=ED=91=9C=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/04-experiment.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/04-experiment.md b/docs/04-experiment.md index 874a68b..84bcc9d 100644 --- a/docs/04-experiment.md +++ b/docs/04-experiment.md @@ -179,6 +179,21 @@ Redis 조회 | 100 | p95 | 32.96ms | 11.93ms | 63.80% 감소 | | 100 | p99 | 49.88ms | 21.52ms | 56.86% 감소 | +### 캐시 동작 확인 + +100 VU 부하 테스트 후 Micrometer 지표를 확인했다. + +| 지표 | 결과 | +|---|---:| +| Cache Hit | 955,178 | +| Cache Miss | 1 | +| Cache Hit Ratio | 약 99.9999% | +| DB Lookup | 1 | + +`setup()`에서 수행한 첫 조회는 Cache Miss로 MySQL을 조회했고, +이후 반복 요청은 Redis Cache Hit로 처리됐다. + +따라서 부하 테스트 구간에서는 대부분의 요청이 MySQL 조회 없이 처리된 것을 확인했다. ## 12. 결과 분석 From 7ded1b47861be1f0f62875c4d2c3137c29b1493a Mon Sep 17 00:00:00 2001 From: LocKey Date: Fri, 31 Jul 2026 17:10:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20RedirectServiceTest=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../urlshortener/service/RedirectServiceTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/com/backendsystemdesignlab/urlshortener/service/RedirectServiceTest.java b/src/test/java/com/backendsystemdesignlab/urlshortener/service/RedirectServiceTest.java index e554e43..1814cfc 100644 --- a/src/test/java/com/backendsystemdesignlab/urlshortener/service/RedirectServiceTest.java +++ b/src/test/java/com/backendsystemdesignlab/urlshortener/service/RedirectServiceTest.java @@ -3,6 +3,7 @@ import com.backendsystemdesignlab.urlshortener.cache.ShortUrlCache; import com.backendsystemdesignlab.urlshortener.encoding.Base62Encoder; import com.backendsystemdesignlab.urlshortener.exception.ShortUrlNotFoundException; +import com.backendsystemdesignlab.urlshortener.metrics.RedirectMetrics; import com.backendsystemdesignlab.urlshortener.url.domain.ShortUrl; import com.backendsystemdesignlab.urlshortener.url.repository.ShortUrlRepository; import org.junit.jupiter.api.Test; @@ -30,6 +31,8 @@ class RedirectServiceTest { private ShortUrlRepository shortUrlRepository; @Mock private Base62Encoder base62Encoder; + @Mock + private RedirectMetrics redirectMetrics; @Test void 캐시에_URL이_있으면_DB를_조회하지_않는다() {