-
[Spring, Spring Boot] 데이터 캐싱(Caching)Spring 2024. 12. 11. 11:15
Spring Boot에서 데이터 캐싱(Caching)은 데이터베이스나 외부 API에서 자주 조회하는 데이터를 임시 저장소에 저장하여 애플리케이션의 성능을 향상시키는 기술입니다. 캐싱을 통해 불필요한 데이터 조회를 줄이고, 응답 속도를 크게 개선할 수 있습니다.
Spring Boot에서 캐싱 구현 방법
캐싱 활성화
- Spring Boot 애플리케이션 클래스에 @EnableCaching 어노테이션을 추가하여 캐싱 기능을 활성화합니다.
@SpringBootApplication @EnableCaching // ---> 캐싱 사용 public class CachingApplication { public static void main(String[] args) { SpringApplication.run(CachingApplication.class, args); } }
캐시 저장소 설정
- Spring Boot는 기본적으로 ConcurrentHashMap을 캐시 저장소로 사용하지만, Redis, Ehcache, Caffeine 등의 외부 캐시를 통합할 수도 있습니다.
캐싱 어노테이션 사용
Spring에서 제공하는 캐싱 관련 어노테이션을 사용하여 캐시를 쉽게 구현할 수 있습니다:- @Cacheable: 캐시에서 데이터를 조회하거나, 없으면 메서드를 실행하고 결과를 캐시에 저장합니다.
@Service public class ProductService { @Cacheable("products") public Product getProductById(Long id) { // 데이터베이스 조회 return productRepository.findById(id).orElseThrow(); } }
- @CachePut: 메서드를 실행하고, 결과를 캐시에 저장하거나 업데이트합니다.
@CachePut(value = "products", key = "#product.id") public Product updateProduct(Product product) { return productRepository.save(product); }
- @Caching: 여러 캐싱 규칙을 조합하여 사용합니다.
// 캐시 무효화(Cache Eviction) @Caching(evict = { @CacheEvict(value = "products", key = "#id"), @CacheEvict(value = "categories", allEntries = true) }) public void deleteProduct(Long id) { productRepository.deleteById(id); } /** @CacheEvict (첫 번째): value = "products": products라는 이름의 캐시에서 무효화 작업을 수행합니다. key = "#id": 삭제할 캐시 항목의 키를 메서드의 매개변수 id 값으로 지정합니다. 예: deleteProduct(5L)을 호출하면 products 캐시에서 키가 5L인 항목이 삭제됩니다. */ /** @CacheEvict (두 번째): value = "categories": categories라는 이름의 캐시에서 무효화 작업을 수행합니다. allEntries = true: 캐시에 저장된 모든 항목을 삭제합니다. 특정 키가 아니라, categories 캐시 전체를 비웁니다. */
캐싱의 장점
- 응답 속도 향상:
- 반복적인 데이터 조회를 줄여 요청 처리 속도를 크게 개선.
- 데이터베이스 부하 감소:
- 데이터베이스나 외부 API 호출을 줄여 리소스 사용을 최적화.
- 비용 절감:
- 대규모 트래픽 환경에서 캐싱을 활용하면 비용을 줄일 수 있음.
캐싱의 단점
- 데이터 불일치(Inconsistency):
- 캐시 데이터와 원본 데이터가 일치하지 않을 수 있음. 이를 해결하기 위해 캐시 갱신 전략 필요.
- 메모리 사용량 증가:
- 캐싱 저장소가 제한된 메모리를 소비하므로 효율적인 관리가 필요.
- 복잡성 증가:
- 캐싱 전략과 관리가 잘못되면 성능 저하나 예기치 않은 문제가 발생할 수 있음.
@CacheEvict와 @CachePut의 비교
@CacheEvict와 @CachePut는 Spring의 캐싱 관리에서 사용하는 두 가지 주요 어노테이션입니다. 각각의 동작 방식과 활용 목적, 장단점을 비교해보겠습니다.
@CacheEvict
동작 방식:
- 캐시에서 데이터를 삭제(무효화)합니다.
- 데이터를 삭제하여 캐시에 더 이상 사용할 수 없도록 만듭니다.
장점:
- 불필요한 데이터 제거:
- 더 이상 유효하지 않은 데이터를 캐시에서 완전히 삭제하므로, 잘못된 데이터가 참조되는 것을 방지합니다.
- 단순성:
- 캐시의 특정 항목이나 전체 항목을 간단히 삭제할 수 있습니다.
- 명시적 무효화:
- 데이터베이스에서 삭제된 데이터에 대해 캐시 항목을 정확히 제거할 수 있습니다.
단점:
- 재조회 필요:
- 삭제된 항목을 다시 사용할 경우, 데이터베이스에서 새로 조회하여 캐시에 저장해야 하므로 추가적인 처리 비용이 발생할 수 있습니다.
- 캐시 효율성 저하:
- 캐시가 비어 있는 경우 데이터베이스에 대한 호출 빈도가 증가할 수 있습니다.
@CachePut
동작 방식:
- 캐시를 무효화하지 않고, 캐시에 데이터를 업데이트합니다.
- 메서드가 실행된 결과를 캐시에 저장하며, 기존 데이터가 있으면 덮어씁니다.
장점:
- 항상 최신 데이터 유지:
- 데이터베이스와 캐시를 동시에 최신 상태로 유지할 수 있습니다.
- 캐시 효율성 유지:
- 캐시 데이터를 무효화하지 않고, 필요한 데이터를 덮어쓰므로 데이터베이스 호출을 줄일 수 있습니다.
- 명시적 업데이트:
- 특정 데이터에 대한 업데이트만 수행할 수 있어 캐시 효율성이 높습니다.
단점:
- 추가 실행 비용:
- 메서드가 항상 실행되고 그 결과가 캐시에 저장되므로, 단순한 데이터 조회보다 비용이 높을 수 있습니다.
- 데이터 불일치 가능성:
- 캐시 업데이트가 데이터베이스 상태와 항상 일치하지 않을 수 있습니다(예: 데이터베이스 상태가 외부 요인으로 변경된 경우).
특징 @CacheEvict @CachePut 사용 목적 더 이상 유효하지 않은
데이터를 캐시에서 제거.항상 실행하여 캐시 데이터를 업데이트. 적합한 경우 - 데이터가 삭제되었거나 더 이상 사용되지 않을 때. - 데이터가 업데이트되었고, 캐시와 데이터베이스를 동기화해야 할 때. 캐시 상태 캐시 항목 제거(무효화). 캐시에 새 데이터로 덮어씀. 성능 고려 캐시를 제거하므로 필요 시 재조회 비용 발생. 캐시 데이터를 유지하므로 데이터베이스 호출 감소 가능. 주요 단점 재조회 시 데이터베이스 호출이 필요해 성능 저하 가능. 메서드가 항상 실행되므로 필요하지 않을 때에도 비용 발생 가능. 캐시 동적 사용
Spring의 @CacheEvict 어노테이션에서 condition과 unless 속성을 사용하면 동적으로 캐시 무효화를 제어할 수 있습니다. 이를 활용하여 특정 조건에서만 캐시를 무효화하거나, 특정 조건에서는 캐시를 유지하도록 구현할 수 있습니다
속성 설명
- condition:
- 캐시 무효화 작업이 수행되기 전에 평가됩니다.
- 표현식이 true를 반환하면 캐시 무효화를 수행하고, false를 반환하면 무효화하지 않습니다.
- unless:
- 캐시 무효화 작업이 수행된 후에 평가됩니다.
- 표현식이 true를 반환하면 무효화 작업을 취소하고, false를 반환하면 무효화를 유지합니다.
@CacheEvict(value = "products", key = "#id", condition = "#id > 10") public void deleteProduct(Long id) { productRepository.deleteById(id); } /** 설명: id 값이 10보다 큰 경우에만 캐시에서 항목을 제거합니다. id <= 10인 경우 캐시는 변경되지 않습니다. */
@CacheEvict(value = "products", key = "#id", unless = "#result == null") public void deleteProduct(Long id) { Product product = productRepository.findById(id).orElse(null); if (product != null) { productRepository.delete(product); } } /** 설명: deleteProduct 메서드 실행 후 반환된 #result가 null이면 캐시를 무효화하지 않습니다. 삭제할 대상이 존재하지 않을 때 캐시를 유지합니다. */
@CacheEvict(value = "products", key = "#id", condition = "#id > 10", unless = "#result == null") public boolean deleteProduct(Long id) { Product product = productRepository.findById(id).orElse(null); if (product != null) { productRepository.delete(product); return true; } return false; } /** 설명: condition: id > 10인 경우에만 캐시 무효화 조건을 진행합니다. unless: 메서드 실행 결과가 null이면 캐시를 무효화하지 않습니다. */
활용 시 주의사항
- SpEL 표현식 사용:
- condition과 unless는 Spring Expression Language(SpEL)를 사용합니다.
- 파라미터나 메서드 결과 값을 기준으로 조건을 지정해야 합니다.
- 예: #id, #root.args[0], #result.
- 성능 고려:
- 조건이 너무 복잡하면 캐싱의 성능 이점을 상쇄할 수 있으므로 간단한 조건을 사용하는 것이 좋습니다.
- 결과 의존성:
- unless는 메서드 결과에 의존하므로, 반환 타입이 적절히 정의되어 있어야 합니다.
- 반환 값이 없는 경우(void)에서는 사용할 수 없습니다.
- 동작 순서:
- condition은 캐시 무효화 실행 여부를 결정하기 전에 평가됩니다.
- unless는 캐시 무효화가 완료된 후 조건을 평가하여 작업을 취소할 수 있습니다.
'Spring' 카테고리의 다른 글
[Spring Boot] RESTful API 설계 원칙 (0) 2024.12.02 [Spring, Spring Boot] 비동기 처리 TaskExecutor (0) 2024.11.28 [Spring, Spring Boot] 비동기 처리 Asynchronous Processing (0) 2024.11.28 [Spring, Spring Boot] 관점 지향 프로그래밍 AOP (Aspect-Oriented Programming) (0) 2024.11.28 [Spring, Spring Boot] 의존성 주입 (Dependency Injection, DI) (0) 2024.11.28