애플리케이션에서의 ETag — 개념, 목적 및 원리

저자: IT Sectr 게시일: 2026-06-14 읽는 시간: 7 분

ETag는 리소스 버전의 고유 식별자를 포함하는 HTTP 응답 헤더입니다. 서버는 콘텐츠 해시나 버전 번호로 ETag를 생성하여 데이터와 함께 클라이언트에 반환합니다. 이후 요청에서 클라이언트는 이 식별자를 If-None-Match 헤더로 전송하여 서버가 리소스 변경 여부를 확인할 수 있게 합니다. MDN Web Docs, 2025에 따르면 ETag는 HTTP에서 조건부 GET 요청 메커니즘의 기초입니다. ETag를 사용한 조건부 요청은 모바일 앱 동기화 중 전송되는 데이터 양을 최대 90%까지 줄입니다.

핵심 요점

  • ETag는 리소스 버전의 고유 식별자를 포함하는 HTTP 헤더로, 일반적으로 콘텐츠의 해시입니다.
  • If-None-Match — 클라이언트가 저장된 ETag를 보내면, 리소스가 변경되지 않은 경우 서버가 304 Not Modified를 반환합니다.
  • 트래픽 절약 — ETag를 사용한 조건부 요청은 응답 본문이 전송되지 않으므로 모바일 앱 동기화 중 데이터 양을 줄입니다.
  • 강한 ETag와 약한 ETag — 강한 ETag는 콘텐츠를 바이트 단위로 구분하고, 약한 ETag는 리소스의 의미론적 동등성을 허용합니다.
  • 용도 — ETag는 REST API에서 데이터 동기화, 캐싱 및 편집 충돌 방지에 사용됩니다.

HTTP와 모바일 애플리케이션에서 ETag란?

ETag(Entity Tag)는 조건부 헤더 계열에 속하는 HTTP 헤더로, 캐시된 리소스의 유효성을 검사합니다. 서버는 ETag를 해시(MD5, SHA-256) 또는 리소스 버전 번호로 계산하여 GET 요청에 대한 응답으로 반환합니다. 클라이언트는 ETag를 데이터와 함께 저장하고 이후 요청에서 If-None-Match 헤더로 전송합니다. 리소스 콘텐츠가 변경되지 않은 경우 서버는 응답 본문 없이 304 Not Modified 상태로 응답합니다.

모바일 애플리케이션의 경우 ETag는 다운로드되는 데이터 양을 줄여주기 때문에 매우 중요합니다. 실행 또는 동기화할 때마다 앱은 If-None-Match 요청으로 리소스의 최신 상태를 확인합니다 — 전체 데이터를 로드하는 대신 304를 받고 로컬 복사본을 사용합니다. Google Chrome Team(2024)에 따르면 모바일 API에서 ETag를 사용하면 목록의 평균 응답 크기가 87%, 개별 객체의 경우 94% 감소합니다.

ETag는 서버 측에서 생성되며 결정론적(동일한 콘텐츠에 대해 동일, 공유 캐시에 유용)이거나 응답별로 고유(엄격한 검증용)할 수 있습니다. 모바일 동기화를 위해 설계된 REST API에서는 콘텐츠 해시와 데이터베이스 레코드 버전 번호의 조합이 가장 일반적입니다.

ETag 유형: 강한 식별자와 약한 식별자

강한 ETag(strong ETag)는 사소한 변경(공백, 서식)을 포함한 콘텐츠 변경 시 변경되는 식별자입니다. 형식: “abc123def”(큰따옴표로 묶고, 접두사 없음). 강한 ETag는 리소스가 바이트 단위로 변경되지 않았음을 보장합니다. 범위 요청(Range requests) 및 부분 다운로드 무결성 검사에 필수적입니다.

약한 ETag(weak ETag)W/ 접두사가 있는 식별자입니다(예: W/“abc123def”). 바이트 표현이 다르더라도 리소스가 의미론적으로 동등함을 허용합니다. 약한 ETag는 서식이나 공백은 다르지만 의미는 같은 응답을 동적으로 생성하는 서버에 유용합니다. 그러나 약한 ETag는 범위 요청을 지원하지 않습니다.

