openlang

ON CONFLICT DO NOTHING과 재시도된 작업

유일 키 충돌마다 "이미 접수됨"으로 답하면 안전해 보여요. 그런데 이 답 하나가 서로 다른 세 상황을 가려요.

서버 두 대가 PostgreSQL 18 테이블 하나를 같이 쓰고, 이 테이블의 요청 식별자에는 유일 제약이 걸려 있어요. 저장하는 건 그 식별자뿐이고, 충돌은 어느 서버에서든 날 수 있어요. 클라이언트가 타임아웃 뒤에 재시도해요. 재시도에는 바뀐 페이로드가 실려 올 수 있어요. 첫 시도가 타임아웃 전에 이미 외부 작업을 일으켰을 수도 있고요.

ON CONFLICT DO NOTHING은 두 번째 행이 들어가지 않게 막아요. 하는 일은 거기까지예요. 페이로드를 비교하지 않고, 외부 작업이 어떻게 됐는지도 알려 주지 않아요. Go 요청 컨텍스트를 취소했다고 원격 작업이 없었다는 뜻도 아니에요.

그러니 두 번째 서버에는 식별자보다 많은 정보가 필요해요. 같은 내용의 재시도, 같은 식별자에 바뀐 입력, 결과를 아직 아무도 모르는 작업을 서로 구분할 수 있어야 해요. 그 세 가지에 답할 수 있는 가장 작은 영속 기록을 찾고 싶어요.

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    타임아웃을 한발 앞당겨서 외부 응답보다 먼저 오게 해 봐요. 클라이언트는 기다리기를 멈추지만 워커는 계속 일해요. 뒤늦게 성공이 도착하면 같은 영속 작업에 반영돼야 해요. 재시도가 이미 미확정 상태를 받았더라도요.

  2. 고유 링크

    먼저 불변식 하나를 세우겠습니다. 식별자 하나에는 접수된 페이로드 식별값이 정확히 하나 있고, 충돌이 났다는 사실만으로는 새 요청이 그 값과 같은지도, 작업이 끝났는지도 알 수 없습니다. 응답을 고르기 전에 저장된 행을 읽고 식별값을 비교하십시오. PostgreSQL 18의 Read Committed에서 평범한 SELECT는 그 명령이 시작될 때의 스냅숏을 봅니다. 그러니 보고할 상태를 어느 후속 쿼리로 읽는지 분명히 정해 두어야 합니다.

  3. 고유 링크

    제일 곤란한 지점은 타임아웃보다 좁아요. 외부 서비스가 작업을 받아들였는데, 워커가 결과를 쓰기 전에 죽어요. 슈퍼바이저가 다시 띄워요. 다시 뜬 프로세스는 어떤 상태부터 복원할 수 있나요? 저장된 작업에는 결과가 없고, 재시작만으로는 다시 제출해도 되는지 알 수 없어요.

    그래서 요청 식별자와 같은 행에 접수된 페이로드 식별값, 실행 상태, 하위 서비스의 상관 식별자, 최종 응답을 함께 저장해요. 새 워커는 그 행만 보고 다음 동작을 정해요.

    그래도 원격 쪽 성공이 기록보다 먼저 일어날 수 있어요. 외부 서비스가 상관 식별자로 작업을 조회할 수 있으면 대조해서 맞춰요. 멱등 키를 문서로 지원한다면 접수된 작업의 키를 넘겨요. 둘 다 없다면 결과를 미확정으로 두고, 다시 제출하기 전에 복구 정책부터 정해요.

    입력이 다른 재시도에는 불일치로 답하고 접수된 작업은 건드리지 않아요. 같은 재시도에는 저장된 결과나 실행 중 상태, 아니면 그 미확정 결과를 돌려줘요.

같은 질문의 다른 갈래

  1. doctest는 늘 순조로운 답장만 보여 줘요

    저는 doctest를 좋아해요. @doc 안의 iex> 줄이 doctest Counter로 실제로 돌아가니까 예제가 거짓말을 못 하고, 순수한 변환 파이프라인이라면 거의 완벽한 문서가 돼요. 그런데 다른 프로세스와 대화하는…