LabHub
배우기 러닝패스 코스

되돌릴 수 없는 변경 · 오래 기다리는 변경 · 이론

오래 기다리는 변경

LabHub 에서 이어서 보기

한 줄 요약

잠금 대기, SQL 실행, 재시도 횟수는 서로 다른 예산입니다. 시간 초과를 받았다는 사실만으로 업무 변경이 없었다거나 다시 실행해도 안전하다고 판단하지 않습니다.

왜 이게 필요했나

축제 주문 두 건의 취소를 되돌리려는데 화면이 계속 돌아갑니다. 첫 주문은 수정됐지만 둘째 주문을 다른 담당자가 잡고 있습니다. 이때 무작정 새 요청을 보내면 대기하는 실행이 늘어납니다. 버튼을 빠르게 누르는 행동이 문제를 해결하는 것이 아니라 같은 자원을 기다리는 줄을 길게 만드는 셈입니다. 고객에게 언제 다시 눌러도 되는지 설명하려면 어디서 얼마나 기다리고 무엇을 이미 확정했는지 알아야 합니다.

앞 실습은 승인한 버전을 UPDATE 조건에 넣어 다른 변경을 덮지 않았습니다. 하지만 조건이 안전하다는 것과 응답이 빠르다는 것은 다른 성질입니다. 맞지 않는 조건도 잠금이 풀려야 평가할 수 있습니다. 무한히 기다리지 않도록 한도를 정하고, 실패한 뒤 같은 연결을 다음 요청에 넘겨도 되는 상태인지 확인해야 합니다. 설정을 바꾼 연결이 풀에 돌아간 뒤 다른 사용자의 요청까지 짧은 시간에 끊어 버리는 것도 사고입니다.

어떻게 동작하나

PostgreSQL의 lock_timeout은 잠금을 얻으려는 개별 시도의 대기를 제한합니다. statement_timeout은 서버가 문장을 처리하는 시간을 제한합니다. 한 배치에 UPDATE가 여러 개 있으면 각각의 문장과 잠금 획득이 있으므로 두 설정 중 어느 것도 배치 전체의 경과 시간 상한과 같지 않습니다. 전체 API의 시간 예산에는 연결, 여러 SQL, 재시도 사이 대기와 응답 전송도 들어갑니다.

이번 실습은 작은 배치의 DB 대기와 실행을 먼저 분리해 관측합니다. 잠금 한도는 50–500ms, 문장 한도는 그보다 크고 2000ms 이하로 둡니다. 잠금 한도보다 문장 한도가 짧으면 먼저 SQL 전체가 취소돼 잠금 전용 한도가 무엇을 했는지 구분하기 어렵습니다. 이 숫자는 학습 환경의 계약이지 운영에서 그대로 사용하라는 권장값이 아닙니다.

설정은 트랜잭션 안에서 set_config의 세 번째 인자를 true로 주어 적용합니다. 함수가 성공하거나 오류로 롤백한 뒤에는 빌려 온 연결의 원래 설정이 남아 있어야 합니다. 세션 범위 설정을 쓰면 성공한 트랜잭션 뒤에도 한도가 남을 수 있습니다. 실패할 때 롤백됐다는 시험 하나로는 그 누수를 놓칩니다. 그래서 성공·실패 두 경로 모두 설정값과 열린 트랜잭션 유무를 검사합니다.

두 행을 차례로 되돌리는 중 둘째 행에서 시간 초과가 나면 첫 행도 롤백해야 합니다. 이미 실행한 첫 UPDATE를 되돌리는 것은 Python 예외 이름이 아니라 바깥 DB 트랜잭션입니다. 오류가 난 문맥을 정상적으로 빠져나오게 예외를 전파하고, 연결이 IDLE 상태인지 확인합니다. 실패한 트랜잭션을 열어 둔 채 다음 SQL부터 재시도하면 오류만 겹칠 수 있습니다.

현장에서 만나는 모습

이번에는 잠금 획득 실패의 SQLSTATE 55P03만 제한적으로 재시도합니다. 문장 취소 57014, 승인 내용 충돌, 입력 오류, 원인을 모르는 연결 오류는 호출자에게 전달합니다. 오류 메시지를 한국어 또는 영어 문자열로 비교하지 않고 드라이버의 오류 분류를 사용합니다. 분류만으로 모든 상황의 재시도 안전성이 보장되는 것은 아닙니다. 이 예제는 보상 요청 ID와 승인 내용이 고정되고, 실패한 시도의 트랜잭션이 롤백되는 계약을 함께 사용합니다.

retry_undo의 attempts는 최초 호출을 포함한 총 시도 수입니다. 3이면 최초 한 번과 재시도 두 번입니다. 대기 함수는 실패한 시도 번호 1, 2를 받으며 마지막 실패 뒤에는 호출하지 않습니다. 서비스에서는 이 대기 함수에 상한 있는 지연과 흔들림을 넣을 수 있지만, 이 수업은 횟수 제한만 구현합니다. 전체 경과 시간 마감이나 모든 서버의 동시 요청 수 제한을 구현했다고 부르지 않습니다.

잠금이 풀린 뒤에도 승인한 값이 같다는 보장은 없습니다. 기다리게 한 담당자가 값을 변경하고 커밋했다면 다음 시도는 Conflict로 멈춰야 합니다. 재시도를 성공시키려고 기대 버전을 현재 값으로 교체하면 원래의 승인 보호가 사라집니다. 기다림 때문에 실패한 요청과 승인 내용이 낡아서 거절된 요청의 다음 행동이 달라야 하는 이유입니다.

다음 확인에서 할 것

퀴즈에서 잠금·문장·시도 예산과 설정 누수를 구분합니다. 다음 모듈의 종합 실습에서는 다른 연결이 둘째 행을 잡게 하여 실제 55P03을 관측하고, 지연 트리거로 57014도 발생시킵니다. 같은 연결이 정리됐는지, 첫 행이 남지 않았는지, 잠금 해제 뒤 같은 보상 ID의 재시도가 한 번만 효과를 냈는지를 SQL로 확인합니다.

참고: [PostgreSQL 연결 기본 설정](https://www.postgresql.org/docs/16/runtime-config-client.html), [오류 코드](https://www.postgresql.org/docs/16/errcodes-appendix.html), [psycopg 트랜잭션 관리](https://www.psycopg.org/psycopg3/docs/basic/transactions.html). 2026-09-13 확인했으며 실제 실행 환경은 PostgreSQL 16과 psycopg 3.2.3입니다.