์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Spring Cache๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•˜๊ธฐ ๐Ÿš€

๐Ÿš€ Spring Cache๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•˜๊ธฐ ๐Ÿš€

Java ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์บ์‹œ ํ™œ์šฉ ์™„์ „์ •๋ณต! ์„ฑ๋Šฅ ๋ํŒ์™• ๋˜๊ธฐ ๐Ÿ’ฏ

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„! ์˜ค๋Š˜์€ ์ •๋ง ๊ฟ€์žผ ์ฃผ์ œ์ธ Spring Cache๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ํ•จ๊ป˜ ์•Œ์•„๋ณผ๊ฒŒ์š”! ๐Ÿ”ฅ

์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ฑฐ๋ถ์ด์ฒ˜๋Ÿผ ๋А๋ฆฌ๊ฒŒ ๋™์ž‘ํ•œ๋‹ค๋ฉด? DB ์ฟผ๋ฆฌ๊ฐ€ ๋งค๋ฒˆ ์‹คํ–‰๋ผ์„œ ์‚ฌ์šฉ์ž๋“ค์ด "์ด๊ฑฐ ๋ญ์ž„? ์™œ ์ด๋ ‡๊ฒŒ ๋А๋ฆผ?" ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด? ์ด์ œ ์บ์‹œ๋ฅผ ํ™œ์šฉํ•ด์„œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์„ ์น˜ํƒ€๊ธ‰ ์†๋„๋กœ ๋ฐ”๊ฟ”๋ด…์‹œ๋‹ค! ๐Ÿข โ†’ ๐Ÿ†

์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ณ  ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋ถ„๋“ค์ด๋ผ๋ฉด ์ด ๊ธ€์„ ํ†ตํ•ด ์‹ค๋ ฅ์„ ํ•œ ๋‹จ๊ณ„ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”. ์ž, ์ด์ œ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”? ๐Ÿ˜Ž

๐Ÿ“š ๋ชฉ์ฐจ

  1. ์บ์‹œ๊ฐ€ ๋ญ๊ธธ๋ž˜? ๊ธฐ๋ณธ ๊ฐœ๋… ์ดํ•ดํ•˜๊ธฐ
  2. Spring Cache ์†Œ๊ฐœ ๋ฐ ์„ค์ • ๋ฐฉ๋ฒ•
  3. ์บ์‹œ ์–ด๋…ธํ…Œ์ด์…˜ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ
  4. ๋‹ค์–‘ํ•œ ์บ์‹œ ๋งค๋‹ˆ์ € ์•Œ์•„๋ณด๊ธฐ
  5. ์‹ค์ „ ์˜ˆ์ œ๋กœ ๋ฐฐ์šฐ๋Š” Spring Cache
  6. ์บ์‹œ ์ „๋žต ๋ฐ ํŒจํ„ด
  7. ์„ฑ๋Šฅ ์ธก์ • ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง
  8. ์ฃผ์˜์‚ฌํ•ญ ๋ฐ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค
  9. ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ
  10. ๋งˆ๋ฌด๋ฆฌ ๋ฐ ์ถ”๊ฐ€ ํ•™์Šต ์ž๋ฃŒ

1. ์บ์‹œ๊ฐ€ ๋ญ๊ธธ๋ž˜? ๊ธฐ๋ณธ ๊ฐœ๋… ์ดํ•ดํ•˜๊ธฐ ๐Ÿค”

์บ์‹œ(Cache)๋ผ๋Š” ๋ง, ๋งŽ์ด ๋“ค์–ด๋ณด์…จ์ฃ ? ๊ทผ๋ฐ ์ •ํ™•ํžˆ ๋ญ”์ง€ ์•„์‹œ๋‚˜์š”? ๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ž„์‹œ๋กœ ์ €์žฅํ•ด๋‘๋Š” ๊ณต๊ฐ„์ด์—์š”. ๋งˆ์น˜ ์—ฌ๋Ÿฌ๋ถ„์ด ์ž์ฃผ ์“ฐ๋Š” ๋ฌผ๊ฑด์„ ์ฑ…์ƒ ์œ„์— ์˜ฌ๋ ค๋‘๋Š” ๊ฒƒ์ฒ˜๋Ÿผ์š”!

๐Ÿช ํŽธ์˜์  ๋น„์œ ๋กœ ์ดํ•ดํ•˜๋Š” ์บ์‹œ

์—ฌ๋Ÿฌ๋ถ„์ด ํŽธ์˜์  ์ ์žฅ์ด๋ผ๊ณ  ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ์†๋‹˜๋“ค์ด ์ž์ฃผ ์‚ฌ๋Š” ๋ฌผ๊ฑด(๋ผ๋ฉด, ์Œ๋ฃŒ์ˆ˜)์€ ๊ณ„์‚ฐ๋Œ€ ๊ทผ์ฒ˜์— ๋‘๊ณ , ๊ฐ€๋” ์‚ฌ๋Š” ๋ฌผ๊ฑด์€ ์•ˆ์ชฝ ์ง„์—ด๋Œ€์— ๋‘์ž–์•„์š”. ์ด๊ฒŒ ๋ฐ”๋กœ ์บ์‹œ์˜ ์›๋ฆฌ์˜ˆ์š”! ์ž์ฃผ ์ ‘๊ทผํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋นจ๋ฆฌ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๊ณณ(๋ฉ”๋ชจ๋ฆฌ)์— ๋‘๊ณ , ๋‚˜๋จธ์ง€๋Š” ๋А๋ฆฐ ์ €์žฅ์†Œ(๋””์Šคํฌ, DB)์— ๋‘๋Š” ๊ฑฐ์ฃ .

์บ์‹œ๊ฐ€ ํ•„์š”ํ•œ ์ด์œ  ๐Ÿ’โ€โ™€๏ธ

์™œ ์บ์‹œ๊ฐ€ ํ•„์š”ํ• ๊นŒ์š”? ๋‹ค์Œ ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด๋ณด์„ธ์š”:

  1. DB ์ฟผ๋ฆฌ๋Š” ๋น„์‹ธ์š”! (์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆผ)
  2. ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์š”์ฒญํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์š”
  3. ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์€ ํ•ญ์ƒ ์ง€์—ฐ์‹œ๊ฐ„์ด ๋ฐœ์ƒํ•ด์š”
  4. ์„œ๋ฒ„ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ณ  ์‹ถ์–ด์š”
  5. ์‚ฌ์šฉ์ž์—๊ฒŒ ๋น ๋ฅธ ์‘๋‹ต์„ ์ œ๊ณตํ•˜๊ณ  ์‹ถ์–ด์š”
์บ์‹œ ์—†์„ ๋•Œ ์‚ฌ์šฉ์ž ์„œ๋ฒ„ DB ์š”์ฒญ DB ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ ์‘๋‹ต ๋งค๋ฒˆ DB ์ ‘๊ทผ ๐Ÿ˜ฑ ๋А๋ฆฐ ์‘๋‹ต ์†๋„ โฑ๏ธ ์บ์‹œ ์žˆ์„ ๋•Œ ์‚ฌ์šฉ์ž ์„œ๋ฒ„ ์บ์‹œ DB ์š”์ฒญ ์บ์‹œ ํ™•์ธ ํ•„์š”์‹œ๋งŒ DB ์ ‘๊ทผ ๋น ๋ฅธ ์‘๋‹ต ๋ฐ์ดํ„ฐ ์žฌ์‚ฌ์šฉ ๐Ÿ˜ ๋น ๋ฅธ ์‘๋‹ต ์†๋„ โšก

์บ์‹œ์˜ ์ข…๋ฅ˜ ๐Ÿ—‚๏ธ

์บ์‹œ๋Š” ์—ฌ๋Ÿฌ ๋ ˆ๋ฒจ์—์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”:

  1. ์›น ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ: ์ด๋ฏธ์ง€, CSS, JS ํŒŒ์ผ ๋“ฑ์„ ๋กœ์ปฌ์— ์ €์žฅ
  2. CDN ์บ์‹œ: ์ „ ์„ธ๊ณ„ ์—ฌ๋Ÿฌ ์ง€์—ญ์— ์ฝ˜ํ…์ธ  ๋ณต์‚ฌ๋ณธ ์ €์žฅ
  3. API ๊ฒŒ์ดํŠธ์›จ์ด ์บ์‹œ: API ์‘๋‹ต์„ ์บ์‹ฑ
  4. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์บ์‹œ: ์šฐ๋ฆฌ๊ฐ€ ์˜ค๋Š˜ ๋ฐฐ์šธ Spring Cache!
  5. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์บ์‹œ: DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ

"์บ์‹œ๋Š” ์ปดํ“จํ„ฐ ๊ณผํ•™์—์„œ ๊ฐ€์žฅ ์–ด๋ ค์šด ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์ด๋ฆ„ ์ง“๊ธฐ์™€ ์˜คํ”„-๋ฐ”์ด-์› ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค." - ๋งˆํ‹ด ํŒŒ์šธ๋Ÿฌ

ใ…‹ใ…‹ใ…‹ ์บ์‹œ๊ฐ€ ์–ด๋ ต๋‹ค๊ณ ์š”? ๊ฑฑ์ • ๋งˆ์„ธ์š”! ์ด ๊ธ€์„ ๋‹ค ์ฝ์œผ๋ฉด ์—ฌ๋Ÿฌ๋ถ„๋„ ์บ์‹œ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด์š”! ๐Ÿ‘‘

2. Spring Cache ์†Œ๊ฐœ ๋ฐ ์„ค์ • ๋ฐฉ๋ฒ• โš™๏ธ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ Spring Cache์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ๊ฒŒ์š”! Spring Framework๋Š” ์บ์‹ฑ์„ ์œ„ํ•œ ์ถ”์ƒํ™” ๋ ˆ์ด์–ด๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ, ์ด๊ฒŒ ๋ฐ”๋กœ Spring Cache ์ถ”์ƒํ™”์˜ˆ์š”.

Spring Cache์˜ ํŠน์ง• ๐ŸŒŸ

  1. ์„ ์–ธ์  ์บ์‹ฑ (์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜)
  2. ๋‹ค์–‘ํ•œ ์บ์‹œ ์ œ๊ณต์ž(provider) ์ง€์›
  3. ํŠธ๋žœ์žญ์…˜๊ณผ์˜ ํ†ตํ•ฉ
  4. ์บ์‹œ ์ถ”์ƒํ™”๋กœ ๊ตฌํ˜„์ฒด ์‰ฝ๊ฒŒ ๊ต์ฒด ๊ฐ€๋Šฅ

๐Ÿง  ์™œ Spring Cache๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ์š”?

์—ฌ๋Ÿฌ๋ถ„์ด ์ง์ ‘ ์บ์‹œ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ทธ๋Ÿฌ๋ฉด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์บ์‹œ ๋กœ์ง์ด ์„ž์—ฌ์„œ ์ฝ”๋“œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ ธ์š”. Spring Cache๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด AOP(๊ด€์  ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ) ๋ฐฉ์‹์œผ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์บ์‹œ ๊ธฐ๋Šฅ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”!

Spring Cache ์„ค์ •ํ•˜๊ธฐ ๐Ÿ› ๏ธ

Spring Boot ํ”„๋กœ์ ํŠธ์—์„œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฅด๋ฉด ๋ผ์š”:

1. ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ

๋จผ์ € Maven์ด๋‚˜ Gradle์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด์š”.

Maven (pom.xml):


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
        

Gradle (build.gradle):


implementation 'org.springframework.boot:spring-boot-starter-cache'
        

์ถ”๊ฐ€๋กœ ์บ์‹œ ๊ตฌํ˜„์ฒด(์˜ˆ: Caffeine, Redis ๋“ฑ)๋„ ํ•„์š”ํ•ด์š”:


// Caffeine ์บ์‹œ ์‚ฌ์šฉ ์‹œ
implementation 'com.github.ben-manes.caffeine:caffeine'

// Redis ์บ์‹œ ์‚ฌ์šฉ ์‹œ
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
        

2. ์บ์‹œ ํ™œ์„ฑํ™”ํ•˜๊ธฐ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ž˜์Šค์— @EnableCaching ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์š”:


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching  // ์ด ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์บ์‹œ ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”!
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
        

์ด @EnableCaching ์–ด๋…ธํ…Œ์ด์…˜์ด Spring์˜ ์บ์‹œ ์ธํ”„๋ผ๋ฅผ ํ™œ์„ฑํ™”ํ•ด์ค˜์š”. ์ด์ œ ์บ์‹œ ๊ด€๋ จ ์–ด๋…ธํ…Œ์ด์…˜๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์–ด์š”! ๐Ÿ‘

3. ์บ์‹œ ๋งค๋‹ˆ์ € ์„ค์ •ํ•˜๊ธฐ

๊ธฐ๋ณธ์ ์œผ๋กœ Spring Boot๋Š” ConcurrentMapCacheManager๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋” ๊ฐ•๋ ฅํ•œ ์บ์‹œ ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”.

Caffeine ์บ์‹œ ์„ค์ • ์˜ˆ์‹œ:


import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .initialCapacity(100)
                .maximumSize(1000));
        return cacheManager;
    }
}
        

์œ„ ์„ค์ •์€ ๋‹ค์Œ์„ ์˜๋ฏธํ•ด์š”:

  1. ์บ์‹œ ํ•ญ๋ชฉ์€ ์ž‘์„ฑ ํ›„ 60๋ถ„ ํ›„์— ๋งŒ๋ฃŒ๋ผ์š”
  2. ์ดˆ๊ธฐ ์บ์‹œ ์šฉ๋Ÿ‰์€ 100๊ฐœ ํ•ญ๋ชฉ์ด์—์š”
  3. ์ตœ๋Œ€ 1000๊ฐœ ํ•ญ๋ชฉ๊นŒ์ง€ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด์š”
Spring Cache ์•„ํ‚คํ…์ฒ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ (@Cacheable, @CachePut, @CacheEvict) Spring Cache ์ถ”์ƒํ™” (CacheManager, Cache) ConcurrentMap Caffeine EhCache Redis ๊ธฐํƒ€...

์ด์ œ Spring Cache๋ฅผ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋๋‚ฌ์–ด์š”! ๋‹ค์Œ์œผ๋กœ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊ฒŒ์š”. ๐Ÿš€

3. ์บ์‹œ ์–ด๋…ธํ…Œ์ด์…˜ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ ๐Ÿ“

Spring Cache์˜ ํ•ต์‹ฌ์€ ์–ด๋…ธํ…Œ์ด์…˜์ด์—์š”! ์ด ์–ด๋…ธํ…Œ์ด์…˜๋“ค์„ ์ž˜ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ ๋ช‡ ์ค„๋กœ ์บ์‹œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ง„์งœ ๊ฐœ๋ฐœ์ž ์ธ์ƒ์ด ํŽธํ•ด์ง„๋‹ค๋‹ˆ๊นŒ์š”? ใ…‹ใ…‹ใ…‹

