본문 바로가기
IT

HTTP 409 Conflict — 충돌·동시 수정·중복 원인·해결

by 샤나엘 2026. 6. 28.
반응형

HTTP 409 Conflict — 충돌·동시 수정·중복 원인·해결

저장 버튼을 눌렀더니 409 Conflict가 떴다. 데이터 형식도 맞고 권한도 있는데 거절당했다. 409는 "요청이 자원의 현재 상태와 충돌한다"는 뜻이다. 형식이 틀린 것도(400), 검증에 실패한 것도(422) 아니다. 자원이 지금 놓인 상태와 부딪힌 것이다. 대표적으로 이미 있는 걸 또 만들려 하거나, 같은 자원을 두 사람이 동시에 수정해 버전이 어긋났을 때 난다. 이 글은 409가 정확히 무엇인지, 동시성과 중복이라는 두 축에서 왜 생기는지, 그리고 낙관적 잠금·버전 관리·멱등키로 어떻게 푸는지를 정리한다.

 

HTTP 409 Conflict

이 글의 구성

 

🔎409 Conflict의 의미
🧩자주 나오는 발생 원인 4가지
💻재현과 충돌 확인
🛠해결 — 동시성·중복 다루기
⚖️422·412와의 차이
💬자주 묻는 질문 5가지

🔎 409 Conflict의 의미

409는 4xx, 클라이언트 요청에 문제가 있다는 묶음이다. 다만 그 문제가 요청 자체의 형식이나 값이 아니라, 요청이 자원의 "현재 상태"와 맞지 않는다는 데 있다. 서버는 요청을 이해했고 처리할 능력도 있지만, 지금 자원이 놓인 상태에서는 그 요청을 받아들이면 모순이 생긴다. 그래서 충돌(Conflict)이다.

명세는 409 응답에 "충돌의 원인을 사용자가 알아볼 만한 정보를 담는 것이 좋다"고 권한다. 사용자가 충돌을 인지하고 스스로 풀 여지를 줘야 하기 때문이다. 그래서 잘 설계된 API는 409와 함께 "무엇이 이미 존재한다" 또는 "당신이 본 버전이 낡았다" 같은 단서를 본문에 담는다. 충돌은 주로 자원의 상태를 바꾸는 동작, 즉 생성·수정 같은 요청에서 생긴다.

핵심 — 형식이 아니라 상태의 문제

400은 형식이 깨진 것, 422는 값이 규칙에 안 맞는 것, 409는 값도 형식도 맞지만 지금 자원 상태와 부딪히는 것이다. 같은 입력이라도 자원 상태가 바뀌면 409가 났다 안 났다 한다. 그래서 409는 "다시 보내면 되는" 문제가 아니라, 상태를 확인하거나 동기화한 뒤에야 풀리는 경우가 많다.


🧩 자주 나오는 발생 원인 4가지

원인 1 — 중복 생성 (이미 존재함)

가장 흔하다. 이미 가입된 이메일로 또 가입, 같은 키로 자원을 또 생성하는 경우다. 고유 제약을 가진 자원을 중복 생성하려다 현재 상태(이미 존재)와 충돌한다.

원인 2 — 동시 수정 충돌

두 사람이 같은 문서를 열어 각자 수정하고 저장할 때다. 나중에 저장하는 쪽이 앞사람의 변경을 덮어쓰려는 순간, 서버가 "당신이 본 버전은 이미 바뀌었다"며 409로 막는다. 협업 편집에서 자주 본다.

원인 3 — 버전·낙관적 잠금 불일치

서버가 자원에 버전 번호를 두고, 요청이 들고 온 버전이 현재 버전과 다르면 거절하는 방식이다. 내가 읽은 시점 이후 누군가 먼저 바꿨다는 뜻이라, 충돌로 409가 난다.

원인 4 — 상태 전이 위반

이미 취소된 주문을 또 취소하거나, 이미 완료된 작업을 되돌리려는 경우다. 자원이 그 동작을 받을 수 없는 상태에 있어, 현재 상태와 요청이 충돌한다.


💻 재현과 충돌 확인

409는 자원의 현재 상태와 부딪히는 요청을 보내면 재현된다. 같은 자원을 두 번 만들어 보는 게 가장 쉽다.

