BackEnd/SpringBoot

์Šคํ”„๋ง ๋ถ€ํŠธ ๋กœ์ปฌ ์บ์‹œ ์ ์šฉํ•˜๊ธฐ

hyunki.Dev 2023. 6. 25. 17:11

๐Ÿ“Œ ๋“ค์–ด๊ฐ€๋ฉฐ

๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ ๊ฐ€์žฅ ์‹ ๊ฒฝ์“ฐ๊ฒŒ ๋˜๋Š” ๊ฒƒ์€ ์•„๋ฌด๋ž˜๋„ API์˜ ์„ฑ๋Šฅ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋งŽ์€ ํŠธ๋ž˜ํ”ฝ์„ ๋ฐ›์•„๋„ ์•ˆ์ •์ ์ธ ์†๋„๋กœ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•ด ์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด๋Ÿฌํ•œ API์˜ ์„ฑ๋Šฅ์„ ๋†’์ผ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์ค‘ ํ•˜๋‚˜์ธ ์บ์‹œ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์บ์‹œ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด Redis๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•˜๋Š”๋ฐ์š” ํ•ด๋‹น ํฌ์ŠคํŒ…์—์„œ๋Š” ์Šคํ”„๋ง๋ถ€ํŠธ์˜ ์ž์ฒด ์บ์‹œ(๋กœ์ปฌ ์บ์‹œ)๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

๐Ÿ“Œ ์บ์‹œ๋ž€?

์บ์‹œ๋Š” ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด๋‘๊ณ  ์žฌํ™œ์šฉํ•˜๋Š” ๊ธฐ์ˆ ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค.

 

์ผ๋ฐ˜์ ์œผ๋กœ ์บ์‹œ๋Š” ์ผ์‹œ์ ์ธ ํŠน์ง•์ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์ง‘ํ•ฉ์„ ๊ณ ์† ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ดํ›„์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ๋™์ผํ•œ ์š”์ฒญ์ด ์žˆ์„ ๊ฒฝ์šฐ,

๋ฐ์ดํ„ฐ์˜ ์›๋ณธ ์Šคํ† ๋ฆฌ์ง€ ์œ„์น˜๋กœ ์•ก์„ธ์Šคํ•  ๋•Œ๋ณด๋‹ค ๋” ๋น ๋ฅด๊ฒŒ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์บ์‹ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด์ „์— ๊ฒ€์ƒ‰ํ–ˆ๊ฑฐ๋‚˜ ๊ณ„์‚ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ถ€๋ถ„์ ์œผ๋กœ ์„ค๋ช…ํ•˜์ž๋ฉด,

์ผ๋ฐ˜์ ์œผ๋กœ ๋™์ผํ•œ ๋ฆฌ์†Œ์Šค ๋Œ€ํ•ด ๋นˆ๋ฒˆํ•œ SELECT๋กœ ๋ฐœ์ƒ๋˜๋Š” DBMS ๊ณผ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ณ ์ž ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ์‹์œผ๋กœ ๊ณผ๋ถ€ํ•˜๋ฅผ ์ค„์ด๋ฉด ๋†’์€ ํŠธ๋ž˜ํ”ฝ์ด ๊ฐ‘์ž๊ธฐ ๋ชฐ๋ฆฌ๋”๋ผ๋„ Web Application์ด ์ฃฝ์ง€ ์•Š๊ณ  ๋ฒ„ํ‹ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

๐Ÿ“Œ Cache Manager

์บ์‹œ ์ถ”์ƒํ™”์—์„œ๋Š” ์บ์‹œ ๊ธฐ์ˆ ์„ ์ง€์›ํ•˜๋Š” ์บ์‹œ ๋งค๋‹ˆ์ €๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ๋‘ CacheManager ๋ฅผ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋ฉฐ ์ด ์ค‘์—์„œ CaffeineCacheManager ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

โœ”๏ธ EhCacheCacheManager

