조회수: 105

412 Precondition Failed 원인과 해결

412 Precondition Failed는 If-Match ETag가 서버의 현재 버전과 더는 일치하지 않는 것입니다. 오래된 쓰기와 CDN ETag 함정을 짚어냅니다. 무료 즉시 진단으로 바로 확인.

내 도메인에 이 문제가 있는지 지금 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.

Problem

조건부 PUT·PATCH·DELETE412 Precondition Failed로 돌아옵니다. 요청은 유효했고, 인증도 통과했고, 라우트도 됩니다. 그런데 당신이 조건 — If-Match ETag나 If-Unmodified-Since 날짜 — 을 붙였고, 서버가 그 조건을 리소스의 현재 상태에 대해 검사했는데 거짓이 나왔습니다. 당신이 마지막으로 읽은 이후로 누군가 또는 무언가가 리소스를 바꾼 겁니다.

Symptoms

  • 저장이 “다른 사용자가 레코드를 수정했습니다” 또는 “버전 충돌 — 새로고침하세요”로 실패합니다.
  • If-Match: "..."가 붙은 PUT은 412인데, 조건 없는 맨 PUT은 성공하고 덮어씁니다.
  • 오리진에 직접 보내면 사전조건이 통과하는데, CDN이나 리버스 프록시를 거치는 순간 실패합니다.
  • 생성 전용 PUTIf-None-Match: *를 걸었더니 리소스가 이미 존재해서 412가 납니다.

What 412 Actually Means

RFC 9110(§15.5.13)은 412를 “요청 헤더 필드에 주어진 하나 이상의 조건이 서버에서 테스트됐을 때 거짓으로 평가된” 경우로 정의합니다. 핵심 단어는 조건입니다: 412는 절대 우연히 일어나지 않습니다. 요청이 사전조건 헤더를 실었을 때만 나타나고, 서버는 그 사전조건이 성립하지 않았다고 알려주는 겁니다.

이건 낙관적 동시성 제어(optimistic concurrency control)의 기계장치이고, 딱 하나의 문제를 풀려고 존재합니다: 손실된 업데이트(lost update). 리소스를 GET하면 서버가 ETag: "v1"을 돌려줍니다. 당신이 편집합니다. If-Match: "v1"을 붙여 PUT합니다 — “내가 읽은 그 버전 그대로일 때만 적용하라”는 뜻이죠. 그 틈에 다른 클라이언트가 "v2"를 저장했다면 서버의 ETag는 이제 "v2"이고, 당신의 사전조건은 실패하고, 412를 받습니다 — 남의 작업을 조용히 짓밟는 대신에. 그 412는 시스템이 제대로 도는 겁니다. 당신이 눈치도 못 챌 경쟁 상태를 잡아낸 거죠.

명세는 평가 순서도 못 박습니다(§13.2.2): If-MatchIf-Unmodified-SinceIf-None-MatchIf-Modified-Since보다 우선합니다. 요청이 둘 이상의 조건을 실을 때 이 순서가 중요하고, 오래된 If-Match가 나머지 전부를 단락시키는 이유입니다. 이 설계 전체가 하나의 약속입니다: 당신이 겨냥한 버전이 여전히 그 자리에 있지 않는 한, 어떤 안전하지 않은 쓰기도 착지하지 않는다.