# 같은 이메일로 두 번째 생성 시도 → 409
curl -i -X POST https://example.com/users \
  -H 'Content-Type: application/json' \
  -d '{"email": "kim@example.com"}'

# 응답 — 충돌 원인을 본문에 담는다
HTTP/1.1 409 Conflict
{
  "error": "conflict",
  "message": "이미 등록된 이메일입니다"
}

동시 수정 충돌은 버전(또는 ETag)을 들고 수정 요청을 보낼 때, 그 사이 자원이 바뀌어 있으면 재현된다. 잘 만든 API는 "현재 버전"이나 "충돌한 필드"를 응답에 알려준다.

# 낡은 버전으로 수정 시도 → 409
curl -i -X PUT https://example.com/docs/1 \
  -H 'Content-Type: application/json' \
  -d '{"version": 3, "title": "수정"}'

# 서버의 현재 버전은 5 → 충돌
HTTP/1.1 409 Conflict
{ "message": "최신 버전(5)이 아닙니다. 다시 불러오세요" }

409의 단서도 상태 코드가 아니라 응답 본문이다. "무엇이 이미 있는지" 또는 "어느 버전과 어긋났는지"를 읽으면, 다시 시도할지 동기화부터 할지가 정해진다.


🛠 해결 — 동시성·중복 다루기

409는 단순 재시도로는 안 풀리는 경우가 많다. 충돌의 종류(중복인가 동시 수정인가)에 따라 접근이 다르다.

충돌 유형 해결 방법
중복 생성 먼저 존재 여부 조회, 있으면 생성 대신 조회·갱신으로 분기
중복 요청(재전송) 멱등키(Idempotency-Key)로 같은 요청의 중복 처리 방지
동시 수정 낙관적 잠금 — 버전·ETag 비교 후 어긋나면 최신본 다시 불러오기
상태 전이 위반 현재 상태를 먼저 확인하고, 허용된 전이만 요청

동시 수정 충돌 풀기 (낙관적 잠금)

 

01자원을 읽을 때 버전 번호(또는 ETag)도 함께 받아 둔다.
02수정 요청에 그 버전을 실어 보낸다(본문 version 필드 또는 If-Match 헤더).
03서버는 현재 버전과 비교해, 같으면 적용하고 다르면 거절한다. 본문 버전 충돌은 409, If-Match 같은 조건 헤더 불일치는 412로 응답하는 게 표준이다.
04409를 받으면 최신본을 다시 불러와 변경을 병합하거나 사용자에게 선택을 묻는다.

핵심은 "덮어쓰기 전에 버전을 확인한다"는 발상이다. 이렇게 하면 나중 저장이 앞사람의 변경을 모르고 지우는 사고(lost update)를 막는다. 충돌이 나면 무작정 재시도하지 말고, 최신 상태를 반영한 뒤 다시 시도하는 흐름을 설계해야 한다.

409는 형식이 아니라 상태의 충돌이다. 그래서 그냥 재시도가 아니라, 최신 상태를 확인하고 동기화한 뒤 다시 보내야 풀린다.

 

— 덮어쓰기 전에 버전을 확인하라


⚖️ 422·412와의 차이

409는 비슷한 4xx 코드와 헷갈리기 쉽다. 무엇과 부딪혔는지에 따라 코드가 갈린다.

코드 무엇의 문제 대표 상황
409 자원의 현재 상태와 충돌 중복 생성, 동시 수정 버전 불일치
422 값이 검증 규칙 위반 이메일 형식 오류, 필수값 누락
412 보낸 사전조건이 실패 If-Match 조건 불일치(조건부 요청)

셋의 차이는 미묘하다. 422는 보낸 값 자체가 규칙에 안 맞는 것이고, 409는 값은 맞지만 자원 상태와 부딪히는 것이다. 412 Precondition Failed는 클라이언트가 If-Match 같은 사전조건을 명시적으로 걸었고 그 조건이 실패했을 때다. 동시성 제어에서 412와 409가 둘 다 쓰이는데, 클라이언트가 조건 헤더로 명시했으면 412, 서버가 스스로 상태 충돌을 감지했으면 409로 보는 게 일반적이다. 다만 API마다 관습이 달라, 무엇을 쓰는지는 문서로 확인하는 게 안전하다.