์ž๋ฐ”์—์„œ ์œ ๋ช…ํ•œ ์บ์‹œ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜์ธ EhCache๋ฅผ ์ง€์›ํ•˜๋Š” ์บ์‹œ ๋งค๋‹ˆ์ €์ž…๋‹ˆ๋‹ค.

 

โœ”๏ธ CaffeineCacheManager

Java 8๋กœ Guava ์บ์‹œ๋ฅผ ์žฌ์ž‘์„ฑํ•œ Caffeine ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์บ์‹œ ๋งค๋‹ˆ์ €์ž…๋‹ˆ๋‹ค.

EhCache์™€ ํ•จ๊ป˜ ์ธ๊ธฐ ์žˆ๋Š” ์บ์‹œ ๋งค๋‹ˆ์ €์ธ๋ฐ, EhCache๋ณด๋‹ค ์ข‹์€ ์„ฑ๋Šฅ์„ ๊ฐ–๋Š”๋‹ค๊ณ  ํ•ด์„œ ์ตœ๊ทผ ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ CaffeineCacheManager

๋จผ์ € Ehcache๋Š” multi-level ์บ์‹œ, distributed ์บ์‹œ, ์บ์‹œ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๊ณผ ๊ฐ™์€ ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Caffeine์€ Ehcache๋ณด๋‹ค ์บ์‹œ์˜ ์„ฑ๋Šฅ์ด ๋†’์œผ๋ฉฐ, ์‹ค์ œ๋กœ ๋” ์šฐ์ˆ˜ํ•œ ์บ์‹œ ์ œ๊ฑฐ ์ „๋žต์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ Window TinyLfu ํ‡ด์ถœ ์ •์ฑ…์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ๊ฑฐ์˜ ์ตœ์ ์˜ ์ ์ค‘๋ฅ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 

Caffeine์— ๋Œ€ํ•œ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์€ Caffeine Wiki๋ฅผ ์ฐธ์กฐ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿผ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์บ์‹œ๋ฅผ ์ ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์˜์กด์„ฑ ์ถ”๊ฐ€ build.gradle

	//spring cache with caffeine
	implementation 'org.springframework.boot:spring-boot-starter-cache'
	implementation 'com.github.ben-manes.caffeine:caffeine'// caffeine

 

CacheConfig ์ถ”๊ฐ€

package kr.co.starbucks.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import kr.co.starbucks.constants.CacheType;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();

        List<CaffeineCache> caches = Arrays.stream(CacheType.values())
            .map(cache -> new CaffeineCache(
                cache.getName(),
                Caffeine.newBuilder()
//                    .expireAfterWrite(cache.getExpireAfterWrite(), TimeUnit.SECONDS)
                    .expireAfterAccess(cache.getExpireAfterWrite(), TimeUnit.SECONDS)
                    .maximumSize(cache.getMaximumSize())
                    .recordStats()
                    .build()
            ))
            .collect(Collectors.toList());

        cacheManager.setCaches(caches);
        return cacheManager;
    }

}

 

๐Ÿ“Œ  Creating Parameters

 

initialCapacity: ๋‚ด๋ถ€ ํ•ด์‹œ ํ…Œ์ด๋ธ”์˜ ์ตœ์†Œํ•œ์˜ ํฌ๊ธฐ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

maximumSize: ์บ์‹œ์— ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์—”ํŠธ๋ฆฌ ์ˆ˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

maximumWeight: ์บ์‹œ์— ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ์—”ํŠธ๋ฆฌ์˜ ์ตœ๋Œ€ ๋ฌด๊ฒŒ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

expireAfterAccess: ์บ์‹œ๊ฐ€ ์ƒ์„ฑ๋œ ํ›„, ํ•ด๋‹น ๊ฐ’์ด ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋Œ€์ฒด๋˜๊ฑฐ๋‚˜ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝ์€ ํ›„ ํŠน์ • ๊ธฐ๊ฐ„์ด ๊ฒฝ๊ณผํ•˜๋ฉด ์บ์‹œ์—์„œ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋˜๋„๋ก ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