์ฃผ์š” ์บ์‹œ ์–ด๋…ธํ…Œ์ด์…˜ 5๊ฐ€์ง€ ๐Ÿ†

1. @Cacheable

๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œ์— ์ €์žฅํ•˜๊ณ , ๋™์ผํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋‹ค์‹œ ํ˜ธ์ถœ๋  ๋•Œ ์บ์‹œ์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์™€์š”.


@Cacheable(value = "users", key = "#userId")
public User getUserById(Long userId) {
    // ์ด ๋ฉ”์„œ๋“œ๋Š” ์ฒ˜์Œ ํ˜ธ์ถœ๋  ๋•Œ๋งŒ ์‹คํ–‰๋˜๊ณ , 
    // ๊ฐ™์€ userId๋กœ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๋ฉด DB ์ ‘๊ทผ ์—†์ด ์บ์‹œ์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์š”
    return userRepository.findById(userId).orElse(null);
}
                

2. @CachePut

๋ฉ”์„œ๋“œ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œ์— ์ €์žฅํ•˜์ง€๋งŒ, ์บ์‹œ ํ™•์ธ์€ ํ•˜์ง€ ์•Š์•„์š”. ์ฃผ๋กœ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์‹œ ์‚ฌ์šฉํ•ด์š”.


@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    // ์ด ๋ฉ”์„œ๋“œ๋Š” ํ•ญ์ƒ ์‹คํ–‰๋˜๊ณ , ๊ฒฐ๊ณผ๊ฐ€ ์บ์‹œ์— ์ €์žฅ๋ผ์š”
    // ๊ธฐ์กด ์บ์‹œ ๋‚ด์šฉ์„ ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธํ•  ๋•Œ ์‚ฌ์šฉํ•ด์š”
    return userRepository.save(user);
}
                

3. @CacheEvict

์บ์‹œ์—์„œ ํ•ญ๋ชฉ์„ ์ œ๊ฑฐํ•ด์š”. ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋  ๋•Œ ์‚ฌ์šฉํ•ด์š”.


@CacheEvict(value = "users", key = "#userId")
public void deleteUser(Long userId) {
    // ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋ฉด 'users' ์บ์‹œ์—์„œ ํ•ด๋‹น userId์— ๋Œ€ํ•œ ํ•ญ๋ชฉ์ด ์ œ๊ฑฐ๋ผ์š”
    userRepository.deleteById(userId);
}

// ์บ์‹œ ์ „์ฒด ๋น„์šฐ๊ธฐ
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
    // 'users' ์บ์‹œ์˜ ๋ชจ๋“  ํ•ญ๋ชฉ์„ ์ œ๊ฑฐํ•ด์š”
    System.out.println("๋ชจ๋“  ์‚ฌ์šฉ์ž ์บ์‹œ๊ฐ€ ์ง€์›Œ์กŒ์–ด์š”!");
}
                

4. @Caching

์—ฌ๋Ÿฌ ์บ์‹œ ์ž‘์—…์„ ๊ทธ๋ฃนํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”.


@Caching(
    evict = { 
        @CacheEvict(value = "users", key = "#user.id"),
        @CacheEvict(value = "usersByName", key = "#user.name")
    },
    put = { 
        @CachePut(value = "userDetails", key = "#user.id") 
    }
)
public User updateUserWithMultipleCaches(User user) {
    // ์—ฌ๋Ÿฌ ์บ์‹œ ์ž‘์—…์„ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•ด์š”
    return userRepository.save(user);
}
                

5. @CacheConfig

ํด๋ž˜์Šค ๋ ˆ๋ฒจ์—์„œ ๊ณตํ†ต ์บ์‹œ ์„ค์ •์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด์š”.


@Service
@CacheConfig(cacheNames = {"users"})  // ์ด ํด๋ž˜์Šค์˜ ๋ชจ๋“  ์บ์‹œ ์ž‘์—…์€ 'users' ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•ด์š”
public class UserService {
    
    @Cacheable(key = "#userId")  // cacheNames๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์•„๋„ 'users' ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•ด์š”
    public User getUser(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }
    
    @CacheEvict(key = "#userId")  // ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ 'users' ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•ด์š”
    public void deleteUser(Long userId) {
        userRepository.deleteById(userId);
    }
}
                

SpEL ํ‘œํ˜„์‹์œผ๋กœ ์บ์‹œ ํ‚ค ์ง€์ •ํ•˜๊ธฐ ๐Ÿ”‘

Spring Cache๋Š” SpEL(Spring Expression Language)์„ ์‚ฌ์šฉํ•ด์„œ ๋™์ ์œผ๋กœ ์บ์‹œ ํ‚ค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๊ฑด ์ง„์งœ ๊ฟ€ํŒ์ด์—์š”! ๐Ÿ˜

์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” SpEL ํ‘œํ˜„์‹ ์˜ˆ์‹œ:

  1. #paramName - ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์œผ๋กœ ์ ‘๊ทผ
  2. #p0, #a0 - ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ ์œ„์น˜๋กœ ์ ‘๊ทผ
  3. #root.method - ํ˜„์žฌ ๋ฉ”์„œ๋“œ ์ด๋ฆ„
  4. #root.target - ํ˜„์žฌ ๋Œ€์ƒ ๊ฐ์ฒด
  5. #result - ๋ฉ”์„œ๋“œ ์‹คํ–‰ ๊ฒฐ๊ณผ (@CachePut, @CacheEvict์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
  6. #user.id - ๊ฐ์ฒด์˜ ์†์„ฑ์— ์ ‘๊ทผ
  7. T(ํŒจํ‚ค์ง€.ํด๋ž˜์Šค).์ •์ ๋ฉ”์„œ๋“œ() - ์ •์  ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ

์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ID์™€ ์—ญํ• ๋กœ ๋ณตํ•ฉ ํ‚ค๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด:


@Cacheable(value = "userPermissions", key = "#userId + '_' + #role")
public List<Permission> getUserPermissions(Long userId, String role) {
    // ๋ณตํ•ฉ ํ‚ค๋กœ ์บ์‹ฑ: "123_ADMIN" ๊ฐ™์€ ํ˜•ํƒœ
    return permissionRepository.findByUserIdAndRole(userId, role);
}
        

์กฐ๊ฑด๋ถ€ ์บ์‹ฑ ์„ค์ •ํ•˜๊ธฐ ๐Ÿ”„

ํ•ญ์ƒ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•˜๊ณ  ์‹ถ์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์–ด์š”. Spring Cache๋Š” condition๊ณผ unless ์†์„ฑ์œผ๋กœ ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ์„ ์ง€์›ํ•ด์š”.


// userId๊ฐ€ 100๋ณด๋‹ค ํฐ ๊ฒฝ์šฐ์—๋งŒ ์บ์‹ฑ
@Cacheable(value = "users", key = "#userId", condition = "#userId > 100")
public User getUserById(Long userId) {
    return userRepository.findById(userId).orElse(null);
}

// ๊ฒฐ๊ณผ๊ฐ€ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์บ์‹ฑ
@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
    return userRepository.findById(userId).orElse(null);
}

// ๊ด€๋ฆฌ์ž๊ฐ€ ์•„๋‹Œ ์‚ฌ์šฉ์ž๋งŒ ์บ์‹ฑ
@Cacheable(value = "users", key = "#user.id", condition = "!#user.admin")
public User processUser(User user) {
    // ์ฒ˜๋ฆฌ ๋กœ์ง
    return user;
}
        

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

condition์€ ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ „์— ํ‰๊ฐ€๋˜๊ณ , unless๋Š” ๋ฉ”์„œ๋“œ ์‹คํ–‰ ํ›„์— ํ‰๊ฐ€๋ผ์š”. ๋”ฐ๋ผ์„œ unless์—์„œ๋Š” #result๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, condition์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด์š”!

์ด์ œ Spring Cache ์–ด๋…ธํ…Œ์ด์…˜์˜ ๊ธฐ๋ณธ์„ ๋งˆ์Šคํ„ฐํ–ˆ์–ด์š”! ๋‹ค์Œ์œผ๋กœ ๋‹ค์–‘ํ•œ ์บ์‹œ ๋งค๋‹ˆ์ €์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ๊ฒŒ์š”. ๐Ÿง 

4. ๋‹ค์–‘ํ•œ ์บ์‹œ ๋งค๋‹ˆ์ € ์•Œ์•„๋ณด๊ธฐ ๐Ÿ—ƒ๏ธ

Spring์€ ๋‹ค์–‘ํ•œ ์บ์‹œ ๊ตฌํ˜„์ฒด๋ฅผ ์ง€์›ํ•ด์š”. ๊ฐ๊ฐ ์žฅ๋‹จ์ ์ด ์žˆ์œผ๋‹ˆ ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๋Š” ๊ฑธ ์„ ํƒํ•˜๋ฉด ๋ผ์š”! ์บ์‹œ ๋งค๋‹ˆ์ €๋Š” ํ”„๋กœ์ ํŠธ์˜ ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋‹ˆ ์‹ ์ค‘ํ•˜๊ฒŒ ์„ ํƒํ•˜์„ธ์š”!

์ฃผ์š” ์บ์‹œ ๋งค๋‹ˆ์ € ๋น„๊ต ๐Ÿ“Š

1. ConcurrentMapCacheManager

ํŠน์ง•: Spring Boot์˜ ๊ธฐ๋ณธ ์บ์‹œ ๋งค๋‹ˆ์ €. ConcurrentHashMap์„ ์‚ฌ์šฉํ•ด ๋ฉ”๋ชจ๋ฆฌ์— ์บ์‹œ๋ฅผ ์ €์žฅํ•ด์š”.

์žฅ์ : ์„ค์ •์ด ๊ฐ„๋‹จํ•˜๊ณ  ์ถ”๊ฐ€ ์˜์กด์„ฑ์ด ํ•„์š” ์—†์–ด์š”.

๋‹จ์ : ์บ์‹œ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ์—†๊ณ , ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์ด ์ œํ•œ์ ์ด์—์š”.

์ ํ•ฉํ•œ ๊ฒฝ์šฐ: ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ด๋‚˜ ๊ฐ„๋‹จํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, ํ”„๋กœํ† ํƒ€์ž…์— ์ ํ•ฉํ•ด์š”.


@Bean
public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("users", "products");
}
                

2. CaffeineCacheManager

ํŠน์ง•: Caffeine์€ Java 8๋กœ ์žฌ์ž‘์„ฑ๋œ Guava ์บ์‹œ๋กœ, ๊ณ ์„ฑ๋Šฅ ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์˜ˆ์š”.

์žฅ์ : ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ, ๋‹ค์–‘ํ•œ ์บ์‹œ ์ •์ฑ…(์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ๋งŒ๋ฃŒ, ํฌ๊ธฐ ์ œํ•œ ๋“ฑ), ํ†ต๊ณ„ ์ œ๊ณต

๋‹จ์ : ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ฐ ์„œ๋ฒ„๋งˆ๋‹ค ๋…๋ฆฝ์ ์ธ ์บ์‹œ๋ฅผ ๊ฐ€์ ธ์š”.

์ ํ•ฉํ•œ ๊ฒฝ์šฐ: ๊ณ ์„ฑ๋Šฅ์ด ํ•„์š”ํ•œ ๋‹จ์ผ ์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•ด์š”.


@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(500)
            .recordStats());
    return cacheManager;
}
                

3. EhCacheCacheManager

ํŠน์ง•: EhCache๋Š” ์˜ค๋ž˜๋œ Java ์บ์‹œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์š”.

์žฅ์ : ๋””์Šคํฌ ์ €์žฅ ์ง€์›, ๋ถ„์‚ฐ ์บ์‹ฑ ์˜ต์…˜, ํ’๋ถ€ํ•œ ์„ค์ • ์˜ต์…˜

๋‹จ์ : Caffeine๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์•ฝ๊ฐ„ ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์–ด์š”.

์ ํ•ฉํ•œ ๊ฒฝ์šฐ: ๋””์Šคํฌ ๊ธฐ๋ฐ˜ ์บ์‹ฑ์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ EhCache์˜ ํŠน์ • ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์ ํ•ฉํ•ด์š”.


@Bean
public CacheManager cacheManager() {
    return new EhCacheCacheManager(ehCacheManager());
}

@Bean
public EhCacheManagerFactoryBean ehCacheManager() {
    EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
    factory.setConfigLocation(new ClassPathResource("ehcache.xml"));
    factory.setShared(true);
    return factory;
}
                

4. RedisCacheManager

ํŠน์ง•: Redis๋Š” ์ธ๊ธฐ ์žˆ๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋กœ, ๋ถ„์‚ฐ ์บ์‹ฑ์— ์ ํ•ฉํ•ด์š”.

์žฅ์ : ๋ถ„์‚ฐ ํ™˜๊ฒฝ ์ง€์›, ์˜์†์„ฑ ์˜ต์…˜, ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ง€์›

๋‹จ์ : ๋ณ„๋„์˜ Redis ์„œ๋ฒ„ ์„ค์ •์ด ํ•„์š”ํ•˜๊ณ , ๋„คํŠธ์›Œํฌ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์žˆ์–ด์š”.

์ ํ•ฉํ•œ ๊ฒฝ์šฐ: ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋‚˜ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์— ์ ํ•ฉํ•ด์š”.


@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
                    
    return RedisCacheManager.builder(redisConnectionFactory)
            .cacheDefaults(config)
            .build();
}
                

5. CompositeCacheManager

ํŠน์ง•: ์—ฌ๋Ÿฌ ์บ์‹œ ๋งค๋‹ˆ์ €๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

์žฅ์ : ๋‹ค์–‘ํ•œ ์บ์‹ฑ ์ „๋žต์„ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ž์ฃผ ์ ‘๊ทผํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋กœ์ปฌ ์บ์‹œ์—, ๊ณต์œ  ๋ฐ์ดํ„ฐ๋Š” Redis์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด์š”.

๋‹จ์ : ์„ค์ •์ด ๋ณต์žกํ•˜๊ณ  ์บ์‹œ ์ผ๊ด€์„ฑ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด์š”.

์ ํ•ฉํ•œ ๊ฒฝ์šฐ: ๋‹ค์–‘ํ•œ ์บ์‹ฑ ์š”๊ตฌ์‚ฌํ•ญ์ด ์žˆ๋Š” ๋ณต์žกํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•ด์š”.


