์ƒ์„ธ ์ปจํ…์ธ 

๋ณธ๋ฌธ ์ œ๋ชฉ

Spring Boot์—์„œ Redis ์บ์‹œ ์ ์šฉ ์ „๋žต๊ณผ ์‹ค์ „ ๊ตฌํ˜„: ์บ์‹œ ํžˆํŠธ์œจ 90% ๋‹ฌ์„ฑํ•˜๊ธฐ

Developer/Backend

by ์›ฐํฌ 2025. 5. 29. 09:22

๋ณธ๋ฌธ

๐Ÿš€ Spring Boot์—์„œ Redis ์บ์‹œ ์ ์šฉ ์ „๋žต๊ณผ ์‹ค์ „ ๊ตฌํ˜„

- ์บ์‹œ ํžˆํŠธ์œจ 90% ๋‹ฌ์„ฑํ•˜๊ธฐ

API ์‘๋‹ต ์†๋„๊ฐ€ ๋А๋ฆฌ๊ฑฐ๋‚˜ DB ๋ถ€ํ•˜๊ฐ€ ๋†’์„ ๋•Œ ๊ฐ€์žฅ ๋จผ์ € ๊ณ ๋ ค๋˜๋Š” ํ•ด๊ฒฐ์ฑ… ์ค‘ ํ•˜๋‚˜๋Š” ์บ์‹œ(Cache)์ž…๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๋‹จ์ˆœํžˆ Redis๋ฅผ ๋ถ™์˜€๋‹ค๊ณ  ํ•ด์„œ ์„ฑ๋Šฅ์ด ๋ฌด์กฐ๊ฑด ์ข‹์•„์ง€์ง„ ์•Š์Šต๋‹ˆ๋‹ค.
์บ์‹œ ํžˆํŠธ์œจ(Cache Hit Rate)์ด ๋‚ฎ์œผ๋ฉด Redis๊ฐ€ ์žˆ์–ด๋„ ํšจ๊ณผ๋Š” ๋ฏธ๋ฏธํ•˜์ฃ .

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๋‹ค์Œ ๋‚ด์šฉ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค:

  • ์บ์‹œ ํžˆํŠธ์œจ์˜ ๊ฐœ๋…๊ณผ ๊ณ„์‚ฐ ๋ฐฉ๋ฒ•
  • DB์™€ ์บ์‹œ ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ ๋ฌธ์ œ ํ•ด๊ฒฐ ์ „๋žต
  • ์‹ค์ „ ๊ณ„์ธต ๊ตฌ์กฐ์—์„œ์˜ ์บ์‹œ ์ ์šฉ ์˜ˆ์‹œ
  • ์บ์‹œ ํžˆํŠธ์œจ์„ ๋†’์ด๊ธฐ ์œ„ํ•œ ์‹ค์ „ ํŒ
  • @Cacheable, @CacheEvict ์‹ค์ „ ์ฝ”๋“œ

 

โœ… ์บ์‹œ๋Š” ์–ด๋””์— ์ ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์„๊นŒ? Controller vs Service

์บ์‹œ๋Š” Controller์— ์ง์ ‘ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ , Service ๊ณ„์ธต์— ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๊ฐ ๊ณ„์ธต์˜ ์ฑ…์ž„(SOC: Separation of Concerns)์„ ๊ณ ๋ คํ•˜๋ฉด Service ๊ณ„์ธต์ด ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋” ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“Œ Controller์— ์บ์‹œ๋ฅผ ์ ์šฉํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ…

ํ•ญ๋ชฉ Controller์—์„œ ์บ์‹œ ์ ์šฉ ์‹œ
โœ… ์žฅ์  ๋น ๋ฅธ ์‘๋‹ต ๊ฐ€๋Šฅ (์บ์‹œ → ๋ฐ”๋กœ ๋ฆฌํ„ด)
๋‹จ์ˆœ JSON ์บ์‹œ์— ์œ ๋ฆฌ
โŒ ๋‹จ์  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ค‘๋ณต
๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ ์นจํ•ด
ํ…Œ์ŠคํŠธ ์–ด๋ ค์›€
์žฌ์‚ฌ์šฉ์„ฑ ๋‚ฎ์Œ

 