ETag 유형 비교:

특성강한 ETag약한 ETag
형식“hash”W/“hash”
민감도바이트 단위의미론적
범위 요청지원미지원
CDN 캐싱이상적제한적
동기화높은 정밀도충돌 허용

ETag vs Last-Modified: 무엇을 선택할까

Last-Modified는 리소스의 마지막 수정 날짜와 시간을 나타내는 HTTP 헤더입니다. 클라이언트는 이를 If-Modified-Since 헤더로 다시 보냅니다. Last-Modified는 구현이 더 간단하지만(서버는 날짜만 필요), 근본적인 한계가 있습니다: 1초 해상도(같은 초 내의 두 변경을 구분 불가)와 타임스탬프가 같을 때 콘텐츠 변경 여부를 확인할 수 없음(예: 백업 복원 후).

ETag는 이러한 문제를 해결합니다: 콘텐츠 해시는 시간에 관계없이 모든 수정 시 변경됩니다. 따라서 최신 REST API는 두 헤더를 조합하여 사용합니다: ETag는 정확한 검증용, Last-Modified는 CDN에서의 대략적인 필터링용입니다. Apache HTTP Server와 Nginx는 기본적으로 정적 파일에 대해 두 헤더를 모두 생성합니다.

동기화 기능이 있는 모바일 애플리케이션의 경우 ETag가 더 중요합니다. 편집 충돌을 감지할 수 있기 때문입니다. 클라이언트가 If-Match: “etag”와 함께 PUT 요청을 보내면, 다른 클라이언트가 리소스를 수정한 경우 서버가 요청을 거부합니다(낙관적 잠금). Last-Modified는 초 단위 정밀도로 인해 이러한 신뢰성을 보장할 수 없습니다.

Kotlin에서 ETag 사용 예제

클라이언트 측 구현을 살펴보겠습니다. Retrofit과 OkHttp를 사용하여 Kotlin으로 모바일 앱에서 ETag를 구현하는 예제입니다. 각 GET 요청 시 클라이언트는 응답에서 ETag를 저장하고, 다음 요청에서 If-None-Match 헤더로 전송합니다. 서버가 304를 반환하면 데이터를 다시 다운로드하지 않습니다.

ETag 캐싱을 사용한 OkHttp 클라이언트 설정:

kotlin
class EtagClient {
    private val etagCache =
        mutableMapOf<String, String>()

    private val client = OkHttpClient.Builder().build()

    suspend fun fetchWithEtag(
        url: String
    ): Result<String> {
        val request = Request.Builder()
            .url(url)
            .header("If-None-Match",
                etagCache[url] ?: "")
            .build()

        val response = client.newCall(request).await()

        return when (response.code) {
            304 -> Result.success(
                "not_modified")
            200 -> {
                response.header("ETag")?.let {
                    etagCache[url] = it
                }
                Result.success(response.body?.string()
                    ?: "")
            }
            else -> Result.failure(
                Exception("HTTP ${response.code}"))
        }
    }
}

클라이언트는 성공적인 200 응답 후 ETag를 저장하고 다음 요청에서 If-None-Match 헤더로 전송합니다. 304 응답 시 클라이언트는 로컬 버전이 최신임을 알고 재다운로드에 트래픽을 낭비하지 않습니다. 이 패턴은 자주 요청되는 리소스에 대해 모바일 앱의 네트워크 비용을 80–90% 절감합니다.

모바일 앱 동기화에서 ETag의 역할

ETag는 REST API와의 모바일 앱 동기화를 최적화하는 핵심 메커니즘입니다. 표준 동기화 방식에서 클라이언트는 먼저 ETag 검증과 함께 리소스 목록을 요청합니다 — 변경된 리소스가 없으면 서버는 304를 반환하고 클라이언트는 동기화를 완료합니다. 변경사항이 있으면 서버는 수정된 리소스만 반환합니다. 이 방식을 델타 동기화라고 하며 트래픽이 제한된 모바일 기기에 매우 중요합니다.