@Bean
public CacheManager cacheManager(
        CaffeineCacheManager caffeineCacheManager,
        RedisCacheManager redisCacheManager) {
    
    CompositeCacheManager compositeCacheManager = new CompositeCacheManager(
            caffeineCacheManager,
            redisCacheManager
    );
    compositeCacheManager.setFallbackToNoOpCache(true);
    return compositeCacheManager;
}
                
์บ์‹œ ๋งค๋‹ˆ์ € ๋น„๊ต ๊ธฐ๋Šฅ ๋ณต์žก์„ฑ ์„ฑ๋Šฅ ๋‚ฎ์Œ ๋†’์Œ ๋‚ฎ์Œ ๋†’์Œ ConcurrentMap ๊ฐ„๋‹จํ•จ Caffeine ๊ณ ์„ฑ๋Šฅ EhCache ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ Redis ๋ถ„์‚ฐ ์บ์‹ฑ ์„ฑ๋Šฅ ํ–ฅ์ƒ ๊ธฐ๋Šฅ ์ฆ๊ฐ€ ๋ถ„์‚ฐ ํ™˜๊ฒฝ

์บ์‹œ ๋งค๋‹ˆ์ € ์„ ํƒ ๊ฐ€์ด๋“œ ๐Ÿงญ

์–ด๋–ค ์บ์‹œ ๋งค๋‹ˆ์ €๋ฅผ ์„ ํƒํ•ด์•ผ ํ• ์ง€ ๊ณ ๋ฏผ๋œ๋‹ค๋ฉด, ๋‹ค์Œ ์งˆ๋ฌธ๋“ค์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”:

  1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹จ์ผ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋‚˜์š”, ์•„๋‹ˆ๋ฉด ์—ฌ๋Ÿฌ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋‚˜์š”?
  2. ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ํ›„์—๋„ ์œ ์ง€๋˜์–ด์•ผ ํ•˜๋‚˜์š”?
  3. ์บ์‹œ ํฌ๊ธฐ๋Š” ์–ผ๋งˆ๋‚˜ ๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋‚˜์š”?
  4. ์บ์‹œ ํ•ญ๋ชฉ์˜ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ํ•„์š”ํ•œ๊ฐ€์š”?
  5. ์บ์‹œ ํ†ต๊ณ„๋‚˜ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ํ•„์š”ํ•œ๊ฐ€์š”?

๐Ÿ’ก ์ถ”์ฒœ ๊ฐ€์ด๋“œ

  • ๋‹จ์ผ ์„œ๋ฒ„, ๊ฐ„๋‹จํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: ConcurrentMapCacheManager ๋˜๋Š” CaffeineCacheManager
  • ๋‹จ์ผ ์„œ๋ฒ„, ๊ณ ์„ฑ๋Šฅ ํ•„์š”: CaffeineCacheManager
  • ๋ถ„์‚ฐ ํ™˜๊ฒฝ, ์—ฌ๋Ÿฌ ์„œ๋ฒ„ ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ณต์œ  ํ•„์š”: RedisCacheManager
  • ๋””์Šคํฌ ์ €์žฅ์ด ํ•„์š”ํ•œ ๋Œ€์šฉ๋Ÿ‰ ์บ์‹œ: EhCacheCacheManager
  • ๋ณต์žกํ•œ ์š”๊ตฌ์‚ฌํ•ญ, ์—ฌ๋Ÿฌ ์บ์‹ฑ ์ „๋žต ํ•„์š”: CompositeCacheManager

์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๊ตฌ๋งคํ•  ๋•Œ, ์ด๋Ÿฐ ์บ์‹œ ์ตœ์ ํ™” ๊ธฐ์ˆ ์„ ์•„๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์ฐพ์œผ๋ฉด ํ”„๋กœ์ ํŠธ ์„ฑ๋Šฅ์ด ํ™•์‹คํžˆ ๋‹ฌ๋ผ์งˆ ๊ฑฐ์˜ˆ์š”! ๐Ÿš€

5. ์‹ค์ „ ์˜ˆ์ œ๋กœ ๋ฐฐ์šฐ๋Š” Spring Cache ๐Ÿ’ป

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ ์ด์ œ ์‹ค์ „ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด Spring Cache๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊ฒŒ์š”! ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๋” ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”.

์˜ˆ์ œ 1: ์ƒํ’ˆ ์ •๋ณด ์บ์‹ฑํ•˜๊ธฐ ๐Ÿ›’

์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์—์„œ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ์บ์‹ฑํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ์š”.

1. ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค


@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String description;
    private BigDecimal price;
    private int stock;
    
    // ์ƒ์„ฑ์ž, getter, setter ์ƒ๋žต
}
            

2. ๋ ˆํฌ์ง€ํ† ๋ฆฌ


public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByPriceGreaterThan(BigDecimal price);
}
            

3. ์„œ๋น„์Šค ํด๋ž˜์Šค (์บ์‹œ ์ ์šฉ)


@Service
@CacheConfig(cacheNames = {"products"})
public class ProductService {

    private final ProductRepository productRepository;
    
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
    
    @Cacheable(key = "#id")
    public Product getProductById(Long id) {
        System.out.println("DB์—์„œ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค: " + id);
        return productRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("์ƒํ’ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + id));
    }
    
    @Cacheable(key = "'premium_' + #price")
    public List<Product> getPremiumProducts(BigDecimal price) {
        System.out.println("DB์—์„œ ํ”„๋ฆฌ๋ฏธ์—„ ์ƒํ’ˆ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๊ฐ€๊ฒฉ: " + price);
        return productRepository.findByPriceGreaterThan(price);
    }
    
    @CachePut(key = "#product.id")
    public Product updateProduct(Product product) {
        System.out.println("์ƒํ’ˆ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค: " + product.getId());
        return productRepository.save(product);
    }
    
    @CacheEvict(key = "#id")
    public void deleteProduct(Long id) {
        System.out.println("์ƒํ’ˆ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค: " + id);
        productRepository.deleteById(id);
    }
    
    @CacheEvict(allEntries = true)
    public void clearProductCache() {
        System.out.println("๋ชจ๋“  ์ƒํ’ˆ ์บ์‹œ๋ฅผ ์ง€์›๋‹ˆ๋‹ค");
    }
}
            

4. ์ปจํŠธ๋กค๋Ÿฌ


@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;
    
    public ProductController(ProductService productService) {
        this.productService = productService;
    }
    
    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.getProductById(id);
    }
    
    @GetMapping("/premium")
    public List<Product> getPremiumProducts(@RequestParam BigDecimal minPrice) {
        return productService.getPremiumProducts(minPrice);
    }
    
    @PutMapping
    public Product updateProduct(@RequestBody Product product) {
        return productService.updateProduct(product);
    }
    
    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
    }
    
    @DeleteMapping("/cache")
    public void clearCache() {
        productService.clearProductCache();
    }
}
            

์œ„ ์˜ˆ์ œ์—์„œ getProductById ๋ฉ”์„œ๋“œ๋Š” ์ฒ˜์Œ ํ˜ธ์ถœ๋  ๋•Œ๋งŒ DB์— ์ ‘๊ทผํ•˜๊ณ , ์ดํ›„ ๊ฐ™์€ ID๋กœ ํ˜ธ์ถœํ•˜๋ฉด ์บ์‹œ์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์™€์š”. ์ฝ˜์†” ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”!

์˜ˆ์ œ 2: ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ์บ์‹ฑํ•˜๊ธฐ ๐Ÿ”

์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋ฅผ ์บ์‹ฑํ•ด์„œ ๋กœ๊ทธ์ธ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณผ๊ฒŒ์š”.


@Service
public class UserAuthService {

    private final UserRepository userRepository;
    
    public UserAuthService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Cacheable(value = "userAuth", key = "#username", condition = "#username != null")
    public UserDetails loadUserByUsername(String username) {
        System.out.println("DB์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค: " + username);
        
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + username));
        
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                user.isEnabled(),
                true, true, true,
                user.getAuthorities()
        );
    }
    
    @CacheEvict(value = "userAuth", key = "#username")
    public void invalidateUserCache(String username) {
        System.out.println("์‚ฌ์šฉ์ž ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•ฉ๋‹ˆ๋‹ค: " + username);
    }
    
    // ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹œ ์บ์‹œ ์—…๋ฐ์ดํŠธ
    @CacheEvict(value = "userAuth", key = "#username")
    public void changePassword(String username, String newPassword) {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + username));
        
        user.setPassword(newPassword);
        userRepository.save(user);
        
        // ์บ์‹œ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฌดํšจํ™”๋ฉ๋‹ˆ๋‹ค
        System.out.println("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์บ์‹œ๊ฐ€ ๋ฌดํšจํ™”๋ฉ๋‹ˆ๋‹ค.");
    }
}
            

์ด ์˜ˆ์ œ์—์„œ๋Š” ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋ฅผ ์บ์‹ฑํ•ด์„œ ๋กœ๊ทธ์ธ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ–ˆ์–ด์š”. ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•ด์„œ ์ตœ์‹  ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์–ด์š”.

์˜ˆ์ œ 3: ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ๊ณผ ๋™์  ํ‚ค ์ƒ์„ฑ ๐Ÿ”„

์ข€ ๋” ๋ณต์žกํ•œ ์บ์‹ฑ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‚ดํŽด๋ณผ๊ฒŒ์š”.


@Service
public class ReportService {

    private final ReportRepository reportRepository;
    
    public ReportService(ReportRepository reportRepository) {
        this.reportRepository = reportRepository;
    }
    
    // ๊ด€๋ฆฌ์ž๋Š” ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋ด์•ผ ํ•˜๋ฏ€๋กœ ์บ์‹ฑํ•˜์ง€ ์•Š์Œ
    @Cacheable(
        value = "reports", 
        key = "#reportType + '_' + #period", 
        condition = "!#isAdmin",
        unless = "#result == null || #result.isEmpty()"
    )
    public List<ReportData> generateReport(String reportType, String period, boolean isAdmin) {
        System.out.println("๋ณด๊ณ ์„œ ์ƒ์„ฑ ์ค‘: " + reportType + ", ๊ธฐ๊ฐ„: " + period + ", ๊ด€๋ฆฌ์ž: " + isAdmin);
        
        // ์‹ค์ œ๋กœ๋Š” ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๊ฐ€ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค
        return reportRepository.generateReport(reportType, period);
    }
    
    // ๋งค์ผ ์ž์ •์— ์Šค์ผ€์ค„๋Ÿฌ๋กœ ํ˜ธ์ถœ๋˜์–ด ์บ์‹œ ๊ฐฑ์‹ 
    @CacheEvict(value = "reports", allEntries = true)
    @Scheduled(cron = "0 0 0 * * ?")
    public void clearReportCache() {
        System.out.println("๋ณด๊ณ ์„œ ์บ์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ„: " + new Date());
    }
    
    // ํŠน์ • ์œ ํ˜•์˜ ๋ณด๊ณ ์„œ๋งŒ ์บ์‹œ ๊ฐฑ์‹ 
    @CacheEvict(value = "reports", key = "#reportType + '_*'", allEntries = false)
    public void refreshReportCache(String reportType) {
        System.out.println(reportType + " ์œ ํ˜•์˜ ๋ณด๊ณ ์„œ ์บ์‹œ๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.");
    }
}
            

์ด ์˜ˆ์ œ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ–ˆ์–ด์š”:

  1. ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ (condition, unless)
  2. ๋ณตํ•ฉ ํ‚ค ์ƒ์„ฑ (reportType + '_' + period)
  3. ์Šค์ผ€์ค„๋ง์„ ํ†ตํ•œ ์บ์‹œ ์ž๋™ ๊ฐฑ์‹ 
  4. ํŠน์ • ํŒจํ„ด์˜ ์บ์‹œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐฑ์‹ 

๐Ÿ’ก ์‹ค๋ฌด ํŒ

์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‹ค์Œ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์„ธ์š”:

  1. ์บ์‹œ ํ‚ค๋Š” ์ถฉ๋ถ„ํžˆ ๊ตฌ์ฒด์ ์ด์–ด์•ผ ํ•ด์š” (์ถฉ๋Œ ๋ฐฉ์ง€)
  2. ์บ์‹œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ์ ์ ˆํžˆ ์„ค์ •ํ•˜์„ธ์š” (๋ฐ์ดํ„ฐ ์‹ ์„ ๋„ vs ์„ฑ๋Šฅ)
  3. ์บ์‹œ ํฌ๊ธฐ ์ œํ•œ์„ ์„ค์ •ํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ๊ด€๋ฆฌํ•˜์„ธ์š”
  4. ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ์„ ํ™œ์šฉํ•ด ๋ถˆํ•„์š”ํ•œ ์บ์‹ฑ์„ ๋ฐฉ์ง€ํ•˜์„ธ์š”
  5. ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•ด์„œ ์บ์‹œ ๋™์ž‘์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์„ธ์š”

์ด๋Ÿฐ ์‹ค์ „ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด Spring Cache๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฐ์ด ์˜ค์‹œ๋‚˜์š”? ์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ ๊ด€๋ จ ์žฌ๋Šฅ์„ ์ฐพ๋Š”๋‹ค๋ฉด, ์ด๋Ÿฐ ์บ์‹ฑ ๊ธฐ์ˆ ์„ ์ž˜ ์•„๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ๋งŒ๋‚  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”! ๐Ÿ˜‰

6. ์บ์‹œ ์ „๋žต ๋ฐ ํŒจํ„ด ๐Ÿงฉ

์บ์‹œ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ ์ ˆํ•œ ์ „๋žต๊ณผ ํŒจํ„ด์„ ์ ์šฉํ•ด์•ผ ํ•ด์š”. ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜ ์บ์‹ฑํ• ์ง€, ์–ด๋–ป๊ฒŒ ๋ฌดํšจํ™”ํ• ์ง€ ๋“ฑ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ด์š”.

์ฃผ์š” ์บ์‹œ ์ „๋žต ๐Ÿ“‹

1. Cache-Aside (Lazy Loading) ํŒจํ„ด

๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์œผ๋กœ, ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•  ๋•Œ ๋จผ์ € ์บ์‹œ๋ฅผ ํ™•์ธํ•˜๊ณ , ์—†์œผ๋ฉด DB์—์„œ ๊ฐ€์ ธ์™€ ์บ์‹œ์— ์ €์žฅํ•ด์š”.

์žฅ์ : ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์บ์‹ฑ๋˜๊ณ , ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•ด์š”.

๋‹จ์ : ์บ์‹œ ๋ฏธ์Šค ์‹œ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•˜๊ณ , ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด์š”.

Spring Cache์—์„œ: @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์ด ์ด ํŒจํ„ด์„ ๊ตฌํ˜„ํ•ด์š”.