Controller ์บ์‹œ ์ ์šฉ ์˜ˆ์‹œ (๋น„์ถ”์ฒœ):

@GetMapping("/api/products/{id}")
public ResponseEntity<ProductDto> getProduct(@PathVariable Long id) {
    String key = "product:" + id;
    String cached = redisTemplate.opsForValue().get(key);
    if (cached != null) {
        return ResponseEntity.ok(objectMapper.readValue(cached, ProductDto.class));
    }

    ProductDto product = productService.getProductById(id);
    redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(product), 10, TimeUnit.MINUTES);
    return ResponseEntity.ok(product);
}

 

โœ… ๊ทธ๋ž˜์„œ ๋Œ€๋ถ€๋ถ„์€ Service ๊ณ„์ธต์— ์ ์šฉ

Service๋Š” ๋„๋ฉ”์ธ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณ„์ธต์ด๊ธฐ ๋•Œ๋ฌธ์— ์บ์‹œ ์กฐ๊ฑด/๋ฌดํšจํ™” ๋“ฑ์˜ ์ •์ฑ…์„ ๋” ์œ ์—ฐํ•˜๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ, ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์žฌ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ์ผ๊ด€์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.

 

Service ์บ์‹œ ์ ์šฉ ์˜ˆ์‹œ (์ถ”์ฒœ):

@Cacheable(value = "productCache", key = "#id")
public ProductDto getProductById(Long id) {
    Product product = productRepository.findById(id)
        .orElseThrow(() -> new NotFoundException("Product not found"));
    return new ProductDto(product);
}

@CacheEvict(value = "productCache", key = "#productDto.id")
public void updateProduct(ProductDto productDto) {
    Product product = productRepository.findById(productDto.getId())
        .orElseThrow(() -> new NotFoundException("Product not found"));
    product.setName(productDto.getName());
    product.setPrice(productDto.getPrice());
    productRepository.save(product);
}

๐Ÿง  ์–ธ์ œ Controller ์บ์‹œ๋„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์„๊นŒ?

  • ์ •์  JSON ์‘๋‹ต (ex. ์ธ๊ธฐ ์ƒํ’ˆ, ๋žญํ‚น ๋“ฑ)
  • TTL์ด ๋ช…ํ™•ํ•˜๊ณ , ์‚ฌ์šฉ์ž ์ธ์ฆ๊ณผ ๋ฌด๊ด€ํ•œ ๊ฒฝ์šฐ

Controller ์บ์‹œ ์‚ฌ์šฉ ์˜ˆ์‹œ:

@GetMapping("/api/ranking")  
public ResponseEntity<List> getDailyRanking() {  
    String key = "ranking:daily";  
    String cached = redisTemplate.opsForValue().get(key);  
    if (cached != null) {  
    	return ResponseEntity.ok(objectMapper.readValue(cached, new TypeReference<List>() {}));  
	}

    List<ProductDto> ranking = productService.getDailyRanking();
    redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(ranking), 1, TimeUnit.HOURS);
    return ResponseEntity.ok(ranking);
}

 

๐Ÿงฉ โœ๏ธ ๋‚ด ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์™€ ์บ์‹œ ์ ์šฉ ์œ„์น˜

์ œ๊ฐ€ ๊ฐœ๋ฐœ ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ณ„์ธต ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  Controller → Application Service → Database Service → DB

๐Ÿ’ก ๊ทธ๋ž˜์„œ ์บ์‹œ๋Š” ์–ด๋””์— ๋„ฃ์—ˆ๋‚˜?

