이 글에서는 시스템 실행 중 성능을 향상시키기 위해 데이터를 캐싱하는 몇 가지 기술을 살펴보겠습니다. 일반적으로 계산하는 데 시간이 오래 걸리는 값을 캐싱하면 나중에 액세스할 때 시간을 절약할 수 있습니다. 캐싱되는 값은 자주 변경되지 않거나, 최신 버전이 필요하지 않은 경우가 많습니다.
Spring Boot에서 캐싱
Spring Boot REST API에서 캐싱을 활성화하는 가장 간단한 방법은 애플리케이션에 설정하고, 엔드포인트에 @Cacheable
어노테이션을 사용하는 것입니다.
애플리케이션에서 다음과 같이 @EnableCaching
어노테이션을 추가하기만 하면 됩니다.
@SpringBootApplication
@EnableCaching
public class SpringBootCachingApplication {
public static void main(final String[] args) {
SpringApplication.run(SpringBootCachingApplication.class, args);
}
}
컨트롤러에서, 캐싱하고자 하는 메서드에 @Cacheable
어노테이션을 추가합니다.
/v1/status
API는 캐싱이 설정되지 않았으므로, 호출할 때마다 실제 메서드가 실행됩니다./v2/status
API는 캐싱이 설정되어 있으므로, 첫 번째 호출만 메서드를 실행하고 이후 호출에서는 캐싱된 결과를 반환합니다./v3/status
API는 캐싱 설정에 매개변수를 포함하여, 캐시가 매개변수에 따라 결과를 다르게 저장하도록 합니다. 즉, 이전에 사용된 매개변수로 호출하면 캐싱된 결과를 사용하고, 그렇지 않으면 메서드를 실행합니다.
@RestController
@Log4j2
public class CacheController {
@GetMapping("/v1/status")
public ResponseEntity<String> statusV1() {
log.info("status V1 called");
// 약간의 무거운 처리
return ResponseEntity.ok("DONE");
}
@GetMapping("/v2/status")
@Cacheable("status")
public ResponseEntity<String> statusV2() {
log.info("status V2 called");
// 약간의 무거운 처리
return ResponseEntity.ok("DONE");
}
@GetMapping("/v3/status/{param}")
@Cacheable("status")
public ResponseEntity<String> statusV3(@PathVariable final int param) {
log.info("status V3 called");
// 약간의 무거운 처리
return ResponseEntity.ok(param == 1 ? "DONE" : "IN PROCESS");
}
}
JPA 캐싱
때로는 API의 결과를 캐싱할 수 없지만, 쿼리의 결과를 캐싱함으로써 동일한 쿼리를 여러 번 실행하는 것을 피할 수 있습니다. JPA와 Spring Boot는 이를 스마트하게 처리할 수 있습니다.
엔티티를 정의해보겠습니다.
@Entity
@Getter
@Setter
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
그런 다음, 리포지토리에서 @Cacheable
어노테이션을 추가하여 결과를 캐시에 저장할 메서드를 지정합니다. findById
와 같은 "standard" 메서드를 캐싱해야 하는 경우, 이를 오버라이드하고 어노테이션을 추가할 수 있습니다.
@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
@Override
@Cacheable("customers")
Optional<Customer> findById(Long aLong);
@Cacheable("customers")
List<Customer> findByName(String name);
}
Caffeine
일부 경우에는 REST 엔드포인트나 JPA 레벨에서 캐시를 사용할 수 없는 경우가 있습니다. 이때는 캐시를 수동으로 구현해야 하며, 이를 위해 Caffeine 라이브러리를 사용할 수 있습니다.
Caffeine은 캐시에 저장할 요소의 최대 개수를 정의하고 만료 조건을 설정할 수 있습니다.
프로젝트에 다음 의존성을 추가해야 합니다.
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
사용법은 매우 직관적입니다:
- 키와 값을 사용해 캐시를 정의합니다.
- 캐시의 최대 크기를 설정할 수 있습니다.
- 만료 기간을 설정할 수 있습니다.
- build 메서드에서 캐시 미스가 발생했을 때 캐시를 채우는 방법을 정의합니다.
@Service
@RequiredArgsConstructor
public class CustomerService {
private final LoadingCache<Long, String> customerCache =
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.refreshAfterWrite(Duration.ofMinutes(1))
.build(this::findCustomerName);
private final CustomerRepository customerRepository;
private String findCustomerName(final Long key) {
return customerRepository.findById(key).map(Customer::getName).orElse("");
}
public String getCustomerName(final Long id) {
return customerCache.get(id);
}
}
getCustomerName
을 호출하면, 이 메서드는 캐시 정의에 따라 실행됩니다. 먼저, 해당 키와 일치하는 항목이 있는지 확인하고, 그렇지 않으면 findCustomerName
메서드를 호출해 캐시를 채웁니다.
'Backend > Spring' 카테고리의 다른 글
Spring Boot에서 Lombok을 활용한 효율적인 Java 개발 가이드 🌟 (0) | 2024.12.02 |
---|---|
Spring Boot에서 API 응답을 구조화하는 가장 좋은 방법 (0) | 2024.11.24 |
[Spring Boot] 대용량 데이터 쿼리 REST 엔드포인트 처리 (3) | 2024.11.10 |
[Spring Boot] MultipartFile transferTo() 사용 파일 저장시 주의사항 (0) | 2023.01.10 |
[Spring Boot] MultipartFile + Modal 사용 파일 업로드 (0) | 2023.01.06 |
IT 기술과 개발 내용을 포스팅하는 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!