@Cacheable(value = "users", key = "#userId")
public User getUser(Long userId) {
    // ์บ์‹œ์— ์—†์œผ๋ฉด ์ด ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋จ
    return userRepository.findById(userId).orElse(null);
}
                

2. Write-Through ํŒจํ„ด

๋ฐ์ดํ„ฐ๋ฅผ ์“ธ ๋•Œ DB์™€ ์บ์‹œ๋ฅผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•ด์š”.

์žฅ์ : ์บ์‹œ์™€ DB์˜ ์ผ๊ด€์„ฑ์ด ์œ ์ง€๋ผ์š”.

๋‹จ์ : ์“ฐ๊ธฐ ์ž‘์—…์ด ์•ฝ๊ฐ„ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์–ด์š”.

Spring Cache์—์„œ: @CachePut ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.


@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    // DB ์—…๋ฐ์ดํŠธ ํ›„ ๊ฒฐ๊ณผ๊ฐ€ ์ž๋™์œผ๋กœ ์บ์‹œ์— ์ €์žฅ๋จ
    return userRepository.save(user);
}
                

3. Write-Behind (Write-Back) ํŒจํ„ด

๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ์บ์‹œ์— ์“ฐ๊ณ , ๋‚˜์ค‘์— ๋น„๋™๊ธฐ์ ์œผ๋กœ DB์— ๊ธฐ๋กํ•ด์š”.

์žฅ์ : ์“ฐ๊ธฐ ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋ผ์š”.

๋‹จ์ : ๋ฐ์ดํ„ฐ ์†์‹ค ์œ„ํ—˜์ด ์žˆ๊ณ , ๊ตฌํ˜„์ด ๋ณต์žกํ•ด์š”.

Spring Cache์—์„œ: ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ, ๋ฉ”์‹œ์ง€ ํ์™€ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.


// ์บ์‹œ ์—…๋ฐ์ดํŠธ
@CachePut(value = "users", key = "#user.id")
public User updateUserInCache(User user) {
    // ๋ฉ”์‹œ์ง€ ํ์— ์—…๋ฐ์ดํŠธ ์ด๋ฒคํŠธ ๋ฐœํ–‰
    messageSender.send(new UserUpdateEvent(user));
    return user;
}

// ๋น„๋™๊ธฐ ๋ฆฌ์Šค๋„ˆ
@Async
@EventListener
public void handleUserUpdateEvent(UserUpdateEvent event) {
    User user = event.getUser();
    userRepository.save(user);
}
                

4. Read-Through ํŒจํ„ด

์บ์‹œ ๋ฏธ์Šค ์‹œ ์บ์‹œ ์ž์ฒด๊ฐ€ DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•ด์š”.

์žฅ์ : ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๊ฐ€ ๋‹จ์ˆœํ•ด์ง€๊ณ , ์บ์‹œ ๋กœ์ง์ด ์บก์Аํ™”๋ผ์š”.

๋‹จ์ : ์บ์‹œ ๊ตฌํ˜„์ด ๋ฐ์ดํ„ฐ ์†Œ์Šค์— ์˜์กด์ ์ด์—์š”.

Spring Cache์—์„œ: ์บ์‹œ ๋งค๋‹ˆ์ € ๊ตฌํ˜„์ฒด์— ๋”ฐ๋ผ ๋‹ค๋ฅด์ง€๋งŒ, @Cacheable๊ณผ ์œ ์‚ฌํ•œ ๋™์ž‘์„ ํ•ด์š”.

5. Refresh-Ahead ํŒจํ„ด

์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํ•ญ๋ชฉ์„ ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „์— ๋ฏธ๋ฆฌ ๊ฐฑ์‹ ํ•ด์š”.

์žฅ์ : ์บ์‹œ ๋ฏธ์Šค๋ฅผ ์ค„์ด๊ณ  ์ผ๊ด€๋œ ์„ฑ๋Šฅ์„ ์ œ๊ณตํ•ด์š”.

๋‹จ์ : ์˜ˆ์ธก ๋กœ์ง์ด ํ•„์š”ํ•˜๊ณ , ๋ถˆํ•„์š”ํ•œ ๊ฐฑ์‹ ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”.

Spring Cache์—์„œ: ์Šค์ผ€์ค„๋Ÿฌ์™€ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.


@Scheduled(fixedRate = 600000) // 10๋ถ„๋งˆ๋‹ค ์‹คํ–‰
public void refreshPopularItems() {
    List<Long> popularItemIds = analyticsService.getTopViewedItems(100);
    for (Long itemId : popularItemIds) {
        // ์บ์‹œ ๊ฐ•์ œ ๊ฐฑ์‹ 
        Item item = itemRepository.findById(itemId).orElse(null);
        if (item != null) {
            cacheManager.getCache("items").put(itemId, item);
        }
    }
}
                
์บ์‹œ ์ „๋žต ๋น„๊ต ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ 1. ์บ์‹œ ํ™•์ธ 2. ์บ์‹œ ๋ฏธ์Šค ์‹œ DB ์กฐํšŒ 3. ๊ฒฐ๊ณผ ์บ์‹œ์— ์ €์žฅ Cache-Aside ํŒจํ„ด 1. ์บ์‹œ ์—…๋ฐ์ดํŠธ 2. DB ์—…๋ฐ์ดํŠธ Write-Through ํŒจํ„ด

์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต ๐Ÿ”„

์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋ž˜๋˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด์š”. ๋‹ค์Œ์€ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•˜๋Š” ์ฃผ์š” ์ „๋žต์ด์—์š”:

1. TTL (Time-To-Live) ๊ธฐ๋ฐ˜ ๋งŒ๋ฃŒ

์บ์‹œ ํ•ญ๋ชฉ์— ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ์„ค์ •ํ•ด์š”. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์ด์—์š”.


// Caffeine ์บ์‹œ์— TTL ์„ค์ •
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES));
    return cacheManager;
}

// Redis ์บ์‹œ์— TTL ์„ค์ •
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10));
    return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .build();
}
                

2. ๋ช…์‹œ์  ๋ฌดํšจํ™”

๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๊ด€๋ จ ์บ์‹œ ํ•ญ๋ชฉ์„ ๋ช…์‹œ์ ์œผ๋กœ ์ œ๊ฑฐํ•ด์š”.


@CacheEvict(value = "users", key = "#userId")
public void updateUserProfile(Long userId, UserProfileDto profileDto) {
    User user = userRepository.findById(userId).orElseThrow();
    user.updateProfile(profileDto);
    userRepository.save(user);
}
                

3. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋ฌดํšจํ™”

๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ , ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์—์„œ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•ด์š”.


// ์ด๋ฒคํŠธ ๋ฐœํ–‰
@Transactional
public void updateProduct(Product product) {
    productRepository.save(product);
    eventPublisher.publishEvent(new ProductUpdatedEvent(product.getId()));
}

// ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
@EventListener
public void handleProductUpdated(ProductUpdatedEvent event) {
    cacheManager.getCache("products").evict(event.getProductId());
    cacheManager.getCache("productCategories").clear();
}
                

4. ๋ฒ„์ „ ๊ธฐ๋ฐ˜ ์บ์‹ฑ

์บ์‹œ ํ‚ค์— ๋ฐ์ดํ„ฐ ๋ฒ„์ „์„ ํฌํ•จ์‹œ์ผœ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ƒˆ ๋ฒ„์ „์œผ๋กœ ์บ์‹œํ•ด์š”.


@Cacheable(value = "products", key = "#productId + '_' + @productVersionService.getVersion(#productId)")
public Product getProduct(Long productId) {
    return productRepository.findById(productId).orElse(null);
}

// ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ
@Transactional
public void updateProduct(Product product) {
    productRepository.save(product);
    productVersionService.incrementVersion(product.getId());
}
                

์บ์‹œ ํ‚ค ์„ค๊ณ„ ์›์น™ ๐Ÿ”‘

ํšจ๊ณผ์ ์ธ ์บ์‹œ ํ‚ค ์„ค๊ณ„๋Š” ์บ์‹œ ์„ฑ๋Šฅ๊ณผ ๊ด€๋ฆฌ์— ์ค‘์š”ํ•ด์š”:

  1. ๊ณ ์œ ์„ฑ: ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋Š” ๋‹ค๋ฅธ ํ‚ค๋ฅผ ๊ฐ€์ ธ์•ผ ํ•ด์š”.
  2. ๊ฐ„๊ฒฐ์„ฑ: ํ‚ค๋Š” ๊ฐ€๋Šฅํ•œ ์งง๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š” (๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ).
  3. ์˜๋ฏธ์„ฑ: ํ‚ค๋Š” ๋‚ด์šฉ์„ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด์š” (๋””๋ฒ„๊น… ์šฉ์ด์„ฑ).
  4. ์ผ๊ด€์„ฑ: ํ‚ค ์ƒ์„ฑ ๊ทœ์น™์€ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”.
  5. ์ถฉ๋Œ ๋ฐฉ์ง€: ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋‚˜ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•ด ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜์„ธ์š”.

์บ์‹œ ํ‚ค ์˜ˆ์‹œ:


// ๋‚˜์œ ์˜ˆ: ๋„ˆ๋ฌด ์ผ๋ฐ˜์ 
@Cacheable(value = "data", key = "#id")

// ์ข‹์€ ์˜ˆ: ๊ตฌ์ฒด์ ์ด๊ณ  ์˜๋ฏธ ์žˆ์Œ
@Cacheable(value = "users", key = "'user_' + #id")

// ๋ณตํ•ฉ ํ‚ค ์‚ฌ์šฉ
@Cacheable(value = "reports", key = "#reportType + '_' + #period + '_' + #format")

// ๊ฐ์ฒด ์†์„ฑ ์‚ฌ์šฉ
@Cacheable(value = "orders", key = "#order.customerId + '_' + #order.status")

// ํ•ด์‹œ ํ•จ์ˆ˜ ์‚ฌ์šฉ (๋ณต์žกํ•œ ๊ฐ์ฒด)
@Cacheable(value = "searchResults", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#searchCriteria.toString().getBytes())")
            

๐Ÿ’ก ์บ์‹œ ์ „๋žต ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

  1. ์ฝ๊ธฐ๊ฐ€ ๋งŽ๊ณ  ์“ฐ๊ธฐ๊ฐ€ ์ ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์„ธ์š”.
  2. ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” TTL์„ ์งง๊ฒŒ ์„ค์ •ํ•˜์„ธ์š”.
  3. ์บ์‹œ ํ‚ค์— ๋ฒ„์ „์ด๋‚˜ ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”.
  4. ์บ์‹œ ํฌ๊ธฐ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์ œํ•œํ•˜์„ธ์š”.
  5. ์บ์‹œ ํžˆํŠธ์œจ์„ ์ธก์ •ํ•˜๊ณ  ์ตœ์ ํ™”ํ•˜์„ธ์š”.
  6. ์บ์‹œ ๊ณ„์ธต์„ ์‚ฌ์šฉํ•˜์„ธ์š” (L1/L2 ์บ์‹œ).
  7. ์บ์‹œ ํญ๋ฐœ(Cache Stampede)์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ „๋žต์„ ๋งˆ๋ จํ•˜์„ธ์š”.

์ ์ ˆํ•œ ์บ์‹œ ์ „๋žต์„ ์„ ํƒํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”. ์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ์ž๋ฅผ ์ฐพ์„ ๋•Œ, ์ด๋Ÿฐ ์บ์‹œ ์ „๋žต์„ ์ž˜ ์•„๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์„ ํƒํ•˜๋ฉด ํ”„๋กœ์ ํŠธ๊ฐ€ ํ›จ์”ฌ ํšจ์œจ์ ์œผ๋กœ ์ง„ํ–‰๋  ๊ฑฐ์˜ˆ์š”! ๐Ÿš€

7. ์„ฑ๋Šฅ ์ธก์ • ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ“Š

์บ์‹œ๋ฅผ ๋„์ž…ํ–ˆ๋‹ค๋ฉด ๊ทธ ํšจ๊ณผ๋ฅผ ์ธก์ •ํ•˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”. ์บ์‹œ๊ฐ€ ์ •๋ง๋กœ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ณ  ์žˆ๋Š”์ง€, ์–ด๋–ค ๋ถ€๋ถ„์„ ๋” ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„์•ผ ํ•ด์š”.

์บ์‹œ ์„ฑ๋Šฅ ์ง€ํ‘œ ๐Ÿ“ˆ

๋‹ค์Œ์€ ์บ์‹œ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•˜๋Š” ์ฃผ์š” ์ง€ํ‘œ๋“ค์ด์—์š”:

1. ์บ์‹œ ํžˆํŠธ์œจ (Hit Rate)

์ „์ฒด ์บ์‹œ ์š”์ฒญ ์ค‘ ์บ์‹œ์—์„œ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์€ ๋น„์œจ์ด์—์š”.

๊ณ„์‚ฐ: (์บ์‹œ ํžˆํŠธ ์ˆ˜) / (์ „์ฒด ์บ์‹œ ์š”์ฒญ ์ˆ˜) ร— 100%

๋ชฉํ‘œ: ์ผ๋ฐ˜์ ์œผ๋กœ 80% ์ด์ƒ์ด ์ข‹์€ ํžˆํŠธ์œจ๋กœ ๊ฐ„์ฃผ๋ผ์š”.

๊ฐœ์„  ๋ฐฉ๋ฒ•: ์บ์‹œ ํฌ๊ธฐ ์ฆ๊ฐ€, TTL ์กฐ์ •, ์บ์‹œ ํ‚ค ์ตœ์ ํ™”

2. ์‘๋‹ต ์‹œ๊ฐ„ (Response Time)

์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์ด์—์š”.

์ธก์ •: ์บ์‹œ ํžˆํŠธ ์‹œ ์‘๋‹ต ์‹œ๊ฐ„ vs ์บ์‹œ ๋ฏธ์Šค ์‹œ ์‘๋‹ต ์‹œ๊ฐ„

๋ชฉํ‘œ: ์บ์‹œ ํžˆํŠธ ์‹œ ์‘๋‹ต ์‹œ๊ฐ„์ด ๋ฏธ์Šค ์‹œ๋ณด๋‹ค ํ˜„์ €ํžˆ ๋นจ๋ผ์•ผ ํ•ด์š”.

๊ฐœ์„  ๋ฐฉ๋ฒ•: ์บ์‹œ ๊ตฌํ˜„์ฒด ๋ณ€๊ฒฝ, ์บ์‹œ ๊ณ„์ธตํ™”, ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์ตœ์†Œํ™”

3. ์บ์‹œ ํฌ๊ธฐ ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰

์บ์‹œ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์–‘์ด์—์š”.

์ธก์ •: ํ•ญ๋ชฉ ์ˆ˜, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ (MB/GB)

๋ชฉํ‘œ: ๋ฉ”๋ชจ๋ฆฌ ํ•œ๊ณ„ ๋‚ด์—์„œ ์ตœ์ ์˜ ํžˆํŠธ์œจ ๋‹ฌ์„ฑ

๊ฐœ์„  ๋ฐฉ๋ฒ•: ์บ์‹œ ํฌ๊ธฐ ์ œํ•œ, ๋งŒ๋ฃŒ ์ •์ฑ… ์ ์šฉ, ํ•ญ๋ชฉ ํฌ๊ธฐ ์ตœ์ ํ™”

4. ์บ์‹œ ์ ์ค‘ ์ง€์—ฐ ์‹œ๊ฐ„ (Hit Latency)

์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์ด์—์š”.

์ธก์ •: ๋ฐ€๋ฆฌ์ดˆ(ms) ๋‹จ์œ„๋กœ ์ธก์ •

๋ชฉํ‘œ: ๊ฐ€๋Šฅํ•œ ๋‚ฎ๊ฒŒ ์œ ์ง€ (์ผ๋ฐ˜์ ์œผ๋กœ < 10ms)

๊ฐœ์„  ๋ฐฉ๋ฒ•: ๋” ๋น ๋ฅธ ์บ์‹œ ๊ตฌํ˜„์ฒด ์‚ฌ์šฉ, ์บ์‹œ ํ‚ค ๋‹จ์ˆœํ™”

5. ์บ์‹œ ํšŒ์ˆ˜์œจ (Eviction Rate)

์บ์‹œ ๊ณต๊ฐ„ ํ™•๋ณด๋ฅผ ์œ„ํ•ด ์ œ๊ฑฐ๋˜๋Š” ํ•ญ๋ชฉ์˜ ๋น„์œจ์ด์—์š”.

์ธก์ •: ๋‹จ์œ„ ์‹œ๊ฐ„๋‹น ์ œ๊ฑฐ๋œ ํ•ญ๋ชฉ ์ˆ˜

๋ชฉํ‘œ: ๋‚ฎ์€ ํšŒ์ˆ˜์œจ (๋†’์€ ํšŒ์ˆ˜์œจ์€ ์บ์‹œ ํฌ๊ธฐ๊ฐ€ ๋ถ€์กฑํ•จ์„ ์˜๋ฏธ)

๊ฐœ์„  ๋ฐฉ๋ฒ•: ์บ์‹œ ํฌ๊ธฐ ์ฆ๊ฐ€, ๋งŒ๋ฃŒ ์ •์ฑ… ์ตœ์ ํ™”, ์ค‘์š” ํ•ญ๋ชฉ ์šฐ์„ ์ˆœ์œ„ ์ง€์ •

Spring Boot Actuator๋กœ ์บ์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ”

Spring Boot Actuator๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์บ์‹œ ํ†ต๊ณ„๋ฅผ ์‰ฝ๊ฒŒ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์–ด์š”.

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


// Maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

// Gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'
            

2. ์„ค์ • ์ถ”๊ฐ€ (application.properties ๋˜๋Š” application.yml)


# ์บ์‹œ ๋ฉ”ํŠธ๋ฆญ ํ™œ์„ฑํ™”
management.endpoints.web.exposure.include=health,info,metrics,caches
management.endpoint.metrics.enabled=true
management.endpoint.caches.enabled=true
            

3. ์บ์‹œ ํ†ต๊ณ„ ํ™œ์„ฑํ™” (Caffeine ์˜ˆ์‹œ)


@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(500)
            .recordStats());  // ํ†ต๊ณ„ ๊ธฐ๋ก ํ™œ์„ฑํ™”
    return cacheManager;
}
            