์ €๋Š” Database Service ๊ณ„์ธต์— ์บ์‹œ๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

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

 

โœ… ์‹ค์ „ ์ ์šฉ: Database Service ๊ณ„์ธต ์บ์‹œ ์ฝ”๋“œ

@Service
public class UserDatabaseService {

    @Cacheable(value = "userCache", key = "#userId", unless = "#result == null")
    public UserDto findUserById(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new NotFoundException("User not found"));
        return new UserDto(user);
    }

    @CacheEvict(value = "userCache", key = "#userDto.id")
    public void updateUser(UserDto userDto) {
        User user = userRepository.findById(userDto.getId())
            .orElseThrow(() -> new NotFoundException("User not found"));
        user.setName(userDto.getName());
        user.setEmail(userDto.getEmail());
        userRepository.save(user);
    }
}

๐Ÿ”ง RedisCacheManager ์„ค์ • (TTL ํฌํ•จ)

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10))
        .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

    return RedisCacheManager.builder(connectionFactory)
        .cacheDefaults(config)
        .build();
}

 

๐Ÿง  ์บ์‹œ ํžˆํŠธ์œจ์ด๋ž€?

์บ์‹œ ํžˆํŠธ์œจ = (์บ์‹œ ํžˆํŠธ ์ˆ˜ / ์ „์ฒด ์š”์ฒญ ์ˆ˜) × 100

Redis CLI์—์„œ ํ™•์ธ:

127.0.0.1:6379> INFO stats
keyspace_hits:158920
keyspace_misses:4180

ํžˆํŠธ์œจ = 158920 / (158920 + 4180) ≈ 97.4%

 


โœ… ์บ์‹œ ํžˆํŠธ์œจ์„ ๋†’์ด๊ธฐ ์œ„ํ•œ ์ „๋žต

1. ์ผ๊ด€๋œ ํ‚ค ์„ค๊ณ„

@Cacheable(value = "userCache", key = "'user:' + #userId")

2. ์ธ๊ธฐ ๋ฐ์ดํ„ฐ ํ”„๋ฆฌํžˆํŠธ (Pre-warming)

@PostConstruct
public void preloadPopularItems() {
    List<Product> top = productRepository.findTop10ByOrderBySalesDesc();
    for (Product p : top) {
        redisTemplate.opsForValue().set("product:" + p.getId(), objectMapper.writeValueAsString(p), 1, TimeUnit.HOURS);
    }
}

3. Cache Stampede ๋ฐฉ์ง€

@Cacheable(value = "userCache", key = "#userId", sync = true)

4. TTL์„ ์ƒํ™ฉ๋ณ„๋กœ ์ฐจ๋“ฑ ์ ์šฉ

redisTemplate.opsForValue().set("user:active:123", jsonData, 5, TimeUnit.MINUTES);
redisTemplate.opsForValue().set("user:archived:123", jsonData, 60, TimeUnit.MINUTES);

 

๐Ÿงฉ ๋งˆ๋ฌด๋ฆฌ: ์บ์‹œ๋Š” ๋‹จ์ˆœํ•œ “์†๋„”๊ฐ€ ์•„๋‹ˆ๋‹ค

Redis ์บ์‹œ๋Š” ๋‹จ์ˆœํžˆ "๋น ๋ฅด๊ฒŒ ์‘๋‹ตํ•˜๊ธฐ" ์œ„ํ•œ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ,
์–ด๋–ป๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ๊ด€๋ฆฌํ• ์ง€์— ๋Œ€ํ•œ ์•„ํ‚คํ…์ฒ˜ ์ „๋žต์ž…๋‹ˆ๋‹ค.


โœ… ์บ์‹œ๊ฐ€ ํšจ๊ณผ์ ์ธ ์ƒํ™ฉ

