aws에 spring boot로 만든 프로젝트를 배포 후에 생각지 못한 현상을 발견하게 되었습니다.
첫번째 응답에서 매우 느린 응답 지연현상을 발견했기 때문입니다.



처음에 이유는 분명 어딘가에서 쿼리 결과를 캐싱하고 있다고 생각했습니다. (이제와서 다시보니 캐싱되었다고 보기에는 3초의 시간은 너무 느리네요ㅎㅎ..)
현재는 따로 쿼리 결과를 Redis에 캐싱하지 않고 있어 어디에서 캐싱을 하고 있는지 알아내려고 했습니다.
1. 데이터베이스(Mysql) 쿼리 캐싱
Mysql 8.0 버전 이상부터는 쿼리 캐싱이 제거되었다고 합니다. 따라서 8.0 버전을 쓰고 있는 저에게는 해당 사항이 없습니다.
2. JPA의 1차 캐싱
Jpa의 1차 캐싱은 같은 트랜잭션 내에서 유효합니다.
API를 여러번 호출 했을 때, 각 호출은 독립적인 트랜잭션 이므로 해당 사항이 없어보입니다.
그러면 결과에 대한 캐싱은 어디에서 하는걸까?
SpringBoot Application을 종료하고 재시작 해보았습니다
그 결과 이전과 동일한 3초대의 응답시간이 걸린것을 확인했습니다.
3. OS 캐시 확인
sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
sudo rm -rf /tmp/*
첫번째 명령어를 사용하여 buff/cache 메모리 항목을 비웁니다.(운영환경에서는 drop_caches를 권장하지 않습니다.)
두번째 명령어를 사용하여 임시파일들을 삭제합니다.
두 명령어를 모두 실행하고 어플리케이션을 재시작 해보았지만 이렇다할 큰 차이는 발견할 수 없었습니다.
4. Class Loader, JVM의 JIT컴파일러, Cold Start
아무래도 캐시의 흔적을 찾을 수 없어 구글링을 하던중 배포후 첫번째 요청에 대한 응답 지연현상은 Class Loader, JVM의 JIT 컴파일러, Cold Start와 연관이 있다는 것을 알게 되었습니다.
첫번째 요청에서는 JVM의 JIT컴파일러가 최적화를 수행하거나 데이터를 메모리에 적재하는 작업등이 수행되며 이로 인해
다소 시간이 더 소요될 수 있고 이후 요청에서는 최적화 작업이 이루어진 상태이기 때문에 처리 속도가 빨라질 수 있다고 합니다.
5. Application Warming up 프로세스 구현하기
Application의 초기화 시점에서 실제 트래픽이 발생하기 전에 주요 엔드포인트를 미리 호출함으로써 성능을 최적화할 수 있도록 합니다.
@Component
@Slf4j
public class WarmUpListener implements ApplicationListener<ApplicationReadyEvent> {
@Autowired
private ServerProperties serverProperties;
@Value("${management.endpoint.warm-up.enable:true}")
private boolean enable;
@Value("${management.endpoint.warm-up.times:5}")
private int times;
//요청 바디 초기화
private static WarmUpRequest body() {
final WarmUpRequest warmUpRequest = new WarmUpRequest();
warmUpRequest.setValidTrue(true);
warmUpRequest.setValidFalse(false);
warmUpRequest.setValidString("warm up");
warmUpRequest.setValidNumber(15);
warmUpRequest.setValidBigDecimal(BigDecimal.TEN);
return warmUpRequest;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (enable && event.getApplicationContext() instanceof ServletWebServerApplicationContext) {
AnnotationConfigServletWebServerApplicationContext context =
(AnnotationConfigServletWebServerApplicationContext) event.getApplicationContext();
String contextPath = serverProperties.getServlet().getContextPath();
int port = serverProperties.getPort() != null && serverProperties.getPort() != 0 ?
serverProperties.getPort() : context.getWebServer().getPort();
if (contextPath == null) {
contextPath = "";
}
final String url = "http://localhost:" + port + contextPath + "/warm-up";
log.info("Starting warm up application. Endpoint: {}, {} times", url, times);
RestTemplate restTemplate = new RestTemplate();
for (int i = 0; i < times; i++) {
ResponseEntity<String> response = restTemplate.postForEntity(url, body(), String.class);
log.debug("Warm up response:{}", response);
}
log.info("Completed warm up application");
}
}
}
application.yml이나 properties파일의 설정을 읽어드려 warm-up 이 true일 시
어플리케이션 초기화 시점에서 요청을 실행하도록 합니다.

'Java > Spring' 카테고리의 다른 글
Spring Interceptor를 활용한 Jwt 유저 정보 추출 (0) | 2024.09.04 |
---|---|
Server Side Rendering 방식의 웹 앱에서 JWT을 사용하는 SpringSecurity 적용의 어려움 (0) | 2024.05.27 |
aws에 spring boot로 만든 프로젝트를 배포 후에 생각지 못한 현상을 발견하게 되었습니다.
첫번째 응답에서 매우 느린 응답 지연현상을 발견했기 때문입니다.



처음에 이유는 분명 어딘가에서 쿼리 결과를 캐싱하고 있다고 생각했습니다. (이제와서 다시보니 캐싱되었다고 보기에는 3초의 시간은 너무 느리네요ㅎㅎ..)
현재는 따로 쿼리 결과를 Redis에 캐싱하지 않고 있어 어디에서 캐싱을 하고 있는지 알아내려고 했습니다.
1. 데이터베이스(Mysql) 쿼리 캐싱
Mysql 8.0 버전 이상부터는 쿼리 캐싱이 제거되었다고 합니다. 따라서 8.0 버전을 쓰고 있는 저에게는 해당 사항이 없습니다.
2. JPA의 1차 캐싱
Jpa의 1차 캐싱은 같은 트랜잭션 내에서 유효합니다.
API를 여러번 호출 했을 때, 각 호출은 독립적인 트랜잭션 이므로 해당 사항이 없어보입니다.
그러면 결과에 대한 캐싱은 어디에서 하는걸까?
SpringBoot Application을 종료하고 재시작 해보았습니다
그 결과 이전과 동일한 3초대의 응답시간이 걸린것을 확인했습니다.
3. OS 캐시 확인
sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
sudo rm -rf /tmp/*
첫번째 명령어를 사용하여 buff/cache 메모리 항목을 비웁니다.(운영환경에서는 drop_caches를 권장하지 않습니다.)
두번째 명령어를 사용하여 임시파일들을 삭제합니다.
두 명령어를 모두 실행하고 어플리케이션을 재시작 해보았지만 이렇다할 큰 차이는 발견할 수 없었습니다.
4. Class Loader, JVM의 JIT컴파일러, Cold Start
아무래도 캐시의 흔적을 찾을 수 없어 구글링을 하던중 배포후 첫번째 요청에 대한 응답 지연현상은 Class Loader, JVM의 JIT 컴파일러, Cold Start와 연관이 있다는 것을 알게 되었습니다.
첫번째 요청에서는 JVM의 JIT컴파일러가 최적화를 수행하거나 데이터를 메모리에 적재하는 작업등이 수행되며 이로 인해
다소 시간이 더 소요될 수 있고 이후 요청에서는 최적화 작업이 이루어진 상태이기 때문에 처리 속도가 빨라질 수 있다고 합니다.
5. Application Warming up 프로세스 구현하기
Application의 초기화 시점에서 실제 트래픽이 발생하기 전에 주요 엔드포인트를 미리 호출함으로써 성능을 최적화할 수 있도록 합니다.
@Component
@Slf4j
public class WarmUpListener implements ApplicationListener<ApplicationReadyEvent> {
@Autowired
private ServerProperties serverProperties;
@Value("${management.endpoint.warm-up.enable:true}")
private boolean enable;
@Value("${management.endpoint.warm-up.times:5}")
private int times;
//요청 바디 초기화
private static WarmUpRequest body() {
final WarmUpRequest warmUpRequest = new WarmUpRequest();
warmUpRequest.setValidTrue(true);
warmUpRequest.setValidFalse(false);
warmUpRequest.setValidString("warm up");
warmUpRequest.setValidNumber(15);
warmUpRequest.setValidBigDecimal(BigDecimal.TEN);
return warmUpRequest;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (enable && event.getApplicationContext() instanceof ServletWebServerApplicationContext) {
AnnotationConfigServletWebServerApplicationContext context =
(AnnotationConfigServletWebServerApplicationContext) event.getApplicationContext();
String contextPath = serverProperties.getServlet().getContextPath();
int port = serverProperties.getPort() != null && serverProperties.getPort() != 0 ?
serverProperties.getPort() : context.getWebServer().getPort();
if (contextPath == null) {
contextPath = "";
}
final String url = "http://localhost:" + port + contextPath + "/warm-up";
log.info("Starting warm up application. Endpoint: {}, {} times", url, times);
RestTemplate restTemplate = new RestTemplate();
for (int i = 0; i < times; i++) {
ResponseEntity<String> response = restTemplate.postForEntity(url, body(), String.class);
log.debug("Warm up response:{}", response);
}
log.info("Completed warm up application");
}
}
}
application.yml이나 properties파일의 설정을 읽어드려 warm-up 이 true일 시
어플리케이션 초기화 시점에서 요청을 실행하도록 합니다.

'Java > Spring' 카테고리의 다른 글
Spring Interceptor를 활용한 Jwt 유저 정보 추출 (0) | 2024.09.04 |
---|---|
Server Side Rendering 방식의 웹 앱에서 JWT을 사용하는 SpringSecurity 적용의 어려움 (0) | 2024.05.27 |