4. ์—”๋“œํฌ์ธํŠธ ์ ‘๊ทผ

๋‹ค์Œ URL๋กœ ์บ์‹œ ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์š”:

  • http://localhost:8080/actuator/caches - ์บ์‹œ ๋ชฉ๋ก
  • http://localhost:8080/actuator/metrics/cache.size - ์บ์‹œ ํฌ๊ธฐ
  • http://localhost:8080/actuator/metrics/cache.gets - ์บ์‹œ ์š”์ฒญ ์ˆ˜
  • http://localhost:8080/actuator/metrics/cache.puts - ์บ์‹œ ์‚ฝ์ž… ์ˆ˜
  • http://localhost:8080/actuator/metrics/cache.evictions
    - ์บ์‹œ ์ œ๊ฑฐ ์ˆ˜

Micrometer๋กœ ์บ์‹œ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ํ•˜๊ธฐ ๐Ÿ“

Micrometer๋Š” Spring Boot์— ๋‚ด์žฅ๋œ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๋‹ค์–‘ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ๊ณผ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

1. Prometheus ํ†ตํ•ฉ ์˜ˆ์‹œ


// Maven
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

// Gradle
implementation 'io.micrometer:micrometer-registry-prometheus'
            

2. ์„ค์ • ์ถ”๊ฐ€


# Prometheus ์—”๋“œํฌ์ธํŠธ ํ™œ์„ฑํ™”
management.endpoints.web.exposure.include=prometheus,health,info
            

3. ์ปค์Šคํ…€ ์บ์‹œ ๋ฉ”ํŠธ๋ฆญ ์ถ”๊ฐ€


@Component
public class CacheMetricsCollector {

    private final MeterRegistry meterRegistry;
    private final CacheManager cacheManager;
    
    public CacheMetricsCollector(MeterRegistry meterRegistry, CacheManager cacheManager) {
        this.meterRegistry = meterRegistry;
        this.cacheManager = cacheManager;
        
        // 5์ดˆ๋งˆ๋‹ค ์บ์‹œ ํฌ๊ธฐ ์ธก์ •
        Gauge.builder("custom.cache.size", cacheManager, 
                manager -> manager.getCacheNames().stream()
                        .map(name -> manager.getCache(name))
                        .filter(Objects::nonNull)
                        .count())
                .description("Number of caches")
                .register(meterRegistry);
    }
}
            
์บ์‹œ ํžˆํŠธ์œจ ์‹œ๊ฐํ™” 0 25% 50% 75% 100% ์บ์‹œ ํžˆํŠธ์œจ 0ms 50ms 100ms ์‘๋‹ต ์‹œ๊ฐ„ ์ตœ์  ์˜์—ญ ํžˆํŠธ์œจ์ด ๋†’์„์ˆ˜๋ก ์‘๋‹ต ์‹œ๊ฐ„์ด ๊ฐ์†Œํ•ฉ๋‹ˆ๋‹ค

์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๋„๊ตฌ ๐Ÿ› ๏ธ

์บ์‹œ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ๋“ค์ด์—์š”:

  1. JMeter: ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์„ฑ๋Šฅ ์ธก์ •
  2. Gatling: ๊ณ ์„ฑ๋Šฅ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ
  3. Spring Boot Actuator: ๊ธฐ๋ณธ ๋ฉ”ํŠธ๋ฆญ ์ œ๊ณต
  4. Prometheus + Grafana: ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ๋ฐ ์‹œ๊ฐํ™”
  5. Micrometer: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฉ”ํŠธ๋ฆญ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

JMeter๋ฅผ ์‚ฌ์šฉํ•œ ์บ์‹œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ

  1. ์บ์‹œ๊ฐ€ ์—†๋Š” ์ƒํƒœ์—์„œ API ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  2. ์‘๋‹ต ์‹œ๊ฐ„, ์ฒ˜๋ฆฌ๋Ÿ‰ ๋“ฑ์˜ ๊ธฐ์ค€ ๋ฉ”ํŠธ๋ฆญ ๊ธฐ๋ก
  3. ์บ์‹œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ณ  ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  4. ๊ฒฐ๊ณผ ๋น„๊ต ๋ฐ ๊ฐœ์„  ์ •๋„ ์ธก์ •
  5. ์บ์‹œ ์„ค์ •(ํฌ๊ธฐ, TTL ๋“ฑ)์„ ์กฐ์ •ํ•˜๋ฉฐ ์ตœ์  ๊ตฌ์„ฑ ์ฐพ๊ธฐ

์„ฑ๋Šฅ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ โœ…

์บ์‹œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์˜ˆ์š”:

  1. ์ ์ ˆํ•œ ์บ์‹œ ํฌ๊ธฐ ์„ค์ • (๋„ˆ๋ฌด ์ž‘์œผ๋ฉด ํšŒ์ˆ˜๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒ, ๋„ˆ๋ฌด ํฌ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„)
  2. ๋ฐ์ดํ„ฐ ํŠน์„ฑ์— ๋งž๋Š” ๋งŒ๋ฃŒ ์ •์ฑ… ์ ์šฉ
  3. ์บ์‹œ ํ‚ค ์„ค๊ณ„ ์ตœ์ ํ™” (๋„ˆ๋ฌด ๋ณต์žกํ•˜์ง€ ์•Š๊ฒŒ)
  4. ์บ์‹œ ํ†ต๊ณ„ ๋ชจ๋‹ˆํ„ฐ๋ง ์„ค์ •
  5. ์ฃผ๊ธฐ์ ์ธ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  6. ์บ์‹œ ์›Œ๋ฐ์—… ์ „๋žต ๊ตฌํ˜„ (์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ์ฃผ์š” ๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ ๋กœ๋“œ)
  7. ์บ์‹œ ๊ณ„์ธตํ™” ๊ณ ๋ ค (L1/L2 ์บ์‹œ)
  8. ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ์˜ ์บ์‹œ ์ผ๊ด€์„ฑ ๊ด€๋ฆฌ

"์ธก์ •ํ•  ์ˆ˜ ์—†์œผ๋ฉด ๊ฐœ์„ ํ•  ์ˆ˜ ์—†๋‹ค." - ํ”ผํ„ฐ ๋“œ๋Ÿฌ์ปค

์บ์‹œ ์„ฑ๋Šฅ์„ ์ง€์†์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์ธก์ •ํ•จ์œผ๋กœ์จ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ๊ณ„์† ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ์ž๋ฅผ ์ฐพ์„ ๋•Œ, ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ฒฝํ—˜์ด ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์„ ํƒํ•˜๋ฉด ํฐ ๋„์›€์ด ๋  ๊ฑฐ์˜ˆ์š”! ๐Ÿ“ˆ

8. ์ฃผ์˜์‚ฌํ•ญ ๋ฐ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค โš ๏ธ

์บ์‹œ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”. ์—ฌ๊ธฐ์„œ๋Š” Spring Cache ์‚ฌ์šฉ ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ์•Œ์•„๋ณผ๊ฒŒ์š”.

์ฃผ์˜์‚ฌํ•ญ ๐Ÿšจ

1. ์บ์‹œ ์ผ๊ด€์„ฑ ๋ฌธ์ œ

์บ์‹œ๋œ ๋ฐ์ดํ„ฐ์™€ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ฐ„์˜ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”.

์œ„ํ—˜: ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋˜๊ฑฐ๋‚˜, ์ค‘์š”ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ˆ„๋ฝ๋  ์ˆ˜ ์žˆ์–ด์š”.

ํ•ด๊ฒฐ์ฑ…:

  • ์ ์ ˆํ•œ TTL ์„ค์ •
  • ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์บ์‹œ ํ•ญ๋ชฉ ๋ช…์‹œ์  ๋ฌดํšจํ™”
  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์บ์‹œ ๋ฌดํšจํ™” ๊ตฌํ˜„

// ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์บ์‹œ ๋ฌดํšจํ™”
@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
    return userRepository.save(user);
}
                

2. ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€

๊ณผ๋„ํ•œ ์บ์‹ฑ์€ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”.

์œ„ํ—˜: OutOfMemoryError ๋ฐœ์ƒ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ์ €ํ•˜

ํ•ด๊ฒฐ์ฑ…:

  • ์บ์‹œ ํฌ๊ธฐ ์ œํ•œ ์„ค์ •
  • ์ ์ ˆํ•œ ์บ์‹œ ์ œ๊ฑฐ ์ •์ฑ… ์‚ฌ์šฉ
  • ์บ์‹œํ•  ๋ฐ์ดํ„ฐ ์„ ๋ณ„

// Caffeine ์บ์‹œ ํฌ๊ธฐ ์ œํ•œ
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(1000));  // ์ตœ๋Œ€ 1000๊ฐœ ํ•ญ๋ชฉ์œผ๋กœ ์ œํ•œ
    return cacheManager;
}
                

3. ์บ์‹œ ํญ๋ฐœ (Cache Stampede)

๋งŽ์€ ์บ์‹œ ํ•ญ๋ชฉ์ด ๋™์‹œ์— ๋งŒ๋ฃŒ๋˜์–ด DB์— ๊ฐ‘์ž‘์Šค๋Ÿฌ์šด ๋ถ€ํ•˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ํ˜„์ƒ์ด์—์š”.

์œ„ํ—˜: ์„œ๋ฒ„ ๊ณผ๋ถ€ํ•˜, ์‘๋‹ต ์‹œ๊ฐ„ ๊ธ‰์ฆ

ํ•ด๊ฒฐ์ฑ…:

  • ์บ์‹œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์— ์•ฝ๊ฐ„์˜ ๋ฌด์ž‘์œ„์„ฑ ์ถ”๊ฐ€
  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ฐฑ์‹  ๊ตฌํ˜„
  • ์บ์‹œ ๋ฝ(Lock) ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์‚ฌ์šฉ

// ๋งŒ๋ฃŒ ์‹œ๊ฐ„์— ๋ฌด์ž‘์œ„์„ฑ ์ถ”๊ฐ€
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(duration -> {
                // ๊ธฐ๋ณธ 10๋ถ„ + ์ตœ๋Œ€ 2๋ถ„์˜ ๋ฌด์ž‘์œ„ ์‹œ๊ฐ„ ์ถ”๊ฐ€
                return Duration.ofMinutes(10)
                        .plusSeconds(ThreadLocalRandom.current().nextLong(120));
            }));
    return cacheManager;
}
                

4. ์บ์‹œ ์˜ค์—ผ (Cache Pollution)

๊ฑฐ์˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์บ์‹œ๋ฅผ ์ฐจ์ง€ํ•˜๋Š” ํ˜„์ƒ์ด์—์š”.

์œ„ํ—˜: ์บ์‹œ ํšจ์œจ์„ฑ ์ €ํ•˜, ์ค‘์š” ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ

ํ•ด๊ฒฐ์ฑ…:

  • LRU(Least Recently Used) ๋˜๋Š” LFU(Least Frequently Used) ์ œ๊ฑฐ ์ •์ฑ… ์‚ฌ์šฉ
  • ์ ์ ˆํ•œ ์บ์‹œ ํ‚ค ์„ค๊ณ„
  • ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์„ ๋ณ„์ ์œผ๋กœ ์บ์‹ฑ

5. ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ์˜ ๋ฌธ์ œ

์—ฌ๋Ÿฌ ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค๊ฐ€ ๊ฐ์ž์˜ ๋กœ์ปฌ ์บ์‹œ๋ฅผ ๊ฐ€์งˆ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์˜ˆ์š”.

์œ„ํ—˜: ์„œ๋ฒ„ ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜, ์บ์‹œ ํšจ์œจ์„ฑ ์ €ํ•˜

ํ•ด๊ฒฐ์ฑ…:

  • Redis์™€ ๊ฐ™์€ ๋ถ„์‚ฐ ์บ์‹œ ์‚ฌ์šฉ
  • ์บ์‹œ ๋ฌดํšจํ™” ์ด๋ฒคํŠธ๋ฅผ ๋ชจ๋“  ์„œ๋ฒ„์— ์ „ํŒŒ
  • ์„ธ์…˜ ์–ดํ”ผ๋‹ˆํ‹ฐ(Sticky Session) ๊ตฌํ˜„

// Redis ์บ์‹œ ๋งค๋‹ˆ์ € ์„ค์ •
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
            .build();
}
                

๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ๐Ÿ†

1. ์ ์ ˆํ•œ ์บ์‹œ ๋Œ€์ƒ ์„ ํƒ

๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด์—์š”. ๋‹ค์Œ ํŠน์„ฑ์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์„ธ์š”:

  • ์ฝ๊ธฐ๊ฐ€ ๋งŽ๊ณ  ์“ฐ๊ธฐ๊ฐ€ ์ ์€ ๋ฐ์ดํ„ฐ
  • ๊ณ„์‚ฐ ๋น„์šฉ์ด ๋†’์€ ๋ฐ์ดํ„ฐ
  • ์ž์ฃผ ์ ‘๊ทผํ•˜๋Š” ๋ฐ์ดํ„ฐ
  • ํฌ๊ธฐ๊ฐ€ ์ ๋‹นํ•œ ๋ฐ์ดํ„ฐ

์˜ˆ: ์ƒํ’ˆ ์นดํƒˆ๋กœ๊ทธ, ์‚ฌ์šฉ์ž ํ”„๋กœํ•„, ์„ค์ • ์ •๋ณด ๋“ฑ

2. ์บ์‹œ ๊ณ„์ธตํ™”

์—ฌ๋Ÿฌ ๋ ˆ๋ฒจ์˜ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•ด ์„ฑ๋Šฅ๊ณผ ํ™•์žฅ์„ฑ์„ ๊ฐœ์„ ํ•˜์„ธ์š”:

  • L1 ์บ์‹œ: ์ธ๋ฉ”๋ชจ๋ฆฌ ๋กœ์ปฌ ์บ์‹œ (Caffeine)
  • L2 ์บ์‹œ: ๋ถ„์‚ฐ ์บ์‹œ (Redis)

@Component
public class TwoLevelCacheService {

    private final CaffeineCacheManager localCacheManager;
    private final RedisCacheManager distributedCacheManager;
    
    // ๋จผ์ € ๋กœ์ปฌ ์บ์‹œ ํ™•์ธ, ์—†์œผ๋ฉด ๋ถ„์‚ฐ ์บ์‹œ ํ™•์ธ, ๋‘˜ ๋‹ค ์—†์œผ๋ฉด DB ์กฐํšŒ
    public User getUser(Long userId) {
        String cacheKey = "user_" + userId;
        
        // L1 ์บ์‹œ ํ™•์ธ
        Cache localCache = localCacheManager.getCache("users");
        User user = localCache.get(cacheKey, User.class);
        if (user != null) {
            return user;
        }
        
        // L2 ์บ์‹œ ํ™•์ธ
        Cache distributedCache = distributedCacheManager.getCache("users");
        user = distributedCache.get(cacheKey, User.class);
        if (user != null) {
            // L1 ์บ์‹œ์—๋„ ์ €์žฅ
            localCache.put(cacheKey, user);
            return user;
        }
        
        // DB ์กฐํšŒ
        user = userRepository.findById(userId).orElse(null);
        if (user != null) {
            // ์–‘์ชฝ ์บ์‹œ์— ๋ชจ๋‘ ์ €์žฅ
            localCache.put(cacheKey, user);
            distributedCache.put(cacheKey, user);
        }
        
        return user;
    }
}
                

3. ์บ์‹œ ํ‚ค ์„ค๊ณ„

ํšจ๊ณผ์ ์ธ ์บ์‹œ ํ‚ค ์„ค๊ณ„ ์›์น™:

  • ๋ช…ํ™•ํ•œ ๋„ค์ด๋ฐ ๊ทœ์น™ ์‚ฌ์šฉ
  • ์ถฉ๋ถ„ํ•œ ์ •๋ณด ํฌํ•จ (์ถฉ๋Œ ๋ฐฉ์ง€)
  • ๋„ˆ๋ฌด ๋ณต์žกํ•˜์ง€ ์•Š๊ฒŒ ์œ ์ง€
  • ํ•„์š”์‹œ ํ•ด์‹œ ํ•จ์ˆ˜ ์‚ฌ์šฉ

// ์ข‹์€ ์บ์‹œ ํ‚ค ์˜ˆ์‹œ
@Cacheable(value = "products", key = "'product_' + #id + '_' + #language")
public ProductDto getProductDetails(Long id, String language) {
    // ...
}
                

4. ์บ์‹œ ์›Œ๋ฐ์—…

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ฃผ์š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ์บ์‹œ์— ๋กœ๋“œํ•ด ์ดˆ๊ธฐ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ๋ฐฉ์ง€ํ•˜์„ธ์š”:


@Component
public class CacheWarmer {

    private final ProductService productService;
    
    @EventListener(ApplicationReadyEvent.class)
    public void warmUpCaches() {
        log.info("์บ์‹œ ์›Œ๋ฐ์—… ์‹œ์ž‘...");
        
        // ์ธ๊ธฐ ์ƒํ’ˆ ๋ฏธ๋ฆฌ ์บ์‹ฑ
        List<Long> popularProductIds = getPopularProductIds();
        for (Long id : popularProductIds) {
            productService.getProductById(id);
        }
        
        log.info("์บ์‹œ ์›Œ๋ฐ์—… ์™„๋ฃŒ!");
    }
    
    private List<Long> getPopularProductIds() {
        // ์ธ๊ธฐ ์ƒํ’ˆ ID ๋ชฉ๋ก ๋ฐ˜ํ™˜ ๋กœ์ง
        return List.of(1L, 2L, 3L, 4L, 5L);
    }
}
                

5. ์บ์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ํŠœ๋‹

์บ์‹œ ์„ฑ๋Šฅ์„ ์ง€์†์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์ตœ์ ํ™”ํ•˜์„ธ์š”:

  • ํžˆํŠธ์œจ, ๋ฏธ์Šค์œจ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ 
  • ์บ์‹œ ๋งŒ๋ฃŒ ์ •์ฑ… ํŠœ๋‹
  • ์ฃผ๊ธฐ์ ์ธ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์‹คํ–‰

@Scheduled(fixedRate = 3600000) // 1์‹œ๊ฐ„๋งˆ๋‹ค ์‹คํ–‰
public void logCacheStatistics() {
    Cache usersCache = cacheManager.getCache("users");
    if (usersCache instanceof CaffeineCache) {
        CaffeineCache caffeineCache = (CaffeineCache) usersCache;
        com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = 
                caffeineCache.getNativeCache();
        
        CacheStats stats = nativeCache.stats();
        log.info("์บ์‹œ ํ†ต๊ณ„ - ํžˆํŠธ์œจ: {}%, ์š”์ฒญ ์ˆ˜: {}, ์ ์žฌ ์ˆ˜: {}, ์ œ๊ฑฐ ์ˆ˜: {}",
                stats.hitRate() * 100,
                stats.requestCount(),
                stats.loadCount(),
                stats.evictionCount());
    }
}
                
์บ์‹œ ๊ฒฐ์ • ํ๋ฆ„๋„ ๋ฐ์ดํ„ฐ ์บ์‹ฑ ํ•„์š”? ์ฝ๊ธฐ๊ฐ€ ์“ฐ๊ธฐ๋ณด๋‹ค ๋งŽ์€๊ฐ€? ์•„๋‹ˆ์˜ค ์˜ˆ ์บ์‹ฑํ•˜์ง€ ์•Š์Œ ์บ์‹ฑ ์ ํ•ฉ ์ ์ ˆํ•œ ์บ์‹œ ์ „๋žต ์„ ํƒ Cache-Aside Write-Through Read-Through

์‹ค๋ฌด์—์„œ ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜ ๐Ÿ™…โ€โ™‚๏ธ

  1. ๋ชจ๋“  ๊ฒƒ์„ ์บ์‹ฑํ•˜๋ ค๋Š” ์‹œ๋„: ์บ์‹ฑ์€ ์„ ๋ณ„์ ์œผ๋กœ ํ•ด์•ผ ํšจ๊ณผ์ ์ด์—์š”.
  2. ์บ์‹œ ๋งŒ๋ฃŒ ์ •์ฑ… ๋ถ€์žฌ: ์ ์ ˆํ•œ TTL ์—†์ด ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ด์š”.
  3. ์บ์‹œ ํฌ๊ธฐ ์ œํ•œ ์—†์Œ: ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ œํ•œํ•˜์ง€ ์•Š์œผ๋ฉด OutOfMemoryError๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”.
  4. ์บ์‹œ ํ‚ค ์ถฉ๋Œ: ๋„ˆ๋ฌด ๋‹จ์ˆœํ•œ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋กœ์˜ ์บ์‹œ๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์–ด์š”.
  5. ์บ์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ถ€์žฌ: ์บ์‹œ ์„ฑ๋Šฅ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์—†์–ด์š”.
  6. ์บ์‹œ ๋ฌดํšจํ™” ๋กœ์ง ๋ˆ„๋ฝ: ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์บ์‹œ๋ฅผ ๊ฐฑ์‹ ํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋ผ์š”.
  7. ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๋น„์šฉ ๋ฌด์‹œ: ๋ณต์žกํ•œ ๊ฐ์ฒด๋ฅผ ์บ์‹ฑํ•  ๋•Œ ์ด ๋น„์šฉ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ด์š”.

๐Ÿ’ก ์บ์‹œ ๋””๋ฒ„๊น… ํŒ

์บ์‹œ ๊ด€๋ จ ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊น…ํ•  ๋•Œ ๋„์›€์ด ๋˜๋Š” ํŒ์ด์—์š”:

  1. ๋กœ๊น… ์ถ”๊ฐ€: ์บ์‹œ ํžˆํŠธ/๋ฏธ์Šค๋ฅผ ๋กœ๊น…ํ•ด ๋™์ž‘ ํ™•์ธ
  2. ์บ์‹œ ๋น„ํ™œ์„ฑํ™” ํ…Œ์ŠคํŠธ: ๋ฌธ์ œ๊ฐ€ ์บ์‹œ ๋•Œ๋ฌธ์ธ์ง€ ํ™•์ธ
  3. ์บ์‹œ ๋‚ด์šฉ ๊ฒ€์‚ฌ: ์บ์‹œ ๋งค๋‹ˆ์ €๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ์บ์‹œ ์ƒํƒœ ํ™•์ธ
  4. ์บ์‹œ ํ‚ค ๊ฒ€์ฆ: ์˜๋„ํ•œ ๋Œ€๋กœ ํ‚ค๊ฐ€ ์ƒ์„ฑ๋˜๋Š”์ง€ ํ™•์ธ
  5. ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ ๊ฒ€์‚ฌ: condition, unless ํ‘œํ˜„์‹์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธ

// ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์บ์‹œ ๋‚ด์šฉ ์ถœ๋ ฅ
@Scheduled(fixedRate = 60000)
public void debugCache() {
    Cache usersCache = cacheManager.getCache("users");
    if (usersCache != null && usersCache instanceof ConcurrentMapCache) {
        ConcurrentMapCache mapCache = (ConcurrentMapCache) usersCache;
        Map<Object, Object> nativeCache = mapCache.getNativeCache();
        
        log.debug("ํ˜„์žฌ users ์บ์‹œ ๋‚ด์šฉ:");
        nativeCache.forEach((k, v) -> log.debug("ํ‚ค: {}, ๊ฐ’: {}", k, v));
    }
}
            

์ด๋Ÿฐ ์ฃผ์˜์‚ฌํ•ญ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ๋”ฐ๋ฅด๋ฉด Spring Cache๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”. ์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ์ž๋ฅผ ๊ตฌํ•˜์‹ค ๋•Œ, ์ด๋Ÿฐ ์บ์‹ฑ ๋…ธํ•˜์šฐ๋ฅผ ๊ฐ€์ง„ ๊ฐœ๋ฐœ์ž๋ฅผ ์ฐพ์œผ๋ฉด ํ”„๋กœ์ ํŠธ ํ’ˆ์งˆ์ด ํ•œ์ธต ๋†’์•„์งˆ ๊ฑฐ์˜ˆ์š”! ๐Ÿš€

9. ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ ๐Ÿ”ง

์ด๋ก ์€ ์ด์ œ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ๊ทธ ํ•ด๊ฒฐ์ฑ…์„ ์•Œ์•„๋ณผ๊ฒŒ์š”! ์‹ค์ œ ๊ฐœ๋ฐœ ํ˜„์žฅ์—์„œ ๊ฒช์„ ์ˆ˜ ์žˆ๋Š” ์บ์‹œ ๊ด€๋ จ ๋ฌธ์ œ๋“ค์ด์—์š”.

๋ฌธ์ œ 1: ์บ์‹œ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์š” ๐Ÿค”

์ฆ์ƒ:

@Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ, ๋ฉ”์„œ๋“œ๊ฐ€ ๊ณ„์† ์‹คํ–‰๋˜๊ณ  ์บ์‹œ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

  1. @EnableCaching ์–ด๋…ธํ…Œ์ด์…˜ ๋ˆ„๋ฝ
  2. ์ž์ฒด ํ˜ธ์ถœ(self-invocation) ๋ฌธ์ œ
  3. ์บ์‹œ ํ‚ค ์ƒ์„ฑ ์˜ค๋ฅ˜
  4. ํ”„๋ก์‹œ ๊ด€๋ จ ๋ฌธ์ œ

ํ•ด๊ฒฐ์ฑ…:


// 1. @EnableCaching ํ™•์ธ
@SpringBootApplication
@EnableCaching  // ์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ๋Š”์ง€ ํ™•์ธ!
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// 2. ์ž์ฒด ํ˜ธ์ถœ ๋ฌธ์ œ ํ•ด๊ฒฐ
@Service
public class ProductService {
    