์ƒํ™ฉ ์ด์œ 
๋™์ผํ•œ ์š”์ฒญ์ด ์ž์ฃผ ๋ฐ˜๋ณต๋  ๋•Œ ์‘๋‹ต์„ ๊ทธ๋Œ€๋กœ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ → ํžˆํŠธ์œจ ↑
DB I/O ๋น„์šฉ์ด ํฌ๊ฑฐ๋‚˜ ํŠธ๋ž˜ํ”ฝ์ด ๊ธ‰์ฆํ•  ๋•Œ ๋ถ€ํ•˜ ๋ถ„์‚ฐ
๋ฐ์ดํ„ฐ๊ฐ€ ์ž์ฃผ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์ •์  ์ฝ˜ํ…์ธ ๋กœ ์บ์‹ฑ ์•ˆ์ •์ 
์™ธ๋ถ€ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋•Œ ์‘๋‹ต ์†๋„ + ๋น„์šฉ ์ ˆ๊ฐ

 

์˜ˆ์‹œ

  • ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ •๋ณด (์กฐํšŒ ๋นˆ๋„ ๋†’๊ณ , ๋ณ€๊ฒฝ์€ ๋“œ๋ฌพ)
  • ์ธ๊ธฐ ์ƒํ’ˆ ๋ชฉ๋ก, ๋žญํ‚น ๋ฆฌ์ŠคํŠธ
  • ์ง€์—ญ ๊ธฐ๋ฐ˜ ์ถ”์ฒœ, ํ™˜์œจ/๋‚ ์”จ ๋ฐ์ดํ„ฐ
  • ๋กœ๊ทธ์ธ ํ›„ ์‚ฌ์šฉ์ž ๊ถŒํ•œ ์ •๋ณด

โŒ ์บ์‹œ๋ฅผ ํ”ผํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ

์ƒํ™ฉ ์ด์œ 
๋ฐ์ดํ„ฐ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ์บ์‹œ ๋ฌดํšจํ™” ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•˜๊ณ  ์œ„ํ—˜
์‹ค์‹œ๊ฐ„์„ฑ์ด ๋งค์šฐ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ ์บ์‹œ๋œ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์น˜๋ช…์ ์ผ ์ˆ˜ ์žˆ์Œ
ํŠธ๋žœ์žญ์…˜/์ผ๊ด€์„ฑ์ด ์ค‘์š”ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์บ์‹œ๋ณด๋‹ค DB ์ •ํ™•์„ฑ์ด ์šฐ์„ 

์˜ˆ์‹œ

  • ์€ํ–‰ ๊ณ„์ขŒ ์ž”์•ก, ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
  • ์žฌ๊ณ  ์ˆ˜๋Ÿ‰, ์‹ค์‹œ๊ฐ„ ์˜ˆ์•ฝ ์ฒ˜๋ฆฌ
  • ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„
  • ๋ฏผ๊ฐํ•œ ๊ฐœ์ธ์ •๋ณด ๊ด€๋ จ API

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

  • “๋ฌด์กฐ๊ฑด ์บ์‹ฑ”์€ ์•ˆ ๋œ๋‹ค.
  • “์ž์ฃผ ์กฐํšŒ๋˜์ง€๋งŒ ์ž˜ ์•ˆ ๋ฐ”๋€Œ๋Š” ๋ฐ์ดํ„ฐ”๊ฐ€ ์บ์‹œ ๋Œ€์ƒ์˜ ์šฐ์„ ์ˆœ์œ„.
  • “๋ณ€๊ฒฝ์ด ์ž์ฃผ ๋˜๊ฑฐ๋‚˜, ์ž˜๋ชป๋œ ์ •๋ณด๊ฐ€ ์œ„ํ—˜ํ•œ ๋ฐ์ดํ„ฐ”๋Š” ์บ์‹œ๋ณด๋‹ค DB ์šฐ์„ .
๋ฐ˜์‘ํ˜•

๊ด€๋ จ๊ธ€ ๋”๋ณด๊ธฐ