낙관적 잠금 시나리오에서 ETag는 Lost Update 충돌을 방지하는 데 사용됩니다. 클라이언트가 PUT 요청으로 리소스를 업데이트할 때 If-Match: “etag” 헤더를 포함합니다. ETag가 일치하지 않으면(다른 클라이언트가 이미 리소스를 수정한 경우) 서버는 412 Precondition Failed로 응답하며, 클라이언트는 현재 버전을 다시 가져와 수정을 재시도해야 합니다. 이 방식은 데이터베이스 수준의 잠금 없이 데이터 일관성을 보장합니다.

오프라인 모드의 분산 시스템에서는 ETag가 충돌 해결과 결합하여 사용됩니다. 클라이언트는 모든 리소스의 현재 ETag를 가져와 동기화합니다. 변경사항 전송 시 서버는 If-Match를 확인합니다 — ETag가 일치하지 않으면 충돌이 등록되고 선택한 전략(LWW, 병합)에 따라 해결됩니다. Postman API Report(2025)에 따르면 모바일 애플리케이션용 프로덕션 REST API의 67%가 버전 검증의 기본 메커니즘으로 ETag를 사용합니다.

자주 묻는 질문

ETag HTTP 헤더란 무엇인가요?

ETag는 리소스 버전의 고유 식별자를 포함하는 HTTP 응답 헤더입니다. 클라이언트는 조건부 요청에 사용합니다: 리소스가 변경되지 않은 경우 서버는 응답 본문 없이 304 Not Modified를 반환하여 트래픽을 절약합니다.

ETag와 Last-Modified의 차이점은 무엇인가요?

ETag는 정확한 비교를 위해 콘텐츠 해시를 사용합니다. Last-Modified는 초 단위 정밀도의 수정 날짜를 기반으로 합니다. ETag는 실제 변경 감지에 더 신뢰할 수 있으며 If-Match를 통한 낙관적 잠금을 지원합니다.

강한 ETag와 약한 ETag란 무엇인가요?

강한 ETag(접두사 없음)는 리소스를 바이트 단위로 구분합니다. 약한 ETag(W/ 접두사 있음)는 의미론적 동등성을 허용합니다. 강한 ETag는 범위 요청에 필요하고, 약한 ETag는 동적으로 생성된 콘텐츠에 사용됩니다.

ETag가 모바일 동기화에 어떻게 도움이 되나요?

ETag는 트래픽을 80–90% 절감합니다: 클라이언트는 If-None-Match를 통해 모든 리소스의 최신 상태를 확인하고 변경된 것만 다운로드합니다. ETag가 없으면 클라이언트는 동기화할 때마다 전체 데이터를 다운로드하여 트래픽과 배터리를 낭비하게 됩니다.

서버에서 ETag를 구현하려면 어떻게 해야 하나요?

서버는 ETag를 응답 콘텐츠의 해시(MD5, SHA-256)로 계산하거나 데이터베이스의 레코드 버전 번호를 사용합니다. Spring Boot에서는 @Cacheable 어노테이션에 etag = true를 지정하면 충분합니다. Express.js에서는 etag 미들웨어가 기본적으로 활성화되어 있습니다.

요약

  • ETag는 콘텐츠 해시 또는 버전 번호를 기반으로 리소스 버전을 검증하는 HTTP 헤더입니다.
  • 조건부 요청 — 클라이언트가 저장된 ETag와 함께 If-None-Match를 보내면 서버는 변경이 없을 경우 304로 응답합니다.
  • ETag 유형 — 강한 ETag(바이트 단위, 범위 요청용)와 약한 ETag(의미론적 동등성, W/ 접두사).
  • 장점 — ETag는 Last-Modified보다 정확합니다. 해시가 시간에 관계없이 모든 콘텐츠 변경 시 변경되기 때문입니다.
  • 낙관적 잠금 — If-Match를 통해 ETag는 동시 리소스 편집 시 Lost Update 충돌을 방지합니다.
  • 델타 동기화 — ETag는 변경된 리소스만 전송되는 동기화 방식을 구현합니다.
  • 권장사항 — 모바일 애플리케이션의 REST API에 항상 ETag를 추가하세요. CDN 및 프록시 서버 호환성을 위해 Last-Modified와 함께 사용하세요.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기