409 Conflict
사용법
409 Conflict 오류가 도착하는 경우, 요청 자체는 유효하지만 상태가 맞지 않아 완료할 수 없는 상황입니다. 예를 들어 클라이언트가 이미 삭제된 게시물에 답글을 달려 하면, 대상 리소스는 더 이상 호환되는 상태가 아닙니다. 서버는 그 충돌을 알리기 위해 이 상태를 반환합니다.
참고
409 Conflict는 요청의 형식은 올바르지만 현재 리소스 상태와 충돌한다는 뜻입니다(동시 갱신, 중복 생성). 422 Unprocessable Content는 요청 본문이 문법적으로는 유효하지만 의미상 유효하지 않다는 뜻입니다(검증 규칙 실패). 400 Bad Request는 요청 자체의 형식이 잘못되었다는 뜻입니다(파싱 불가능한 본문, 유효하지 않은 헤더). 409는 상태 충돌, 422는 검증, 400은 문법에 관한 것입니다.
클라이언트는 문제를 어떻게 해소할지 판단하기 위해 메시지 본문을 살펴봐야 합니다. 삭제된 게시물에 답글을 다는 경우라면, 클라이언트는 요청을 포기하거나, 이전 스레드의 삭제를 취소하거나, 이전 텍스트를 인용한 새 스레드를 만드는 선택을 합니다. 이 동작은 전적으로 클라이언트의 책임이며, 서버가 정한 요구 사항이나 정책을 따릅니다.
SEO 영향
Google 같은 검색 엔진은 409 상태를 반환하는 URL을 색인하지 않습니다. 이전에 색인되었다가 이 상태 코드를 반환하는 URL은 검색 결과에서 제거됩니다.
예시
클라이언트가 “ID = 111”인 기존 블로그 게시물에 “좋아요” 태그를 달려고 시도합니다. 서버는 해당 게시물이 동결되어 더 이상 답글이나 태그를 받지 않음을 알리는 응답을 보냅니다.
요청
POST /blog/update?postid=111&flag=like HTTP/1.1
Host: example.com
응답
HTTP/1.1 409 Conflict
Content-Type: text/html
Content-Length: 288
<html>
<head>
<title>Blog Entry Frozen</title>
</head>
<body>
<p>This post has been marked read-only. To post
a reply or tag, change the status and try
again.</p>
</body>
</html>
해결 방법
409 Conflict는 요청이 서버상 리소스의 현재 상태와 충돌한다는 뜻입니다.
재시도 전에 현재 리소스 상태를 가져오세요. GET 요청을 보내 최신 버전을 조회하세요. 클라이언트가 마지막으로 데이터를 읽은 이후 리소스 상태가 바뀐 것입니다. 반환된 ETag나 버전 필드를 클라이언트가 보유한 값과 비교하세요.
조건부 헤더로 충돌을 감지하세요. PUT과 PATCH 요청에 현재 ETag 값을 담은 If-Match를 포함하세요. ETag가 더 이상 일치하지 않으면 서버가 요청을 거부하므로, 무작정 덮어쓰는 일을 막을 수 있습니다. 이 패턴을 낙관적 동시성 제어라고 부릅니다.
PUT /api/resource/42 HTTP/1.1
If-Match: "abc123"
Content-Type: application/json
{"name": "updated value"}
GET과 PUT 사이에 다른 클라이언트가 리소스를 수정했다면 서버는 409로 응답합니다.
변경 사항을 병합하거나 충돌 해소 로직을 구현하세요. 클라이언트가 의도한 변경과 현재 서버 상태를 비교하세요. 마지막 쓰기 우선, 필드 단위 병합, 또는 최종 사용자에게 차이를 해소하도록 요청하는 등의 병합 전략을 적용하세요. 페이로드에 version이나 revision 필드가 있는 API는 클라이언트가 갱신 요청에 현재 버전 번호를 포함하도록 요구합니다.
중복 리소스 충돌을 해소하세요. POST나 PUT으로 리소스를 생성할 때 동일한 고유 식별자를 가진 리소스가 이미 있으면 서버는 409를 반환합니다. 생성 전에 기존 항목이 있는지 확인하고, 재시도 시 중복 생성을 피하도록 멱등성 키를 사용하세요.
데이터베이스 수준의 충돌을 처리하세요. 데이터베이스를 백엔드로 쓰는 애플리케이션 서버는 고유 제약, 외래 키 제약, 행 수준 잠금이 작업을 막을 때 409를 반환합니다. 응답 본문에는 문제가 된 제약 이름이 포함되는 경우가 많습니다. 재시도하기 전에 근본적인 데이터 충돌(중복 키, 누락된 부모 레코드, 동시 트랜잭션)을 해소하세요.
버전 관리 시스템에서는 최신 버전을 받아 변경 사항을 다시 적용하세요. 최신 리비전을 가져와 차이를 조정한 뒤 갱신을 다시 제출하세요. Git 기반 API(GitHub, GitLab)는 푸시가 업스트림 변경과 충돌할 때 409를 반환합니다.
응답 본문에서 충돌 정보를 확인하세요. 서버는 보통 불일치를 설명하는 정보, 즉 충돌한 필드, 현재 값, 또는 기대되는 버전을 포함합니다. 이 데이터를 이용해 올바른 해소 경로를 결정하세요.
상태 갱신을 포함한 재시도 로직을 구현하세요. 자동화된 클라이언트라면 최신 리소스 상태를 가져오고, 의도한 수정을 다시 적용한 뒤, 재제출하는 재시도 루프를 만드세요. 동시성이 높은 상황에서 서버에 과부하를 주지 않도록 지수 백오프를 추가하세요.
코드 참고
.NET
HttpStatusCode.Conflict
Rust
http::StatusCode::CONFLICT
Rails
:conflict
Go
http.StatusConflict
Symfony
Response::HTTP_CONFLICT
Python3.5+
http.HTTPStatus.CONFLICT
Java
java.net.HttpURLConnection.HTTP_CONFLICT
Apache HttpComponents Core
org.apache.hc.core5.http.HttpStatus.SC_CONFLICT
Angular
@angular/common/http/HttpStatusCode.Conflict