    @Autowired
    private ApplicationContext context;
    
    @Cacheable("products")
    public Product getProduct(Long id) {
        // ์บ์‹œ ์ ์šฉ๋จ
        return findProductById(id);
    }
    
    public Product updateAndGet(Long id) {
        // ๋ฌธ์ œ: ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฉด ์บ์‹œ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Œ
        // return getProduct(id);  // โŒ
        
        // ํ•ด๊ฒฐ: ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ
        ProductService proxy = context.getBean(ProductService.class);
        return proxy.getProduct(id);  // โœ…
    }
    
    private Product findProductById(Long id) {
        // DB ์กฐํšŒ ๋กœ์ง
    }
}

// 3. ์บ์‹œ ํ‚ค ๋””๋ฒ„๊น…
@Cacheable(value = "products", key = "#id", condition = "#id > 0")
public Product getProduct(Long id) {
    System.out.println("์บ์‹œ ํ‚ค: " + id);  // ํ‚ค ๊ฐ’ ํ™•์ธ
    return productRepository.findById(id).orElse(null);
}
            

๋ฌธ์ œ 2: ํŠธ๋žœ์žญ์…˜๊ณผ ์บ์‹œ ๋™์ž‘ ์ˆœ์„œ ๋ฌธ์ œ ๐Ÿ”„

์ฆ์ƒ:

ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ์บ์‹œ๋ฅผ ์—…๋ฐ์ดํŠธํ–ˆ๋Š”๋ฐ, ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜๋ฉด ์บ์‹œ์™€ DB๊ฐ€ ๋ถˆ์ผ์น˜ํ•ด์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

์บ์‹œ ์ž‘์—…์ด ํŠธ๋žœ์žญ์…˜๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋˜์–ด ํŠธ๋žœ์žญ์…˜ ๋กค๋ฐฑ ์‹œ ์บ์‹œ๋Š” ์—…๋ฐ์ดํŠธ๋œ ์ƒํƒœ๋กœ ๋‚จ์•„์žˆ์–ด์š”.

ํ•ด๊ฒฐ์ฑ…:


// ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ
@Transactional
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    // ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜๋ฉด DB๋Š” ์›๋ž˜๋Œ€๋กœ ๋Œ์•„๊ฐ€์ง€๋งŒ
    // ์บ์‹œ๋Š” ์ด๋ฏธ ์—…๋ฐ์ดํŠธ๋œ ์ƒํƒœ๋กœ ๋‚จ์•„์žˆ์Œ
    return userRepository.save(user);
}

// ํ•ด๊ฒฐ์ฑ… 1: @Caching ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ
@Transactional
public User updateUserInTransaction(User user) {
    return userRepository.save(user);
}

@CachePut(value = "users", key = "#result.id")
public User updateUser(User user) {
    try {
        return updateUserInTransaction(user);
    } catch (Exception e) {
        // ํŠธ๋žœ์žญ์…˜ ์‹คํŒจ ์‹œ ์บ์‹œ ์—…๋ฐ์ดํŠธ ์•ˆ ํ•จ
        throw e;
    }
}

// ํ•ด๊ฒฐ์ฑ… 2: TransactionSynchronizationManager ์‚ฌ์šฉ
@Transactional
public User updateUser(User user) {
    User savedUser = userRepository.save(user);
    
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            // ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ปค๋ฐ‹๋œ ํ›„์—๋งŒ ์บ์‹œ ์—…๋ฐ์ดํŠธ
            cacheManager.getCache("users").put(user.getId(), savedUser);
        }
    });
    
    return savedUser;
}
            

๋ฌธ์ œ 3: ์บ์‹œ๋œ ๊ฐ์ฒด ์ˆ˜์ • ์‹œ ๋ถ€์ž‘์šฉ ๐Ÿ˜ฑ

์ฆ์ƒ:

์บ์‹œ์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ–ˆ๋”๋‹ˆ ๋‹ค๋ฅธ ์š”์ฒญ์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์ณ์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

์บ์‹œ๋Š” ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ์ €์žฅํ•˜๋ฏ€๋กœ, ์บ์‹œ๋œ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์บ์‹œ์˜ ๋‚ด์šฉ๋„ ๋ณ€๊ฒฝ๋ผ์š”.

ํ•ด๊ฒฐ์ฑ…:


// ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ
@Cacheable("products")
public Product getProduct(Long id) {
    return productRepository.findById(id).orElse(null);
}

// ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ
Product product = productService.getProduct(123L);
product.setPrice(product.getPrice() * 0.9);  // ํ• ์ธ ์ ์šฉ
// ์ด์ œ ๋‹ค๋ฅธ ๋ชจ๋“  ์š”์ฒญ๋„ ํ• ์ธ๋œ ๊ฐ€๊ฒฉ์„ ๋ณด๊ฒŒ ๋จ! ๐Ÿ˜ฑ

// ํ•ด๊ฒฐ์ฑ… 1: ๋ฐฉ์–ด์  ๋ณต์‚ฌ
@Cacheable("products")
public Product getProduct(Long id) {
    Product product = productRepository.findById(id).orElse(null);
    if (product != null) {
        // ๋ณต์‚ฌ๋ณธ ๋ฐ˜ํ™˜
        return new Product(product);  // ๋ณต์‚ฌ ์ƒ์„ฑ์ž ์‚ฌ์šฉ
    }
    return null;
}

// ํ•ด๊ฒฐ์ฑ… 2: ๋ถˆ๋ณ€ ๊ฐ์ฒด ์‚ฌ์šฉ
@Cacheable("products")
public ProductDto getProduct(Long id) {
    Product product = productRepository.findById(id).orElse(null);
    if (product != null) {
        // ๋ถˆ๋ณ€ DTO ๋ฐ˜ํ™˜
        return new ProductDto(product);
    }
    return null;
}

// ํ•ด๊ฒฐ์ฑ… 3: Jackson์„ ์‚ฌ์šฉํ•œ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”
@Bean
public CacheManager cacheManager(ObjectMapper objectMapper) {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES));
    
    // Jackson์„ ์‚ฌ์šฉํ•œ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”๋กœ ๊นŠ์€ ๋ณต์‚ฌ ๊ตฌํ˜„
    cacheManager.setAllowNullValues(true);
    cacheManager.setCacheLoader(new CacheLoader<Object, Object>() {
        @Override
        public Object load(Object key) {
            return null;
        }
        
        @Override
        public Object reload(Object key, Object oldValue) {
            try {
                // ๊นŠ์€ ๋ณต์‚ฌ ๊ตฌํ˜„
                String json = objectMapper.writeValueAsString(oldValue);
                return objectMapper.readValue(json, oldValue.getClass());
            } catch (Exception e) {
                return oldValue;
            }
        }
    });
    
    return cacheManager;
}
            

๋ฌธ์ œ 4: ์บ์‹œ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ์ปค์ ธ์š” ๐Ÿ“ˆ

์ฆ์ƒ:

์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๊ณ„์† ์ฆ๊ฐ€ํ•ด์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

์บ์‹œ ํฌ๊ธฐ ์ œํ•œ์ด ์—†๊ฑฐ๋‚˜, ์บ์‹œ ํ•ญ๋ชฉ์ด ๋„ˆ๋ฌด ํฐ ๊ฒฝ์šฐ์˜ˆ์š”.

ํ•ด๊ฒฐ์ฑ…:


// ํ•ด๊ฒฐ์ฑ… 1: ์บ์‹œ ํฌ๊ธฐ ์ œํ•œ
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(1000)  // ์ตœ๋Œ€ 1000๊ฐœ ํ•ญ๋ชฉ์œผ๋กœ ์ œํ•œ
            .expireAfterWrite(1, TimeUnit.HOURS));  // 1์‹œ๊ฐ„ ํ›„ ๋งŒ๋ฃŒ
    return cacheManager;
}

// ํ•ด๊ฒฐ์ฑ… 2: ํฐ ๊ฐ์ฒด๋Š” ๋ณ„๋„ ์ฒ˜๋ฆฌ
@Cacheable(value = "largeObjects", key = "#id", condition = "#result != null && #result.size() < 1024")
public byte[] getLargeObject(String id) {
    // ๊ฒฐ๊ณผ๊ฐ€ ๋„ˆ๋ฌด ํฌ๋ฉด ์บ์‹œํ•˜์ง€ ์•Š์Œ
    return largeObjectRepository.findById(id);
}

// ํ•ด๊ฒฐ์ฑ… 3: ์บ์‹œ ํ†ต๊ณ„ ๋ชจ๋‹ˆํ„ฐ๋ง
@Scheduled(fixedRate = 300000)  // 5๋ถ„๋งˆ๋‹ค ์‹คํ–‰
public void monitorCacheSize() {
    Cache usersCache = cacheManager.getCache("users");
    if (usersCache instanceof CaffeineCache) {
        CaffeineCache caffeineCache = (CaffeineCache) usersCache;
        com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = 
                caffeineCache.getNativeCache();
        
        long estimatedSize = nativeCache.estimatedSize();
        log.info("ํ˜„์žฌ users ์บ์‹œ ํฌ๊ธฐ: {} ํ•ญ๋ชฉ", estimatedSize);
        
        if (estimatedSize > 10000) {
            log.warn("์บ์‹œ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ํฝ๋‹ˆ๋‹ค! ์ผ๋ถ€ ํ•ญ๋ชฉ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.");
            nativeCache.cleanUp();  // ๋งŒ๋ฃŒ๋œ ํ•ญ๋ชฉ ์ •๋ฆฌ
        }
    }
}
            

๋ฌธ์ œ 5: ์บ์‹œ์™€ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๐Ÿ”„

์ฆ์ƒ:

@Async์™€ @Cacheable์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ ์บ์‹œ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

@Async ๋ฉ”์„œ๋“œ๋Š” ๋ณ„๋„์˜ ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋˜๋ฏ€๋กœ ์บ์‹œ ํ”„๋ก์‹œ์™€ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์–ด์š”.

ํ•ด๊ฒฐ์ฑ…:


// ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ
@Async
@Cacheable("asyncData")
public CompletableFuture<Data> getDataAsync(String key) {
    // ๋น„๋™๊ธฐ + ์บ์‹œ ์กฐํ•ฉ์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ
    return CompletableFuture.supplyAsync(() -> dataService.fetchData(key));
}

// ํ•ด๊ฒฐ์ฑ… 1: ๋ฉ”์„œ๋“œ ๋ถ„๋ฆฌ
@Cacheable("data")
public Data getData(String key) {
    return dataService.fetchData(key);
}

@Async
public CompletableFuture<Data> getDataAsync(String key) {
    // ์บ์‹œ ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๋™๊ธฐ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
    return CompletableFuture.completedFuture(getData(key));
}

// ํ•ด๊ฒฐ์ฑ… 2: ์ˆ˜๋™ ์บ์‹œ ๊ด€๋ฆฌ
@Async
public CompletableFuture<Data> getDataAsync(String key) {
    Cache cache = cacheManager.getCache("asyncData");
    Data cachedData = cache.get(key, Data.class);
    
    if (cachedData != null) {
        return CompletableFuture.completedFuture(cachedData);
    }
    
    return CompletableFuture.supplyAsync(() -> {
        Data newData = dataService.fetchData(key);
        cache.put(key, newData);
        return newData;
    });
}
            

๋ฌธ์ œ 6: ์บ์‹œ ์ง๋ ฌํ™” ์˜ค๋ฅ˜ ๐Ÿ“ฆ

์ฆ์ƒ:

Redis ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ "Cannot serialize" ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

์บ์‹œ์— ์ €์žฅํ•˜๋ ค๋Š” ๊ฐ์ฒด๊ฐ€ ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•˜์ง€ ์•Š์•„์š”.

ํ•ด๊ฒฐ์ฑ…:


// ํ•ด๊ฒฐ์ฑ… 1: ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ
@Entity
public class Product implements Serializable {  // Serializable ๊ตฌํ˜„
    private static final long serialVersionUID = 1L;
    
    // ํ•„๋“œ, ๋ฉ”์„œ๋“œ ๋“ฑ
}

// ํ•ด๊ฒฐ์ฑ… 2: JSON ์ง๋ ฌํ™” ์‚ฌ์šฉ (Redis)
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    // Jackson2JsonRedisSerializer ์‚ฌ์šฉ
    Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
    
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    serializer.setObjectMapper(objectMapper);
    
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(serializer))
            .entryTtl(Duration.ofMinutes(30));
                    
    return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .build();
}

// ํ•ด๊ฒฐ์ฑ… 3: DTO ์‚ฌ์šฉ
@Cacheable("products")
public ProductDto getProduct(Long id) {
    Product product = productRepository.findById(id).orElse(null);
    // Entity ๋Œ€์‹  ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ DTO ๋ฐ˜ํ™˜
    return product != null ? new ProductDto(product) : null;
}
            

๋ฌธ์ œ 7: ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์š” ๐Ÿคจ

์ฆ์ƒ:

condition์ด๋‚˜ unless ์†์„ฑ์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

SpEL ํ‘œํ˜„์‹ ์˜ค๋ฅ˜๋‚˜ ์ปจํ…์ŠคํŠธ ๋ฌธ์ œ์ผ ์ˆ˜ ์žˆ์–ด์š”.

ํ•ด๊ฒฐ์ฑ…:


// ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ
@Cacheable(value = "users", key = "#id", condition = "#user.admin")  // โŒ #user ๋ณ€์ˆ˜ ์—†์Œ
public User getUser(Long id) {
    return userRepository.findById(id).orElse(null);
}

// ํ•ด๊ฒฐ์ฑ… 1: ์˜ฌ๋ฐ”๋ฅธ ๋ณ€์ˆ˜ ์ฐธ์กฐ
@Cacheable(value = "users", key = "#id", condition = "#id > 0")  // โœ…
public User getUser(Long id) {
    return userRepository.findById(id).orElse(null);
}

// ํ•ด๊ฒฐ์ฑ… 2: unless ์‚ฌ์šฉ ์‹œ ์ฃผ์˜
@Cacheable(value = "users", key = "#id", unless = "#result == null || #result.admin")
public User getUser(Long id) {
    return userRepository.findById(id).orElse(null);
}

// ํ•ด๊ฒฐ์ฑ… 3: ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ๋กœ๊น… ์ถ”๊ฐ€
@Cacheable(
    value = "users", 
    key = "#id", 
    condition = "#id > 0", 
    unless = "#result == null || #result.admin"
)
public User getUser(Long id) {
    System.out.println("์กฐ๊ฑด ํ‰๊ฐ€: id > 0 = " + (id > 0));
    User user = userRepository.findById(id).orElse(null);
    if (user != null) {
        System.out.println("unless ํ‰๊ฐ€: result.admin = " + user.isAdmin());
    }
    return user;
}
            

