링크 접수와 장소 분석을 분리하고, 재시도해도 저장 결과가 남게 하기
링크는 202로 바로 접수하고, 분석이 일부만 성공해도 성공한 장소와 핀은 남긴 채 나머지만 재시도하도록 만들었습니다.
- 내가 맡은 범위
- Cloud Tasks 접수·워커 API, OIDC 인증, 장소·출처·방 핀 저장과 재시도 처리
- 선택과 이유
- 성공한 장소는 먼저 커밋하고, 재시도할 실패가 남았을 때만 503을 반환했습니다.같은 작업이 두 번 와도 활성 행 유니크 제약으로 중복 저장을 막았습니다.
- 트레이드오프
- 작업 상태 테이블과 폴링 API를 없애서 관리할 상태는 줄었지만, 202 응답만으로는 분석이 끝났는지 알 수 없습니다.
문제
GGUK는 Instagram 링크에서 장소를 뽑아 방에 저장합니다. 그 사이에 스크래핑, AI 분석, 지오코딩을 거치는데, 외부 서비스가 느리거나 장소 여러 개 중 일부만 처리되는 경우가 있습니다. 링크를 받았다는 응답을 저장까지 끝났다는 뜻으로 쓰면 사용자가 보는 것과 실제 데이터가 달라집니다.
맡은 일
AI 추출과 지오코딩은 팀원이 만든 코드를 쓰고, 저는 접수 API, 내부 워커, DB 저장을 연결했습니다. 접수할 때 방 멤버십과 URL을 확인하고, Cloud Tasks에 등록이 된 다음에만 202 Accepted를 돌려줍니다. 내부 워커는 OIDC로 인증합니다.
이렇게 한 이유
원래 있던 job 테이블, jobId, 폴링 API를 없애고 접수와 처리를 나눴습니다. 중간 상태를 맞출 일이 없어진 대신, 이 API의 응답은 “받았다”는 뜻일 뿐 결과 알림은 아니게 됐습니다.
워커는 장소마다 지오코더 1순위 후보를 저장합니다. 성공한 장소·출처·핀을 먼저 커밋하고, 일시적인 지오코딩 실패가 남아 있으면 503을 반환해 Cloud Tasks가 다시 보내게 했습니다. 이렇게 하면 같은 작업이 여러 번 들어올 수 있어서, 장소는 upsert하고 출처와 핀은 활성 행 유니크 제약으로 충돌을 처리했습니다.
코드
핀 INSERT 뒤에 붙는 충돌 처리입니다. 같은 방, 같은 장소에 삭제되지 않은 핀이 이미 있으면 넘어갑니다. 삭제된 핀은 제약 대상이 아니라서 나중에 다시 등록할 수 있습니다.
.onConflictDoNothing({
target: [pins.roomId, pins.placeId],
where: isNull(pins.deletedAt),
});
원본 코드 · place-result.repository.ts 180–183행
검증 결과
PR #74에서 실제 HTTP 요청과 embedded PostgreSQL로 접수→워커→저장, 부분 실패 후 재시도, 중복 배달, 삭제 후 재등록을 테스트했습니다. 유닛 147건, E2E 28건이 통과했습니다.
운영에서 링크 접수 응답은 0.2~0.7초였고, 분석 작업은 성공 73건 기준 중앙값 6.6초(p90 13.2초)가 걸렸습니다.