์๋ ํ์ธ์. ์ ๋ ํ์ฌ ๋์ถ ๋ฐ ํฌ์ ์ฐ๊ณ ํ๋ซํผ์์ ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ก ๊ทผ๋ฌด ์ค์ธ 2๋ ์ฐจ ๊ฐ๋ฐ์์ ๋๋ค. ์ด๋ฒ ๊ธ์์๋ Look-aside ์บ์ ํจํด์ ํ์ฉํ์ฌ ์ธ๋ถ API ์ฑ๋ฅ์ ๊ฐ์ ํ ๊ฒฝํ์ ๊ณต์ ํ๋ ค๊ณ ํฉ๋๋ค.
์ด ๊ณผ์ ์์ ์ ๊ฐ ์ง๋ฉดํ๋ ๋ฌธ์ ๋ค์ ๋จ์ํ์ง ์์์ต๋๋ค. API ํธ์ถ์ ์ง์ฐ, ์ค๋ณต ํธ์ถ๋ก ์ธํ ๋น์ฉ ๋ฌธ์ , ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํ ๋ฑ ๋ค์ํ ๋ฌธ์ ๋ค์ด ์ฝํ ์์์ต๋๋ค.
ํนํ, ์ธ๋ถ API์ ํ๊ท ์๋ต ์๊ฐ์ด ์ฝ 5100ms์ ๋ฌํ๋ฉด์ ์ฌ์ฉ์ ๋๊ธฐ ์๊ฐ์ด ์ง๋์น๊ฒ ๊ธธ์ด์ก๊ณ , ์ด๋ ์ฌ์ฉ์ ์ดํ๋ก ์ด์ด์ง ์ ์๋ ์ค๋ํ ๋ฌธ์ ๋ก ์ธ์๋์์ต๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ค์ ๋จ์ํ ์ธ๋ถ API ๊ฐ์ ์๊ตฌ์๋ง ์์กดํ๊ธฐ์๋ ์ด๋ ค์ ๊ธฐ์, ์ธ๋ถ API์ ์ฑ๋ฅ๊ฐ์ ์ ์ํด ๋ด๊ฐ ํ ์ ์๋ ๊ฑด ์์๊น ๊ณ ๋ฏผํ๋ค ์บ์๋ฅผ ๋์ ํ๊ธฐ๋ก ๊ฒฐ์ ํ๊ฒ ๋์ต๋๋ค.
์ด ๊ธ์์๋ Look-aside ์บ์์ ๊ฐ๋ , ๊ตฌํ ๊ณผ์ , ๊ทธ๋ฆฌ๊ณ ์ค์ ์ ์ฉ ๊ฒฐ๊ณผ์ ๋ํด ๋ค๋ฃจ๋ ค ํฉ๋๋ค.
์ ํฌ ํ์ฌ์ ๋์ถ ์ค๊ณ ์ํ ์ค ํ๋์ธ ์ฃผํ๋ด๋ณด๋์ถ ์๋น์ค๋ ์ฌ์ฉ์๊ฐ ์ฃผํ ์ฃผ์๋ฅผ ์ ๋ ฅํ๋ฉด ํด๋น ์ฃผํ์ ์์ธ์ ๋ฐ๋ผ ๋์ถ ํ๋๋ฅผ ๊ณ์ฐํด ๋ณด์ฌ์ค๋๋ค. ์ฌ๊ธฐ์ ํต์ฌ์ ์ฃผํ ์์ธ ๋ฐ ๋จ์ง, ํํ ์ ๋ณด ๋ฐ์ดํฐ๋ก, ์ด ์ ๋ณด๋ฅผ ์ธ๋ถ API๋ฅผ ํตํด ๊ฐ์ ธ์ค๊ณ ์์์ต๋๋ค.
์๋ต ์๋ ์ง์ฐ: ์ธ๋ถ API์ ํ๊ท ์๋ต ์๊ฐ์ด ์ฝ 5100ms๋ก, ์ฌ์ฉ์ ๋๊ธฐ ์๊ฐ์ด ์ง๋์น๊ฒ ๊ธธ์ด์ก์ต๋๋ค.
์ธ๋ถ ์์คํ ๊ฐ์ ์ ์ด๋ ค์: ๊ณ ๊ฐ์ฌ์ ์ ์ฅ์์ ์ฑ๋ฅ๊ฐ์ ์ ์๊ตฌํ๋ค ํ๋๋ผ๋ ๊ฐ์ ์ด ๋ฐ๋ก ์ด๋ฃจ์ด์ง๊ธฐ์ ์ด๋ ค์์ด ์์์ต๋๋ค.
์ค๋ณต ์์ฒญ ๋ฐ์: ๋์ถ ์ ์ฒญ ํ๋ก์ธ์ค ์ค ์์ ์ ์ฅ ๊ธฐ๋ฅ์ด ์์ด, ๋์ผํ ์ฃผ์์ ๋ํด ์ค๋ณต ์กฐํ๊ฐ ๋ฐ์ํ์ต๋๋ค.
๋น์ฉ ๋ฌธ์ : ์ธ๋ถ API ํธ์ถ ๋น์ฉ์ด ๋์ ๋๋ฉด์ ์ด์ ๋น์ฉ์ด ์ฆ๊ฐํ์ต๋๋ค.
(์ธ๋ถ API ์๋ต์๋ ์ต์ 3006ms ์ต๋ 9412ms)
๐ API ์๋ต ์๋ ๋ถ์
- ์ต์ : 3006ms
- ํ๊ท : 5132ms
- ์ต๋: 9412ms
์ด๋ก ์ธํด ์ฌ์ฉ์ ๋ถ๋ง์ด ์ฆ๊ฐํ๊ณ , ์๋น์ค ํ์ง ์ ํ๋ก ์ด์ด์ก์ต๋๋ค. ํนํ, ๋์ถ ์ ์ฒญ ๊ณผ์ ์์์ ๋ฐ๋ณต ํธ์ถ๋ก ์ธํด ๋ถํ์ํ ๋น์ฉ์ด ๋ฐ์ํ์ต๋๋ค.
์ธ๋ถ API์ ์ฑ๋ฅ์ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก Look-aside ์บ์ ํจํด์ ์ ํํ์ต๋๋ค.
Look-aside ์บ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค(DB) ๋๋ ์ธ๋ถ API์ ์ ๊ทผํ๊ธฐ ์ ์ ์บ์๋ฅผ ๋จผ์ ํ์ธํ๋ ์บ์ฑ ์ ๋ต์ ๋๋ค. ๋ง์ฝ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ฉด ์ด๋ฅผ ๋ฐํํ๊ณ , ์๋ค๋ฉด ์ธ๋ถ API๋ฅผ ํธ์ถํ ํ ์บ์์ ์ ์ฅํ๋ ๋ฐฉ์์ ๋๋ค.
๐น ๋์ ์๋ฆฌ
1. ์ฌ์ฉ์์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์บ์(Cache) ์์ ๋ฐ์ดํฐ ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค.
2. ์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ๋ฐํํฉ๋๋ค (cache hit).
3. ์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ์ธ๋ถ API์ ์์ฒญ์ ๋ณด๋ด๊ณ , ์๋ต๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์บ์์ ์ ์ฅํ ํ ๋ฐํํฉ๋๋ค (cache miss).
๐ ์์ ํ๋ฆ
- [์ฌ์ฉ์ ์์ฒญ] โ [์บ์ ํ์ธ] โ (๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ๋ฐํ) โ [์๋ต]
- [์ฌ์ฉ์ ์์ฒญ] โ [์บ์ ํ์ธ] โ (๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด) โ [์ธ๋ถ API ํธ์ถ] โ [์บ์์ ์ ์ฅ] โ [์๋ต]
๋ถํ์คํ ์์ฒญ ํจํด: ์ฌ์ฉ์๊ฐ ์์ฒญํ๋ ์ฃผํ์ ์ฃผ์๊ฐ ์ฌ์ ์ ์์ธก๋์ง ์๊ธฐ๋๋ฌธ์, ๋ฏธ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ ์ฌํ ์ ์์์ต๋๋ค. ๋ง์ฝ ํน์ ํ ์ ์์๋ค๋ฉด Cache Warm up์ ๊ณ ๋ คํ์ ๊ฒ ๊ฐ์ต๋๋ค.
์ค๋ณต ์์ฒญ ๋ฌธ์ ํด๊ฒฐ: ๋ฌธ์ ์ ์์ ์ธ๊ธํ๋๋๋ก ๋์ถ ์ ์ฒญ ํ๋ก์ธ์ค ์ค ์์ ์ ์ฅ ๊ธฐ๋ฅ์ด ์์๋๋ฐ, ์์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋๋ง๋ค ๋์ผํ ์ฃผ์์ ๋ํด ์ค๋ณต ์กฐํ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์์์ ์ฅ์ ๋ถ๋ฌ์ฌ๋ Cache hit ๊ฐ๋ฅ์ฑ์ด ๋์ ๊ฑฐ๋ผ ํ๋จํ์ต๋๋ค.
์ฒซ ์์ฒญ ์ง์ฐ ๊ฐ์: Look-aside ํจํด์ ํน์ฑ์ ์ฒซ ์์ฒญ์ ๊ธฐ์กด ์๋์ ๋์ผ ํ ํ ์ง๋ง ์ดํ ๋์ผํ ์์ฒญ์ ์บ์๋ก๋ถํฐ ๋น ๋ฅด๊ฒ ์ ๊ณตํ ์ ์์ต๋๋ค.
[์ฌ์ฉ์ ์์ฒญ] โ [์บ์ ํ์ธ] โ [์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ๋ฐํ] โ [์์ผ๋ฉด ์ธ๋ถ API ํธ์ถ] โ [์๋ต ๋ฐ์ดํฐ ์บ์์ ์ ์ฅ] โ [์๋ต ๋ฐํ]
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(86400))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("realPrice", getRealPriceCacheConfiguration());
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfig)
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
private RedisCacheConfiguration getRealPriceCacheConfiguration() {
long secondsUntilThursday = ChronoUnit.SECONDS.between(LocalDateTime.now(), LocalDateTime.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)).withHour(23).withMinute(30));
return RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(secondsUntilThursday));
}
if(!noCache) {
try {
Cache searchCache = cacheManager.getCache("realPrice");
String cacheKey = buildingCd;
RealPriceInquiryResponse cacheData = searchCache.get(cacheKey, RealPriceInquiryResponse.class);
if (cacheData != null) return cacheData;
} catch (Exception e) {
log.error("Cache lookup failed", e);
}
}
RealPriceInquiryResponse response = webClient.requestRealPriceApi();
try {
searchCache.put(cacheKey, response);
} catch (Exception e) {
log.error("Cache save failed", e);
}
return response;
์ ๊ทธ๋ฆผ์ API ์๋ฒ์ ์บ์ ๊ตฌ์กฐ๋ฅผ ํฌํจํ ์๋ฒ ์ํคํ ์ฒ๋ฅผ ๋ํ๋ธ ๊ฒ์ ๋๋ค. API ์๋ฒ๋ ์ธ๋ถ ์๋ฒ์ ํต์ ์ ์ถ์ํํ ์๋ฒ๋ก, ์ํ ์ ๋ฌธ ๋ฑ ๋ค์ํ ์ธ๋ถ ์์คํ ๊ณผ์ ํต์ ์ ํตํฉํ๊ธฐ ์ํด ์ค๊ณ๋์์ต๋๋ค.
Client Server์ Admin Server๋ API Server๋ก ์์ฒญ์ ๋ณด๋ด๋ฉฐ, ์ด๋ API ์๋ฒ๋ ๋จผ์ ์บ์๋ฅผ ํ์ธํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค. ๋ง์ฝ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด ์ธ๋ถ API ์๋ฒ์ ํต์ ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ณ , ์ด ๋ฐ์ดํฐ๋ฅผ ์บ์์ ์ ์ฅํ ํ ๋ฐํํฉ๋๋ค.
๋จ, Admin Server์ ๊ฒฝ์ฐ, ๋์ถ ํ๋ ์ฌ์ฌ์ ๊ฐ์ด ๋ ์ ํํ ์ ๋ณด๊ฐ ์๊ตฌ๋๋ฏ๋ก, ์บ์๋ ๋ฐ์ดํฐ๊ฐ ์๋ ์ธ๋ถ API ์๋ฒ์ ์ง์ ํต์ ํ์ฌ ์ต์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
(์ฒซ ์์ฒญ์ ํ๊ท ์ฝ 5100ms๋ก ๋์ผ ์บ์ hit์ ํ๊ท 57ms)
๊ตฌ๋ถ | ๋์ ์ | ๋์ ํ |
---|---|---|
ํ๊ท ์๋ต ์๊ฐ | 5132ms | 1299ms |
Cache Miss | 5132ms | 5231ms |
Cache Hit | X | 57ms |
API ํธ์ถ ํ์ | 100% | 76% ๊ฐ์ |
์ฌ์ฉ์ ๋ง์กฑ๋ | ๋ฎ์ | ๋น๊ต์ ์์น |
์๋ต ์๋ ๊ฐ์ : ํ๊ท 5132ms์๋ ์๋ต ์๊ฐ์ด 1299ms๋ก 74.69% ๊ฐ์ ๋์ต๋๋ค.
API ๋น์ฉ ์ ๊ฐ: ์ธ๋ถ API ํธ์ถ ํ์๊ฐ 76% ์ด์ ๊ฐ์ํ์ต๋๋ค.
์ฌ์ฉ์ ๊ฒฝํ ํฅ์: ํ์ด์ง ๋ก๋ฉ ์๋๊ฐ ๋นจ๋ผ์ ธ ์ฌ์ฉ์ ๋ง์กฑ๋๊ฐ ๊ฐ์ ๋์์ต๋๋ค.
์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ชฉ์ ํ๋ ๋ฐ์ ๋ถํฉํ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋ ๊ฒ์ ๊ต์ฅํ ์ค์ํ๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ ํฌ๊ฐ์ ๊ฒฝ์ฐ๋ ์ธ๋ถ API์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์์์๋ ๋ถ๊ตฌํ๊ณ ์บ์๊ฐ ๊ฐฑ์ ๋์ง ์์ผ๋ฉด, ์ ํํ์ง ์์ ๋์ถํ๋๊ฐ ์ฌ์ฉ์์๊ฒ ์ ๊ณต๋ ์ํ์ด ์์์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ธ๋ถ API์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ ์์ ์ ์บ์๋ฅผ ๋น์ฐ๋ ์ ๋ต์ ์ ํํ์ต๋๋ค. ์ธ๋ถ API์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ฃผ๊ธฐ๊ฐ ์ผ์ ํ๊ธฐ ๋๋ฌธ์ ์ด ์๊ฐ์ ๋ง์ถฐ Redis์ ์ ์ฅ๋ ํน์ ํค์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋๋ก ์ค์ ํ์ต๋๋ค.
Redis ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ํ๋ ์ํฉ์์ ์ค๋๋ ๋ฐ์ดํฐ๊ฐ ์์ด๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด LRU(Least Recently Used) ์ ์ฑ ์ ์ ์ฉํ์ต๋๋ค.
์ด๋ฒ Look-aside ์บ์ ๋์ ์ ํตํด ์ธ๋ถ API ํธ์ถ์ ์์กดํ๋ ์๋น์ค์ ์บ์๋ฅผ ํ์ฉํจ์ผ๋ก์จ ์๋ต ์๋๋ฅผ ์กฐ๊ธ์ด๋ผ๋ ๊ฐ์ ํ ์ ์์์ต๋๋ค.
์ธ๋ถ ์์คํ ์ ๋ํ ์์กด์ฑ์ ์๋น์ค ์ด์์ ์์ด ํผํ ์ ์๋ ์๊ฐ๋ค์ด ์์ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋ ์ง๋ง ์ฃผ์ด์ง ํ๊ฒฝ ์์์ ์ต์ ์ ๋ฐฉ๋ฒ์ ์ฐพ๋๊ฒ์ด ์ค์ํ์ง ์๋ ์ถ์ต๋๋ค.
์ด๋ฒ ์์ ์ ํตํด ์ป์ ๊ฒฝํ๋ค๊ณผ ๊ฐ์ ์ฌํญ๋ค์ด ์ฌ๋ฌ๋ถ๋ค์๊ฒ ์กฐ๊ธ์ด๋ผ๋ ๋์์ด ๋์ผ๋ฉดํ๋ฉฐ ์ด๋ง ๊ธ์ ๋ง์น๋๋ก ํ๊ฒ ์ต๋๋ค.