๋ฌธ์ œ 8: ์บ์‹œ ๋ฌดํšจํ™”๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์š” ๐Ÿ”„

์ฆ์ƒ:

@CacheEvict๋ฅผ ํ˜ธ์ถœํ–ˆ๋Š”๋ฐ ์บ์‹œ๊ฐ€ ์—ฌ์ „ํžˆ ์ด์ „ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์š”.

๊ฐ€๋Šฅํ•œ ์›์ธ:

์บ์‹œ ํ‚ค๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ์บ์‹œ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ์–ด์š”.

ํ•ด๊ฒฐ์ฑ…:


// ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ
@Cacheable(value = "users", key = "#userId")
public User getUser(Long userId) {
    return userRepository.findById(userId).orElse(null);
}

@CacheEvict(value = "users", key = "#user.id")  // ํ‚ค ๋ถˆ์ผ์น˜ ๊ฐ€๋Šฅ์„ฑ
public void updateUser(User user) {
    userRepository.save(user);
}

// ํ•ด๊ฒฐ์ฑ… 1: ํ‚ค ์ผ๊ด€์„ฑ ์œ ์ง€
@Cacheable(value = "users", key = "#userId")
public User getUser(Long userId) {
    return userRepository.findById(userId).orElse(null);
}

@CacheEvict(value = "users", key = "#user.id")
public void updateUser(User user) {
    // ๋กœ๊น… ์ถ”๊ฐ€
    System.out.println("์บ์‹œ ๋ฌดํšจํ™”: users::" + user.getId());
    userRepository.save(user);
}

// ํ•ด๊ฒฐ์ฑ… 2: ๋ชจ๋“  ํ•ญ๋ชฉ ์ œ๊ฑฐ (ํ™•์‹คํ•œ ๋ฐฉ๋ฒ•)
@CacheEvict(value = "users", allEntries = true)
public void updateUser(User user) {
    userRepository.save(user);
}

// ํ•ด๊ฒฐ์ฑ… 3: ์—ฌ๋Ÿฌ ์บ์‹œ ๋™์‹œ ๋ฌดํšจํ™”
@Caching(evict = {
    @CacheEvict(value = "users", key = "#user.id"),
    @CacheEvict(value = "userProfiles", key = "#user.id"),
    @CacheEvict(value = "userPermissions", key = "#user.id")
})
public void updateUser(User user) {
    userRepository.save(user);
}
            

๐Ÿ’ก ์‹ค๋ฌด ๋””๋ฒ„๊น… ํŒ

์บ์‹œ ๊ด€๋ จ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ๋•Œ ๋„์›€์ด ๋˜๋Š” ํŒ์ด์—์š”:

  1. ์บ์‹œ ๋กœ๊น… ํ™œ์„ฑํ™”: Spring Boot์—์„œ logging.level.org.springframework.cache=TRACE ์„ค์ •
  2. ์บ์‹œ ๋งค๋‹ˆ์ € ์ง์ ‘ ์ ‘๊ทผ: ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ์บ์‹œ ๋‚ด์šฉ ์ง์ ‘ ํ™•์ธ
  3. ์บ์‹œ ๋น„ํ™œ์„ฑํ™” ํ…Œ์ŠคํŠธ: ๋ฌธ์ œ๊ฐ€ ์บ์‹œ ๋•Œ๋ฌธ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ผ์‹œ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”
  4. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ: ์บ์‹œ ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  5. ์บ์‹œ ํ†ต๊ณ„ ๋ชจ๋‹ˆํ„ฐ๋ง: ํžˆํŠธ์œจ, ๋ฏธ์Šค์œจ ๋“ฑ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด ๋ฌธ์ œ ํŒŒ์•…

์‹ค๋ฌด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์„ ๋ฏธ๋ฆฌ ์•Œ๊ณ  ๋Œ€๋น„ํ•œ๋‹ค๋ฉด, ์บ์‹œ ๊ด€๋ จ ์ด์Šˆ๋ฅผ ๋น ๋ฅด๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด์š”. ์žฌ๋Šฅ๋„ท์—์„œ Java ๊ฐœ๋ฐœ์ž๋ฅผ ์ฐพ์„ ๋•Œ, ์ด๋Ÿฐ ์‹ค๋ฌด ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ๊ฐœ๋ฐœ์ž๋ฅผ ์„ ํƒํ•˜๋ฉด ํ”„๋กœ์ ํŠธ๊ฐ€ ํ›จ์”ฌ ์ˆ˜์›”ํ•˜๊ฒŒ ์ง„ํ–‰๋  ๊ฑฐ์˜ˆ์š”! ๐Ÿ› ๏ธ

10. ๋งˆ๋ฌด๋ฆฌ ๋ฐ ์ถ”๊ฐ€ ํ•™์Šต ์ž๋ฃŒ ๐Ÿ“š

์ง€๊ธˆ๊นŒ์ง€ Spring Cache๋ฅผ ํ™œ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ Spring Cache์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๋ถ€ํ„ฐ ์‹ค๋ฌด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ๊นŒ์ง€ ์ „๋ฐ˜์ ์ธ ๋‚ด์šฉ์„ ์ดํ•ดํ•˜์…จ์„ ๊ฑฐ์˜ˆ์š”!

๐Ÿ“ ํ•ต์‹ฌ ์š”์•ฝ

  1. ์บ์‹œ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…: ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ณณ์— ์ €์žฅํ•˜๋Š” ๊ธฐ์ˆ 
  2. Spring Cache ์„ค์ •: ์˜์กด์„ฑ ์ถ”๊ฐ€, @EnableCaching ํ™œ์„ฑํ™”, ์บ์‹œ ๋งค๋‹ˆ์ € ์„ค์ •
  3. ์ฃผ์š” ์–ด๋…ธํ…Œ์ด์…˜: @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig
  4. ์บ์‹œ ๋งค๋‹ˆ์ €: ConcurrentMapCacheManager, CaffeineCacheManager, RedisCacheManager ๋“ฑ
  5. ์บ์‹œ ์ „๋žต: Cache-Aside, Write-Through, Read-Through, Write-Behind, Refresh-Ahead
  6. ์„ฑ๋Šฅ ์ธก์ •: ํžˆํŠธ์œจ, ์‘๋‹ต ์‹œ๊ฐ„, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋“ฑ ๋ชจ๋‹ˆํ„ฐ๋ง
  7. ์ฃผ์˜์‚ฌํ•ญ: ์บ์‹œ ์ผ๊ด€์„ฑ, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰, ์บ์‹œ ํญ๋ฐœ, ์บ์‹œ ์˜ค์—ผ ๋“ฑ
  8. ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค: ์ ์ ˆํ•œ ์บ์‹œ ๋Œ€์ƒ ์„ ํƒ, ์บ์‹œ ๊ณ„์ธตํ™”, ํšจ๊ณผ์ ์ธ ํ‚ค ์„ค๊ณ„ ๋“ฑ
  9. ์‹ค๋ฌด ๋ฌธ์ œ ํ•ด๊ฒฐ: ์บ์‹œ ๋™์ž‘ ๋ฌธ์ œ, ํŠธ๋žœ์žญ์…˜ ์ด์Šˆ, ๊ฐ์ฒด ์ˆ˜์ • ๋ถ€์ž‘์šฉ ๋“ฑ

Spring Cache ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค ๐ŸŽฏ

Spring Cache๊ฐ€ ํŠนํžˆ ์œ ์šฉํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋“ค์ด์—์š”:

  1. ์ฝ๊ธฐ ์œ„์ฃผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: ๋ธ”๋กœ๊ทธ, ๋‰ด์Šค ์‚ฌ์ดํŠธ, ์ œํ’ˆ ์นดํƒˆ๋กœ๊ทธ ๋“ฑ
  2. ๋ฐ˜๋ณต์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ: ์ž์ฃผ ์กฐํšŒ๋˜์ง€๋งŒ ๊ฑฐ์˜ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ
  3. ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ๊ณ„์‚ฐ: ๋ณต์žกํ•œ ํ†ต๊ณ„, ๋ณด๊ณ ์„œ ์ƒ์„ฑ ๋“ฑ
  4. ์™ธ๋ถ€ API ํ˜ธ์ถœ: ์ œ3์ž ์„œ๋น„์Šค ์‘๋‹ต ์บ์‹ฑ
  5. ์„ธ์…˜ ๋ฐ์ดํ„ฐ: ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด, ๊ถŒํ•œ ๋“ฑ
  6. ์„ค์ • ์ •๋ณด: ์‹œ์Šคํ…œ ์„ค์ •, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์†์„ฑ ๋“ฑ

์ถ”๊ฐ€ ํ•™์Šต ์ž๋ฃŒ ๐Ÿ“š

Spring Cache์— ๋Œ€ํ•ด ๋” ๊นŠ์ด ํ•™์Šตํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋‹ค์Œ ์ž๋ฃŒ๋“ค์„ ์ฐธ๊ณ ํ•˜์„ธ์š”:

2. ์ฑ…

  • "Spring in Action" by Craig Walls (์บ์‹ฑ ๊ด€๋ จ ์žฅ)
  • "High-Performance Java Persistence" by Vlad Mihalcea
  • "Spring Boot in Practice" by Somnath Musib

3. ์˜จ๋ผ์ธ ๊ฐ•์˜

  • Udemy: "Spring Framework 5: Beginner to Guru"
  • Pluralsight: "Spring Framework: Spring Data"
  • Baeldung: "Spring Caching"

4. ๋ธ”๋กœ๊ทธ ๋ฐ ํŠœํ† ๋ฆฌ์–ผ

๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋ฐฐ์šธ ๋งŒํ•œ ์ฃผ์ œ๋“ค ๐Ÿš€

Spring Cache๋ฅผ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๋ฉด, ๋‹ค์Œ ์ฃผ์ œ๋“ค๋„ ์‚ดํŽด๋ณด์„ธ์š”:

  1. ๋ถ„์‚ฐ ์บ์‹ฑ: Hazelcast, Infinispan ๋“ฑ์„ ํ™œ์šฉํ•œ ํด๋Ÿฌ์Šคํ„ฐ ์บ์‹ฑ
  2. ๋ฆฌ์•กํ‹ฐ๋ธŒ ์บ์‹ฑ: Spring WebFlux์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฆฌ์•กํ‹ฐ๋ธŒ ์บ์‹ฑ
  3. ์บ์‹œ ๊ณ„์ธตํ™”: ๋‹ค์ค‘ ๋ ˆ๋ฒจ ์บ์‹œ ์•„ํ‚คํ…์ฒ˜ ๊ตฌํ˜„
  4. ์บ์‹œ ํ”„๋ฆฌ์›Œ๋ฐ: ํšจ๊ณผ์ ์ธ ์บ์‹œ ์›Œ๋ฐ์—… ์ „๋žต
  5. ์บ์‹œ ์ผ๊ด€์„ฑ ํŒจํ„ด: ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ์˜ ์บ์‹œ ์ผ๊ด€์„ฑ ์œ ์ง€ ๋ฐฉ๋ฒ•
  6. ์บ์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง: Micrometer, Prometheus, Grafana๋ฅผ ํ™œ์šฉํ•œ ๊ณ ๊ธ‰ ๋ชจ๋‹ˆํ„ฐ๋ง
Spring Cache ํ•™์Šต ๋กœ๋“œ๋งต ๊ธฐ๋ณธ ๊ฐœ๋… ์‹ค์ „ ์ ์šฉ ๊ณ ๊ธ‰ ์ตœ์ ํ™” ๊ธฐ๋ณธ ๊ฐœ๋… - ์บ์‹œ ์ด๋ก  - Spring Cache ์„ค์ • - ๊ธฐ๋ณธ ์–ด๋…ธํ…Œ์ด์…˜ - ์บ์‹œ ๋งค๋‹ˆ์ € ์ข…๋ฅ˜ - SpEL ํ‘œํ˜„์‹ ์‹ค์ „ ์ ์šฉ - ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์ ์šฉ - ์บ์‹œ ์ „๋žต ์„ ํƒ - ์„ฑ๋Šฅ ์ธก์ • - ๋ฌธ์ œ ํ•ด๊ฒฐ - ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ๊ณ ๊ธ‰ ์ตœ์ ํ™” - ๋ถ„์‚ฐ ์บ์‹ฑ - ๋ฆฌ์•กํ‹ฐ๋ธŒ ์บ์‹ฑ - ์บ์‹œ ๊ณ„์ธตํ™” - ๊ณ ๊ธ‰ ๋ชจ๋‹ˆํ„ฐ๋ง - ์„ฑ๋Šฅ ํŠœ๋‹

๋งˆ์น˜๋ฉฐ ๐Ÿ’ญ

Spring Cache๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์˜ˆ์š”. ํ•˜์ง€๋งŒ ๋ชจ๋“  ๋„๊ตฌ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•  ๋•Œ ์ตœ๊ณ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด์š”.

์บ์‹ฑ์€ ํ•ญ์ƒ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๊ฐ€ ์žˆ์–ด์š”. ์†๋„์™€ ๋ฉ”๋ชจ๋ฆฌ, ์ผ๊ด€์„ฑ๊ณผ ์„ฑ๋Šฅ ์‚ฌ์ด์—์„œ ๊ท ํ˜•์„ ์ฐพ๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”. ์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํŠน์„ฑ๊ณผ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๋Š” ์บ์‹ฑ ์ „๋žต์„ ์„ ํƒํ•˜๊ณ , ์ง€์†์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์ตœ์ ํ™”ํ•˜์„ธ์š”.

์ด ๊ธ€์ด ์—ฌ๋Ÿฌ๋ถ„์˜ Spring Cache ์—ฌ์ •์— ๋„์›€์ด ๋˜์—ˆ๊ธฐ๋ฅผ ๋ฐ”๋ผ์š”! ๋” ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋‚˜์€ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜์„ธ์š”. ๐Ÿš€

Java ๊ฐœ๋ฐœ๊ณผ ๊ด€๋ จ๋œ ๋” ๋งŽ์€ ์ •๋ณด์™€ ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ๊ณ  ๊ณ„์‹ ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์„ ๋ฐฉ๋ฌธํ•ด๋ณด์„ธ์š”. ๋‹ค์–‘ํ•œ ๊ฐœ๋ฐœ ์žฌ๋Šฅ์„ ๊ฐ€์ง„ ์ „๋ฌธ๊ฐ€๋“ค์ด ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๋„์™€๋“œ๋ฆด ์ค€๋น„๊ฐ€ ๋˜์–ด ์žˆ์–ด์š”! ๐Ÿ˜Š

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.