💬 자주 묻는 질문 5가지

Q1그냥 다시 보내면 되나요?

409는 단순 재시도로 안 풀리는 경우가 많습니다. 자원 상태와 부딪힌 거라, 같은 요청을 그대로 또 보내면 또 409가 납니다. 중복이면 기존 자원을 조회하고, 동시 수정이면 최신본을 다시 불러와 반영한 뒤 시도해야 합니다.

Q2중복 가입은 409인가요 422인가요?

API마다 다릅니다. "이미 존재하는 자원과의 충돌"로 보면 409, "입력값 검증 실패"로 보면 422를 씁니다. 둘 다 쓰이는 패턴이라 틀린 건 아닙니다. 내가 쓰는 API가 무엇으로 돌려주는지 문서로 확인하세요.

Q3동시 수정 충돌은 어떻게 막나요?

낙관적 잠금을 씁니다. 자원을 읽을 때 버전이나 ETag를 받아 두고, 수정 요청에 실어 보냅니다. 서버는 현재 버전과 비교해 어긋나면 409로 막죠. 그러면 클라이언트가 최신본을 다시 불러와 병합하거나 사용자에게 선택을 묻습니다. 덮어쓰기 사고를 막는 핵심 장치입니다.

Q4멱등키(Idempotency-Key)는 뭔가요?

같은 요청이 실수로 두 번 전송돼도 한 번만 처리되게 하는 장치입니다. 결제처럼 중복 처리가 치명적인 곳에서, 요청에 고유 키를 붙이면 서버가 같은 키의 두 번째 요청을 새로 처리하지 않고 첫 결과를 돌려줍니다. 네트워크 재전송으로 인한 중복 409·이중 처리를 막습니다.

Q5409와 412는 어떻게 구분하나요?

412 Precondition Failed는 클라이언트가 If-Match 같은 사전조건을 명시적으로 걸었고 그게 실패했을 때입니다. 409는 서버가 스스로 상태 충돌을 감지한 경우죠. 동시성 제어에서 둘 다 쓰이지만, 조건 헤더를 명시했으면 412, 아니면 409가 일반적입니다. 역시 API 규약을 따르세요.


📌 결론

409 Conflict는 "요청이 자원의 현재 상태와 충돌한다"는 신호다. 형식이 깨진 400, 값이 규칙에 안 맞는 422와 달리, 409는 값도 형식도 맞지만 지금 자원 상태와 부딪힌 것이다. 그래서 그냥 재시도가 아니라 상태를 확인하고 동기화하는 접근이 필요하다.

실무 원칙은 다음과 같다.

 

⚔ 상태 충돌임을 안다. 같은 요청도 자원 상태에 따라 409가 났다 안 났다 한다. 단순 재시도로는 안 풀린다.

🔢 동시 수정은 낙관적 잠금으로 막는다. 버전·ETag를 읽어 두고 수정 시 비교해, 어긋나면 최신본을 다시 불러와 반영한다.

🔄 중복은 멱등으로 다룬다. 생성 전 존재 확인, 재전송에는 멱등키, 상태 전이는 현재 상태 확인 후 허용된 동작만 보낸다.

409 트러블슈팅 체크리스트

 

01응답 본문에서 충돌 원인(중복·버전 불일치) 확인.
02중복 생성이면 기존 자원 조회 후 조회·갱신으로 분기.
03동시 수정이면 낙관적 잠금(버전·ETag) 적용, 최신본 병합.
04중복 요청 위험엔 멱등키로 이중 처리 방지.
05상태 전이 위반은 현재 상태 확인 후 허용 동작만 요청.
06409·422·412 구분은 API 문서 규약으로 확인.

본 글은 HTTP 409 응답의 일반적 원인과 해결 방법을 정리한 자료다. 동시성·검증 설계 변경은 영향 범위를 확인한 뒤 신중히 적용한다.

 

#HTTP409 #Conflict #409에러 #동시성제어 #낙관적잠금 #OptimisticLocking #멱등키 #IdempotencyKey #ETag #버전관리 #중복생성 #동시수정 #REST API #API에러 #백엔드

반응형

댓글