Top 3 Causes

  1. 진짜 경쟁에서 온 오래된 ETag - 교과서적 경우이고, 대개 올바른 동작입니다. 버전 "v1"을 읽었는데 편집하는 동안 다른 사람이 "v2"를 썼고, 당신의 If-Match: "v1"이 더는 일치하지 않습니다. 서버는 더 새 데이터를 덮어쓰길 거부하고 412를 반환합니다. 여기 버그는 없습니다 — 해결은 다시 GET하고, 현재 ETag를 취하고, 새 상태 위에 변경을 병합하고, 재제출. 이 412가 짜증난다면, 그게 막은 걸 기억하세요: 당신의 편집이 남의 작업을 조용히 지우는 것.

  2. ETag를 다시 쓰는 프록시 - 분통 터지는 경우. Apache의 mod_deflate는 응답을 압축할 때 ETag에 -gzip을 붙여, 클라이언트는 "abc-gzip"을 캐시하는데 오리진의 정본 ETag는 "abc"입니다. 당신의 If-Match는 망가진 값을 보내고, 오리진은 진짜 값과 비교하니, 동시 활동이 전혀 없어도 조건부 쓰기마다 412입니다. 일부 CDN은 ETag를 재생성하거나 통째로 벗깁니다. 신호는 오리진에는 사전조건이 되는데 엣지를 통하면 깨진다는 것.

  3. 날짜 기반 사전조건과 시계 오차 - If-Unmodified-Since는 초 단위 정밀도로 타임스탬프를 비교하는데, 거칠고 약합니다. 리소스가 당신이 읽은 바로 그 초에 수정됐거나, 서버의 Last-Modified 시계가 클라이언트와 어긋나면, 의미 있는 변화가 없어도 비교가 실패할 수 있습니다. ETag는 강한 식별자이고, HTTP 날짜는 손실 있는 근사치입니다. 가능하면 If-Unmodified-Since에 날짜를 거는 대신 If-Match에 ETag를 거세요.

Diagnose with DechoNet

  • HTTP 진단으로 그 엔드포인트가 깨끗한 GET에 실제로 반환하는 ETagLast-Modified 헤더를 보세요. 412는 당신이 보내는 검증자(validator)와 리소스가 현재 가진 검증자 사이의 불일치이므로, 첫 수는 현재 것을 읽는 겁니다. 진단이 보고하는 ETag가 당신 클라이언트가 캐시한 것과 다르다면, 그게 답입니다 — 당신은 사라진 버전에 조건을 걸고 있습니다. 진단에 ETag가 아예 없다면, 그 엔드포인트는 당신 클라이언트가 가정하는 방식의 조건부 요청을 지원하지 않을 수 있습니다.

Resolution Checklist

  • If-Match에 보내는 ETag를 리소스가 지금 반환하는 ETag와 비교하세요. 다르다면 리소스가 움직인 것 — 쓰기 전에 다시 읽으세요.
  • 진짜 오래된 쓰기 경쟁이면, 리소스를 다시 GET하고, 새 ETag를 취하고, 현재 상태 위에 변경을 다시 얹고, 새 If-Match로 재제출하세요. 같은 오래된 조건을 절대 반복하지 마세요 — 영원히 412입니다.
  • Apache + 압축 뒤에 있다면 -gzip ETag 접미사를 확인하세요. 접미사를 끄거나(최신 Apache의 DeflateAlterETag NoChange) If-Match를 보내기 전에 클라이언트 측에서 벗기세요.
  • 오리진에는 사전조건이 통과하는데 CDN을 통하면 실패한다면, 엣지가 서빙하는 ETag와 오리진이 설정하는 ETag를 비교하세요. ETag를 재생성하는 프록시는 조건부 요청을 설계상 깨뜨립니다 — 오리진의 값을 건드리지 않고 통과시키도록 설정하세요.
  • 날짜의 If-Unmodified-Since보다 강한 ETag의 If-Match를 선호하세요. 초 단위 타임스탬프는 엔티티 태그라면 피할 가짜 412를 유발합니다.
  • 현재 검증자로 조건부 요청을 다시 돌려 이제 2xx가 나오는지 확인하세요.

When to Escalate

  • 동시성이 전혀 없는데 클라이언트가 조건부 쓰기마다 412를 맞는다면, ETag 파이프라인이 오리진과 클라이언트 사이 어딘가에서 깨진 겁니다 — 보통 태그를 다시 쓰거나 벗기는 프록시. 인프라 수정이고, 엣지를 소유한 쪽의 몫입니다.
  • 낙관적 동시성을 지원해야 할 리소스가 정당한 첫 쓰기에서 412를 반환한다면, 상위 캐시가 이전 버전의 오래된 ETag를 서빙하는지 확인하세요. 리소스 상태보다 오래 사는 캐시된 검증자는 모든 조건부 쓰기를 가짜 충돌로 만듭니다.

관련 도구

관련 가이드

가이드 공유

[Ad] Guide Detail Inline
← 전체 가이드 보기