expireAfterWrite: ํ•ญ๋ชฉ์ด ์ƒ์„ฑ๋œ ํ›„ ๋˜๋Š” ํ•ด๋‹น ๊ฐ’์„ ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋ฐ”๋€ ํ›„ ํŠน์ • ๊ธฐ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๊ฐ ํ•ญ๋ชฉ์ด ์บ์‹œ์—์„œ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋˜๋„๋ก ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

refreshAfterWrite: ์บ์‹œ๊ฐ€ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ๋งˆ์ง€๋ง‰์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ ํ›„ ์ง€์ •๋œ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์บ์‹œ๋ฅผ ์ƒˆ๋กœ ๊ณ ์นฉ๋‹ˆ๋‹ค.

weakKeys: ํ‚ค๋ฅผ weak reference๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. (GC์—์„œ ํšŒ์ˆ˜๋จ)

weakValues: Value๋ฅผ weak reference๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. (GC์—์„œ ํšŒ์ˆ˜๋จ)

softValues: Value๋ฅผ soft reference๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. (๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ฐ€๋“ ์ฐผ์„ ๋•Œ GC์—์„œ ํšŒ์ˆ˜๋จ)

recordStats: ์บ์‹œ์— ๋Œ€ํ•œ Statics๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

โš ๏ธ expireAfterWrite ์™€ expireAfterAccess ๊ฐ€ ํ•จ๊ป˜ ์ง€์ •๋œ ๊ฒฝ์šฐ, expireAfterWrite๊ฐ€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

โš ๏ธ maximumSize์™€ maximumWeight๋Š” ํ•จ๊ป˜ ์ง€์ •๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

 

CacheType ์— ๋Œ€ํ•œ ๊ด€๋ฆฌ

package kr.co.starbucks.constants;

import com.google.common.collect.ImmutableMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;

@Getter
public enum CacheType {

    TEST("test"),
    BASIC_POLICY("basic-policy"),
    GIFT_TERMS("gift-terms"),
    GOODS_INFO("goods-info"),
    PAYMENT_POLICY("payment-policy");
    
    private final String name;
    private final int expireAfterWrite;
    private final int maximumSize;

    CacheType(String name) {
        this.name = name;
        this.expireAfterWrite = ConstConfig.DEFAULT_TTL_SEC;
        this.maximumSize = ConstConfig.DEFAULT_MAX_SIZE;
    }

    private static final ImmutableMap<String, CacheType> codes = ImmutableMap.copyOf(
        Stream.of(values()).collect(Collectors.toMap(CacheType::getName, Function.identity())));

    static class ConstConfig {
        static final int DEFAULT_TTL_SEC = 60;
        static final int DEFAULT_MAX_SIZE = 10000;
    }

}

 

์‹ค์ œ cache๋ฅผ ์ ์šฉํ•˜๊ณ  ์‹ถ์€ ๋ฉ”์„œ๋“œ์— @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ ์šฉ

    @Cacheable(value = "payment-policy", cacheManager = "cacheManager")
    public List<PaymentPolicy> getPaymentPolicyList() {
        return paymentPolicyRepository.findByUseYnAndAvailableGiftBuyYn("Y", "Y");
    }

 

์ด๋ ‡๊ฒŒ ์ ์šฉํ•˜๊ณ  ์‹ค์ œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ ํ•ด ๋ณด๋ฉด 

ํ•œ๋ฒˆ๋งŒ JPA ๊ด€๋ จ ๋กœ๊น…(select ๋ฌธ)์ด ์ฐํžˆ๊ณ  ๊ทธ ์ดํ›„์—๋Š” ์—ฌ๋Ÿฌ๋ฒˆ ํ˜ธ์ถœํ•ด๋„

์บ์‹œ๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ๊นŒ์ง€๋Š”select ๋ฌธ์ด ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ 

ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

์ฐธ๊ณ :

https://gngsn.tistory.com/157