본문으로 건너뛰기

Postgres 시퀀스가 ​​동기화되지 않음

증상

애플리케이션이 의도적으로 동일한 ID 값을 재사용하지 않는 경우에도 중복 키 또는 고유 제약 조건 오류로 인해 삽입 작업이 실패하기 시작합니다. 2026년 1월 25일 발생한 원래 사건에서는 OMR 결과 쓰기 중에 이 패턴이 나타났지만 데이터베이스 오류 모드는 일반적입니다.

가능한 원인

  • 과거 스크립트는 ID를 수동으로 삽입했으며 시퀀스를 진행하지 않았습니다.
  • 시퀀스 상태를 재설정하지 않고 로드된 행을 대량으로 가져오거나 복원합니다.
  • 한 테이블은 수동 ID에서 자동 증가로 전환되었으며 이전 행은 여전히 더 큰 값을 보유했습니다.

진단

시퀀스 상태를 실제 최대 행 ID와 비교합니다.

대표적인 SQL:

SELECT pg_get_serial_sequence('exam_results', 'examResultId');

SELECT last_value
FROM public."exam_results_examResultId_seq";

SELECT MAX("examResultId")
FROM exam_results;

last_value가 테이블의 실제 최대 ID보다 낮으면 다음에 생성된 값은 결국 기존 행과 충돌하게 됩니다.

수정

시퀀스를 현재 최대 행 ID로 재설정합니다.

대표적인 SQL:

SELECT setval(
'public."exam_results_examResultId_seq"',
(SELECT MAX("examResultId") FROM exam_results)
);

자체 시퀀스를 사용하는 관련 하위 테이블에 동일한 검사를 적용합니다.

예방

  • 수동 ID 삽입, 데이터 로드 또는 복원 작업 후에 setval(..., max(id))를 필수 후속 조치로 처리합니다.
  • 테이블당 하나의 ID 생성 전략을 유지합니다. 수동 ID와 시퀀스 기반 ID를 혼합하면 오류가 지연됩니다.
  • 동일한 고유 ID 오류가 여러 행에 동시에 나타나는 경우 애플리케이션 페이로드를 디버깅하기 전에 시퀀스를 검사하십시오.

원본 사고 세부사항

2026년 1월 25일 원본 사고는 OMR grading result path에서 발생했습니다. 이전 Python script workflow는 JSON/import row에 examResultId를 수동으로 넣었습니다. 이후 FastAPI/Prisma path는 id를 지정하지 않고 Postgres sequence에 의존했습니다.

세부사항
영향 tableexam_results
사용자 증상result insert 실패로 OMR grading이 끝나지 않았습니다
오류examResultId unique constraint failure
역사적 원인이전 Python/JSON upload path가 manual id를 insert했습니다
즉시 복구setval()로 sequence를 table maximum에 맞췄습니다

유용한 진단 신호는 같은 unique-id error가 한 malformed row가 아니라 여러 학생에게 나타났다는 점입니다. 이는 payload보다 id generator를 가리킵니다.

원본 기록은 old/new id range를 비교했습니다.

관찰의미
과거 row는 2909-2920 근처 id를 가졌습니다manual insert가 row id를 앞서 올렸습니다
새 sequence-backed row는 159-173, 이후 635-661 근처였습니다sequence가 따라오지 못했습니다
sequence가 과거 id에 도달하면서 collision이 시작됐습니다혼합 id strategy의 delayed failure였습니다

Data restore, manual import, table copy, id를 명시적으로 쓰는 migration 뒤에는 같은 방식으로 점검합니다.