Spring Boot · 2026-05-14

Spring Boot에서 ETag와 Last-Modified 활용

Spring Boot에서 ETag와 Last-Modified를 이용한 HTTP 응답 캐싱의 개념, 설정 방법, 구현 예제와 운영 시 고려사항을 정리한 기술 정리

작성일 : 2026-05-14 ㆍ 작성자 : 관리자
post
목차

개요

API 서버에서 불필요한 데이터 전송을 줄이면 응답 속도와 대역폭 사용량이 개선된다. HTTP의 조건부 요청(Conditional Request)을 활용하면 클라이언트가 이미 보유한 리소스와 비교해 변경된 경우에만 본문을 전송한다. 여기서는 Spring Boot 환경에서 ETag와 Last-Modified를 적용해 response caching을 구현하는 방법을 실제 코드 예제와 함께 설명한다.

HTTP 조건부 요청 핵심 개념

ETag

ETag는 리소스의 현재 버전을 식별하는 토큰이다. 서버는 응답에 ETag 헤더를 달고, 클라이언트는 이후 요청에서 If-None-Match 헤더로 보관한 ETag를 보낸다. 서버가 동일하다고 판단하면 304 Not Modified를 반환해 본문 전송을 생략한다.

Last-Modified

Last-Modified는 리소스의 최종 수정 시각이다. 클라이언트는 If-Modified-Since 헤더에 마지막으로 받은 시간을 보내고, 서버는 비교해 변경 없으면 304를 반환한다. ETag와 병행해 사용하면 더 정확한 캐싱 제어가 가능하다.

Spring Boot에서의 기본 접근

Spring에서는 두 가지 수준에서 지원이 가능하다.

  • 필터 수준: ShallowEtagHeaderFilter를 사용해 자동으로 ETag 헤더를 생성
  • 컨트롤러 수준: ResponseEntity의 eTag(), lastModified(), WebRequest.checkNotModified() 등을 직접 사용

1. ShallowEtagHeaderFilter로 빠르게 적용

작업이 적고 대부분의 경우 유용하다. 단, 동적 콘텐츠나 스트리밍 응답에서는 부작용이 발생할 수 있으니 주의한다.

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;

import javax.servlet.Filter;

@Configuration
public class WebConfig {

    @Bean
    public Filter shallowEtagHeaderFilter() {
        return new ShallowEtagHeaderFilter();
    }
}

2. 컨트롤러에서 조건부 응답 직접 처리

리소스 버전 관리가 명확하거나 DB에서 변경 시각을 알고 있을 때 적합하다. 직접 ETag와 Last-Modified를 설정하면 불필요한 바이트 전송을 더 정확히 줄일 수 있다.

예제: ETag와 Last-Modified 동시 처리

package com.example.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.servlet.http.HttpServletRequest;

@RestController
public class ItemController {

    @GetMapping("/items/1")
    public ResponseEntity<String> getItem(HttpServletRequest request) {
        // 예시: 실제로는 DB의 updatedAt과 콘텐츠 해시를 사용
        long lastModified = 1672531200000L; // UTC millis
        String eTag = "\"v1-abc123\""; // 쌍따옴표 포함 권장

        ServletWebRequest webRequest = new ServletWebRequest(request);

        if (webRequest.checkNotModified(eTag, lastModified)) {
            return ResponseEntity.status(304).build();
        }

        String body = "{\"id\":1,\"name\":\"Item\"}";

        return ResponseEntity.ok()
                .eTag(eTag)
                .lastModified(lastModified)
                .body(body);
    }
}

구현 시 유의사항

  • ETag 생성 방식: 리소스 바이트의 해시(SHA-1 등)나 버전 번호를 사용한다. 연산 비용과 충돌 가능성을 고려한다.
  • ShallowEtagHeaderFilter 한계: 응답 전체를 버퍼링해 해시를 계산하므로 메모리 사용량이 증가할 수 있다. 대용량 응답이나 스트리밍에는 부적절하다.
  • 시간 오차: Last-Modified는 초 단위 정밀도 차이로 오검출이 발생할 수 있다. 가능하면 ETag와 병행 사용 권장.
  • 보안과 캐시 무효화: 인증된 응답이나 사용자별 데이터는 Cache-Control 및 Vary 헤더로 세밀하게 제어한다.

Response Caching 관련 HTTP 헤더 설계

ETag/Last-Modified 외에도 다음 헤더를 함께 설계하면 효과적이다.

  • Cache-Control: no-cache, max-age 등의 정책 설정
  • Vary: Accept-Encoding, Authorization 등에 따라 캐시 분리
  • Pragma, Expires: 레거시 클라이언트 호환용

운영 관점 체크리스트

  • 로그에서 304 비율을 모니터링해 캐시 효율성 평가
  • CDN과 연동 시 CDN 캐싱 정책과 충돌하지 않는지 확인
  • 버전 업그레이드 시 ETag 전략을 문서화해 혼선 방지

요약

Spring Boot에서 response caching spring boot 구현은 ETag와 Last-Modified의 적절한 조합으로 효과를 볼 수 있다. 간단히 적용하려면 ShallowEtagHeaderFilter를 사용하고, 정밀 제어가 필요하면 컨트롤러 수준에서 ResponseEntity.eTag(), lastModified()와 WebRequest.checkNotModified()를 활용한다. 운영 시에는 메모리·신뢰성·CDN 정책을 함께 고려해 설계하는 것이 중요하다.

spring boot etag 설정 last-modified spring boot response caching spring boot etag spring boot http caching spring conditional get cache control spring shallowetagheaderfilter