Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/04-experiment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 결과 분석

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
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;

import java.time.Duration;
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<String> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -30,6 +31,8 @@ class RedirectServiceTest {
private ShortUrlRepository shortUrlRepository;
@Mock
private Base62Encoder base62Encoder;
@Mock
private RedirectMetrics redirectMetrics;

@Test
void 캐시에_URL이_있으면_DB를_조회하지_않는다() {
Expand Down