개발 서버에서는 안 보이던 버그
개발과 테스트 단계에서는 보통 한 사람이 한 화면을 씁니다. 그래서 두 사람이 같은 데이터를 거의 같은 순간에 저장할 때 생기는 문제는 운영을 시작한 뒤에야 드러납니다. 대표적인 증상은 다음과 같습니다.
이런 문제는 수십~수백 밀리초 안에 요청이 겹칠 때만 생기기 때문에 재현이 어렵습니다. 로그에는 정상 처리 두 건만 남아 있어서, 원인을 찾는 데 몇 주씩 걸리는 경우가 흔합니다.
동시성 문제의 유형
이 문제는 두 갈래로 나눠서 봐야 합니다. 화면 편집 충돌은 사람이 몇 분씩 붙잡고 있는 데이터에서 생기고, 백엔드 처리 경합은 밀리초 단위로 겹치는 요청에서 생깁니다. 둘은 해법이 다릅니다.
해결 방식 비교
낙관적 잠금
테이블에 version 컬럼을 두고, 저장할 때 "읽었을 때의 버전과 같을 때만 수정하고 버전을 1 올린다"는 조건을 겁니다. 수정된 행이 0건이면 그사이 누군가 먼저 저장한 것이므로 사용자에게 알립니다. 평소에는 잠금을 잡지 않아 성능 부담이 거의 없습니다. HTTP API에서는 ETag와 If-Match 헤더, 412 응답으로 같은 원리를 구현할 수 있습니다.
비관적 잠금
SELECT ... FOR UPDATE로 행을 잠그고 처리가 끝날 때까지 다른 트랜잭션을 기다리게 합니다. 확실하지만 대기가 길어질 수 있고, 두 트랜잭션이 서로 상대의 잠금을 기다리면 교착(데드락)이 생깁니다. 잠그는 순서를 항상 같게 정하고(예: 상품 ID 오름차순), lock_timeout이나 NOWAIT로 대기 시간에 상한을 두어야 합니다.
원자적 갱신과 제약 조건
읽고 계산해서 쓰는 대신 한 문장으로 처리합니다. "UPDATE 재고 SET 수량 = 수량 - 7 WHERE 상품 = ? AND 수량 >= 7"처럼 쓰면 확인과 차감이 한 번에 일어나므로 재고가 음수가 되지 않습니다. 주문 번호는 DB 시퀀스로 발급하고 유니크 키를 걸어 두면, 코드에 실수가 있어도 DB가 중복을 막아 줍니다.
멱등성 키
클라이언트가 요청마다 고유한 키를 보내고, 서버는 이미 처리한 키면 처음 결과를 그대로 돌려줍니다. 결제·주문처럼 재시도가 잦은 API에는 사실상 필수입니다.
업무별 적용 판단
사용자 경험까지 설계하기
충돌을 감지만 하고 "저장 실패"만 띄우면 사용자는 입력한 내용을 잃습니다. "다른 사용자가 먼저 수정했습니다" 안내와 함께 내 입력값과 최신 값을 나란히 보여 주고, 항목별로 고르게 해야 합니다. 편집 시작 시 "홍길동 님이 편집 중" 같은 소프트 잠금을 표시하면 충돌 자체가 줄어듭니다. 이 표시는 브라우저를 닫아도 남지 않도록 15~30분 정도의 만료 시간을 두고 자동으로 해제해야 합니다.
검증과 운영
기능 테스트로는 경합을 잡을 수 없습니다. 같은 상품에 50~100개의 요청을 동시에 보내는 부하 테스트를 만들고, 끝난 뒤 재고 합계·주문 번호 중복·결제 건수가 맞는지 확인합니다. 운영에서는 버전 충돌, 잠금 대기 초과, 멱등성 키 재사용을 감사 로그로 남겨 두면 어느 화면에서 충돌이 잦은지 수치로 보고 개선할 수 있습니다.
POLYGLOTSOFT는 ERP·MES·WMS 같은 업무 시스템을 설계할 때 동시성 제어와 충돌 안내 화면까지 함께 다룹니다. 구독형 개발 서비스로 새 시스템을 만들 때 처음부터 반영할 수 있고, 이미 운영 중인 시스템은 SM 서비스로 재고 불일치나 중복 처리 같은 문제의 원인을 찾아 개선해 드립니다. 비슷한 증상을 겪고 계시다면 편하게 문의해 주십시오.
