Repository navigation
fix: 테일러 콜백이 접수 응답보다 먼저 와도 결과를 잃지 않게 함(P-2) - #98
Conversation
준비 건을 분석 서버의 접수 응답을 받은 뒤에야 저장해, 콜백이 먼저 오면 대상을 찾지 못하거나
같은 면접의 이전 시도에 결과가 붙었다. 늦게 온 접수 응답이 엔티티를 통째로 저장해 결과를 PENDING으로
되돌리기도 했다.
- 분석 서버를 부르기 전에 준비 건을 저장(커밋)하고, callbackUrl에 ?tailorId={id}를 붙인다(UriComponentsBuilder)
- 콜백 컨트롤러가 tailorId 쿼리를 읽어 넘기고, 콜백은 tailorId로 대상을 찾는다(1:1·N:1)
- 함께 온 interviewId·jobId·방식이 그 건과 어긋나면 반영하지 않는다. tailorId가 틀리면 다른 건으로 우회하지 않는다
- tailorId 없는 콜백(배포 전 요청)은 jobId로 찾고, 없으면 면접의 마지막 건이 그 jobId를 기다릴 때만 잇는다
- 접수 응답은 jobId가 비어 있을 때만 채우는 조건부 갱신으로 바꾼다. 상태를 건드리지 않는다
- 이미 확정된 건(재전송, 시간 초과 폴백)은 콜백 결과를 바꾸지 않는다. 넘기지 못한 건이면 전달만 다시 한다
- 분석 서버 요청이 실패하면 접수되지 않은 새 건은 지운다. 그사이 콜백이 와서 결과가 들어갔으면 남긴다
스키마 변경 없음. Analysis 변경 없음(callbackUrl을 불투명한 주소로 쓴다).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danjee2009
left a comment
There was a problem hiding this comment.
검토 범위: 준비 건 생성·콜백 상관관계·늦은 접수 응답 처리와 관련 테스트를 확인했습니다. GitHub 테스트 체크는 검토 시점에 진행 중이었습니다.
배포 전에 나간 요청의 콜백(tailorId 없음)을 jobId로 못 찾으면 면접의 마지막 PENDING 건에 이었다. 그 건이 배포 뒤 새 시도라 아직 jobId가 비어 있으면, 이전 요청의 질문이 새 시도에 저장될 수 있었다(PR #98 리뷰). 면접 id는 시도를 가리지 못하므로 그 경로를 없앤다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
findTarget()의 tailorId == null 분기는 jobId로 찾은 엔티티를 즉시 반환해서, 아래의 interviewId·mode 검증을 건너뜁니다. PR 설명의 “함께 온 면접·방식이 어긋나면 반영하지 않는다”는 보장이 구형 콜백에는 적용되지 않습니다. |
tailorId 없는 콜백은 jobId로 찾은 건을 곧바로 돌려줘, tailorId로 찾은 콜백이 거치는 면접·방식 검사를 건너뛰었다 (PR #98 리뷰). 두 경로가 같은 검사를 거치도록 찾는 부분만 나눈다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
#98 — [P2] 대기에서 이어가는 경로는 선도착한 성공 결과를 다시 실패로 덮습니다. |
사이클을 기다리던 준비를 이어가다 접수 요청이 실패하면 resumeWaiting이 읽어 둔 엔티티를 FAILED·questions=null로 통째로 저장했다. 그사이 콜백이 먼저 와서 결과를 확정·전달했으면 그 결과가 DB에서 사라졌다(PR #98 리뷰). 시간 초과 처리와 같은 조건부 차지(claimExpiration: PENDING·WAITING일 때만)를 얻은 경우에만 실패로 닫고 알린다. 차지하지 못하면 확정된 결과를 그대로 둔다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[P2] 콜백과 실패 처리가 같은 준비 건을 서로 덮어쓸 수 있습니다. — QuestionTailorService.java:661
|
콜백은 PENDING을 읽고 확인한 뒤 엔티티를 따로 저장했다. 그사이 실패 처리(시간 초과, 사이클 대기에서 이어가기 실패)가 claimExpiration으로 먼저 차지하면 콜백이 그 위에 결과를 덮거나, 실패 처리가 들고 있던 낡은 엔티티로 콜백 결과를 다시 지울 수 있었다(PR #98 리뷰). - settleCallback: 아직 PENDING일 때만 상태·tailored·questions·오류·jobId(빈 경우만)를 한 문장으로 고친다. 실패 처리의 claimExpiration과 둘 다 조건부 갱신이라 한쪽만 이긴다. 진 콜백은 아무것도 쓰지 않는다 - 콜백 경로의 엔티티 통째 저장을 없앤다. 채팅 서버 전달은 확정이 커밋된 뒤에 한다 - 실제 DB에서 두 순서를 교차시키는 테스트와, 이어가다 콜백이 먼저 확정한 뒤 요청이 실패하는 시나리오 테스트를 추가한다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[P2] 조회한 엔티티를 수정하면 조건부 UPDATE 전에 결과가 저장될 수 있습니다. — QuestionTailorRepository.java:133
|
open-in-view가 켜져 있어 HTTP 콜백 동안 조회한 준비 건이 관리 상태로 남는다. 그 엔티티에 결과를 적은 뒤 settleCallback을 부르면 flushAutomatically가 쿼리 전에 변경을 먼저 flush해, SUCCEEDED가 조건 없이 저장되고 WHERE status = PENDING이 어긋나 확정이 0건으로 끝났다. 채팅 서버 전달과 준비 완료 알림이 빠졌다(PR #98 리뷰). - 콜백(1:1·N:1)은 조회한 엔티티를 고치지 않고 toBuilder 복사본에 결과를 계산해 조건부 확정에 넘긴다 - MockMvc로 실제 HTTP 콜백(open-in-view 적용)을 보내는 테스트를 추가한다. tailorId 경로와 배포 전 jobId 경로 모두 수정 전 코드에서 실패하고 수정 후 통과한다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
개요
A-1 P-2 "테일러 콜백 선도착 시 결과 보존"입니다. main(bd6663e) 기준입니다. 스키마와 Analysis 계약은 바뀌지 않습니다.
question_tailor)을 분석 서버의 접수 응답(202)을 받은 뒤에야 저장했습니다. 그래서 콜백이 먼저 오면jobId로도 찾지 못했습니다.interviewId로 찾으면 같은 면접의 이전 시도에 결과가 붙기도 했습니다.tailorId)를 실어 보내고, 콜백은 이 id로 대상을 찾습니다.변경
1. 요청 (
QuestionTailorService.startTailor)callbackUrl에?tailorId={id}를 붙입니다.UriComponentsBuilder를 써서 주소에 이미 붙은 쿼리는 그대로 둡니다.jobId만 채웁니다. 조건부 갱신(recordJobId:jobId가 비어 있을 때만)이라 상태를 건드리지 않습니다.discardUnaccepted:jobId가 없고 PENDING일 때만). 그사이 콜백이 와서 결과가 들어갔으면 남깁니다.claimExpiration)로 닫습니다. 이 차지는 아직 PENDING·WAITING인 건만 얻을 수 있습니다. 그사이 먼저 온 콜백이 확정·전달한 결과는 실패로 덮지 않습니다(리뷰 반영).2. 콜백 (
QuestionTailorController,handleCallback·handleMultiCallback)tailorId쿼리(선택)를 읽어 넘깁니다. 본문 형식은 같습니다.tailorId로 대상을 찾습니다. 함께 온interviewId·jobId·방식(SOLO/MULTI)이 그 건과 어긋나면 반영하지 않고 warn을 남깁니다.tailorId가 틀렸을 때 다른 건으로 우회하지 않습니다.tailorId가 없으면 배포 전에 나간 요청으로 봅니다. 이때는jobId로만 찾고, 찾은 건에도 위와 같은 면접·방식 검사를 합니다. 면접 id로는 잇지 않습니다 — 같은 면접의 새 시도(아직jobId가 빈 건)에 이전 요청의 결과가 들어갈 수 있습니다(리뷰 반영). 배포 전에 나간 요청 가운데 접수 응답보다 콜백이 먼저 온 건은 지금처럼 시간 초과로 처리됩니다.settleCallback한 문장으로 확정합니다. 아직 PENDING일 때만 상태·tailored·질문·오류·jobId(비어 있을 때만)를 함께 고칩니다. 실패 처리의claimExpiration도 조건부 갱신이라 둘 중 먼저 차지한 쪽만 쓰고, 진 쪽은 아무것도 쓰지 않습니다(리뷰 반영). 콜백 경로에서 엔티티를 통째로 저장하던 부분은 없앴습니다. 채팅 서버 전달은 확정이 커밋된 뒤에 합니다.toBuilder)에 계산합니다. open-in-view가 켜져 있어 HTTP 콜백 동안 조회한 엔티티가 관리 상태로 남습니다. 그 엔티티를 고치면 조건부 확정 직전에 변경이 먼저 flush되어, 확정이 0건으로 끝나고 채팅 서버 전달이 빠지기 때문입니다(리뷰 반영).외부에서 보이는 동작 변화
tailorId, 식별자 불일치Analysis 변경은 없습니다. Analysis는
callbackUrl을 불투명한 주소로 씁니다. 사이클 콜백도 이미cycleId·requestNo쿼리를 씁니다.테스트
./gradlew test전체 652개가 통과했습니다.QuestionTailorEarlyCallbackTest(신규,@SpringBootTest): 실제 DB에서 확인합니다. 콜백은 다른 스레드(별도 트랜잭션)에서 처리합니다.tailorId경로와 배포 전jobId경로 모두 결과를 확정하고 채팅 서버로 넘깁니다. 수정 전 코드에서는 두 경우 모두 실패했습니다.jobId만 남기고 PENDING으로 기다립니다.QuestionTailorServiceWaitingTest에 2건을 추가했습니다. 이어가다 요청이 실패해도 먼저 확정된 결과는 덮지 않고, 아직 기다리는 건이면 실패로 닫고 알립니다.QuestionTailorServiceCallbackTest에 9건을 추가했습니다. 다른 처리가 먼저 확정했으면 콜백이 결과를 쓰지 않는 경우도 포함합니다.jobId가 아직 없어도tailorId로 찾습니다.tailorId면 우회하지 않습니다.jobId·방식이 어긋나면 반영하지 않습니다.jobId로만 잇고, 못 찾으면 면접으로 잇지 않습니다.handleCallback·handleMultiCallback호출에tailorId자리(null, 배포 전 콜백과 같은 경로)만 더했습니다. 콜백 결과 확인 헬퍼는save대신settleCallback인자를 보게 바꿨고, 기본 목에settleCallback → 1을 더했습니다.알_수_없는_콜백은_저장하지_않는다의 "저장 안 함" 확인도 같은 뜻으로settleCallback을 보게 바꿨습니다. 단언문을 바꾼 기존 테스트는jobId로_못_찾으면_interviewId로_매칭한다하나입니다. 동작을 일부러 바꿔서jobId로_못_찾으면_면접으로_잇지_않는다로 대체했습니다.참고
QuestionTailorService의 요청 빌더를 고칩니다. 명세대로 이 PR을 먼저 병합하고, #97이 그 위로 충돌을 정리합니다.🤖 Generated with Claude Code