refactor: AI 네트워크 호출이 transaction.atomic 내부에서 실행되는 문제 개선 - #50
Conversation
|
AI 네트워크 호출을
다만 네트워크 호출과 DB 저장 사이에 입력 데이터가 변경될 수 있는 경쟁 상태가 남아 있어, 머지 전에 보완이 필요해 보입니다. 1. AI 호출 이후 저장 전에 입력 데이터가 그대로인지 다시 확인해주세요현재 흐름은 다음과 같습니다. study_material.extracted_text로 AI 호출
→ 네트워크 응답 대기
→ _save_tasks_with_estimates()에서 작업 저장AI 호출 중에 현재 예를 들면 다음과 같습니다.
DB 저장 트랜잭션에서 material = (
StudyMaterial.objects
.select_for_update()
.select_related("exam")
.get(pk=study_material_id)
)
if material.analysis_status != MaterialStatus.PROCESSING:
raise StaleAnalysisRequestError(...)
if material.extracted_text != analyzed_text:
raise StaleAnalysisRequestError(...)
추가로 화면·View에서도 AI 분석 중에는 PDF 재추출이나 입력 내용 변경을 막아주면 좋지만, View 방어만으로 끝내지 않고 저장 직전 DB 상태를 재검증해야 합니다. 2. 예상시간은 저장 시점의 최신
|
|
말씀하신 4가지 다 반영했습니다.
추가로 만든 테스트 test_stale_extracted_text_discards_result — 텍스트가 바뀌면 결과가 저장 안 되고 FAILED로 마무리되는지 검증 결과 python manage.py test exams.tests.AITransactionIsolationTestCase → Ran 5 tests, OK 머지되면 이슈 2(analysis_run_id, 실행 소유권)를 이 위에서 이어가겠습니다. |
|
이전 리뷰에서 요청드린 네 가지 수정사항은 모두 반영된 것을 확인했습니다.
전체적인 수정 방향은 좋습니다. 다만 PDF 재추출과 동시에 실행되는 경우의 경쟁 상태가 한 가지 남아 있습니다. 현재 PDF 추출은 아래 순서로 진행됩니다. 반면 current.analysis_status == MaterialStatus.PROCESSING
current.extracted_text == analyzed_text따라서 PDF 재추출이 시작됐지만 아직 새 텍스트 추출이 끝나지 않은 시점에는 저장 직전 검증에 텍스트 추출 상태도 포함해주세요. if current.status != MaterialStatus.COMPLETED:
raise StaleAnalysisRequestError(
"텍스트 추출 상태가 변경되어 분석 결과를 저장할 수 없습니다."
)관련 테스트도 추가 부탁드립니다. 추가로 PDF 추출 담당은 BE2이므로, 이 부분까지 반영되면 PR #50은 머지해도 될 것 같습니다. |
|
analysis_orchestrator.py — _save_tasks_with_estimates() 저장 직전 검증에 status(텍스트 추출 상태) 확인 추가 |
|
이전 리뷰에서 요청드린 PDF 재추출 진행 상태 검증, 재추출 조건부 UPDATE, 재추출 성공 시 AI 분석 상태 초기화와 관련 테스트가 모두 반영된 것을 확인했습니다. 다만 PDF 추출 시작과 AI 분석 시작이 동시에 성공할 수 있는 경쟁 상태가 하나 남아 있습니다. 현재 material_analyze()는 파이썬에서 material.status == COMPLETED를 확인한 뒤 _start_processing()을 호출하지만, _start_processing()의 조건부 UPDATE는 analysis_status만 검사하고 추출 status는 검사하지 않습니다. 따라서 아래 순서가 가능합니다. AI 분석 요청이 status=COMPLETED 확인 저장 직전 검증으로 StudyTask 저장은 막을 수 있지만, 이후 _finish_failure()가 조건 없이 analysis_status=FAILED를 저장하므로 재추출 성공 후 초기화된 PENDING 상태를 이전 AI 요청이 다시 덮어쓸 수 있습니다. 최초 분석과 재시도 양쪽 _start_processing() 조건에 아래 추출 완료 조건을 추가해주세요. status=MaterialStatus.COMPLETED 이렇게 하면 재추출 측은 analysis_status=PROCESSING을 차단하고, AI 분석 측은 status=PROCESSING을 차단해서 어느 요청이 먼저 시작하든 다른 요청이 원자적으로 실패하게 됩니다. 관련 테스트도 단순 순차 호출이 아니라, AI 분석이 상태를 읽은 직후 PDF 추출이 먼저 PROCESSING을 차지하는 순서를 재현해 추가 부탁드립니다. 추가로 추출 status=PROCESSING일 때 현재 메시지가 이미 분석 중인 자료입니다.로 되어 있는데, AI 분석 상태와 구분하기 위해 이미 PDF 텍스트를 추출 중인 자료입니다.로 수정해주세요. 마지막으로 material_extract()는 BE2 담당 코드이므로, 이번 연동 방어 변경은 BE2에게도 명시적으로 확인받은 뒤 머지하면 좋겠습니다. |
|
지적하신 경쟁 상태 반영했습니다.
test_initial_analysis_blocked_when_extraction_wins_race_after_status_check View가 material.status==COMPLETED를 확인한 시점(인메모리 객체는 COMPLETED로 남음) 직후 DB에서 PDF 재추출이 먼저 status=PROCESSING을 차지하는 상황을 그대로 재현해서, _start_processing()이 인메모리 값이 아니라 DB를 다시 조건부로 확인하는지 검증했습니다.
검증 결과 python manage.py test → Ran 171 tests, OK |
|
이전 리뷰에서 요청드린 PDF 추출·AI 분석 시작 경쟁 상태는 반영된 것을 확인했습니다.
위 방향은 적절합니다. 다만 재추출 성공 시 분석 상태를 초기화하는 처리에 한 가지 보완이 필요합니다. 현재 material.analysis_status = MaterialStatus.PENDING
material.analysis_error_message = None
material.analysis_retry_count = 0이 경우 AI 분석 재시도 2회를 모두 사용한 자료도 같은 PDF를 다시 추출하면 PR 설명대로 “텍스트가 실제로 변경된 경우”에만 분석 상태를 초기화하도록 기존 텍스트와 새 추출 결과를 비교해주세요. previous_extracted_text = material.extracted_text
...
if extracted != previous_extracted_text:
material.analysis_status = MaterialStatus.PENDING
material.analysis_error_message = None
material.analysis_retry_count = 0관련해서 다음 테스트도 추가하면 좋겠습니다.
이 부분까지 반영되면 PR #50은 머지해도 될 것 같습니다. |
|
material_extract()에서 재추출 직전 previous_extracted_text로 기존 텍스트를 미리 캡처해두고, 재추출 성공 시 extracted != previous_extracted_text일 때만 analysis_status/analysis_error_message/analysis_retry_count를 초기화하도록 변경했습니다. 텍스트가 동일하면(같은 PDF 재업로드 등) 기존 분석 상태를 그대로 유지합니다. python 테스트 추가 test_extract_success_keeps_analysis_state_when_text_unchanged — analysis_status=FAILED, analysis_retry_count=2(재시도 소진)인 자료가 동일한 텍스트로 재추출에 성공해도 FAILED/retry_count=2가 그대로 유지되는지 확인했습니다. 기존 test_extract_success_resets_stale_analysis_state(텍스트가 실제로 바뀌는 케이스)는 그대로 유지됩니다. 검증 결과 python manage.py test → Ran 172 tests, OK |
close #47
작업 내용
task_extractor.py: analyze_study_material()을 fetch_extracted_tasks()(AI 호출, 트랜잭션 없음) / save_extracted_tasks()(DB 저장, 짧은 트랜잭션)로 분리
analysis_orchestrator.py: _run_analysis_and_estimate()도 동일 구조로 재설계 (기존 외부 트랜잭션이 AI 호출을 계속 감싸고 있던 문제 해결)
tests.py: 기존 mock 패치 대상 재조정 + AITransactionIsolationTestCase 신규 추가 (AI 호출 시점에 실제로 트랜잭션이 없는지 직접 검증)
검증 결과
python manage.py test exams.tests.AITransactionIsolationTestCase → Ran 2 tests, OK
python manage.py test → Ran 153 tests, OK