← 개발 로그 목록

LearningCollector: 이전 실행 실패 항목 재시도를 통한 안정성 향상

/ 12분 분량 / 개발 로그

이번 커밋은 이전 실행에서 'draft_created' 상태가 False로 실패했던 항목들을 다음 실행에서 자동으로 재시도할 수 있도록 `core/orchestrator.py` 파일을 수정했습니다. 이 변경을 통해 데이터 수집 및 처리 과정의 안정성이 향상되었으며, 놓치는 데이터 없이 꾸준히 수집하고 처리할 수 있게 되었습니다.

LearningCollector: 이전 실행 실패 항목 재시도를 통한 안정성 향상

이번 커밋은 이전 실행에서 'draft_created' 상태가 False로 실패했던 항목들을 다음 실행에서 자동으로 재시도할 수 있도록 core/orchestrator.py 파일을 수정했습니다. 이 변경을 통해 데이터 수집 및 처리 과정의 안정성이 향상되었으며, 놓치는 데이터 없이 꾸준히 수집하고 처리할 수 있게 되었습니다.

요약

이번 작업은 core/orchestrator.py 파일의 run() 함수에 _merge_pending() 단계를 추가하는 것을 중심으로 진행되었습니다. 이 새로운 단계는 json_saver.get_pending_jsons() 함수를 호출하여 이전 실행에서 초안 생성에 실패한 항목들을 가져옵니다. 그리고 이번 실행에서 새로 수집된 항목들과 병합하여, 실패했던 항목들도 다음 실행에서 다시 처리될 수 있도록 보장합니다. 이를 통해 run() 함수의 총 처리 항목 수를 계산하는 로직도 수정하여, 실제 처리될 항목 수를 정확히 반영하도록 변경했습니다. 총 37줄의 코드가 추가되었고 4줄의 코드가 삭제되었습니다.

배경 및 목적

소프트웨어 개발 과정에서 예상치 못한 오류나 외부 요인으로 인해 작업이 실패하는 경우는 빈번하게 발생합니다. 특히 데이터 수집 및 처리와 같이 여러 단계를 거치는 파이프라인에서는, 특정 단계에서 실패하더라도 전체 작업이 중단되지 않고 실패한 부분만 재시도할 수 있는 메커니즘이 중요합니다. 기존에는 draft_created 상태가 False로 기록된 항목에 대한 별도의 재시도 로직이 없어, 이런 항목들은 영구적으로 누락될 가능성이 있었습니다.

이 작업의 목적은 다음과 같습니다.

  • 안정성 향상: 이전 실행에서 발생한 'draft_created' 실패 항목들을 다음 실행에서 자동으로 재시도하여 데이터 누락을 방지합니다.
  • 견고한 파이프라인 구축: 예외 상황에서도 데이터 처리 흐름이 끊기지 않고 지속될 수 있도록 파이프라인을 더욱 견고하게 만듭니다.
  • 효율적인 리소스 활용: 실패한 항목들을 효과적으로 관리하고 재시도함으로써, 반복적인 수동 개입을 줄이고 리소스 활용도를 높입니다.

구현 내용

core/orchestrator.py 파일의 run() 함수와 관련하여 다음과 같은 변경이 이루어졌습니다.

주요 변경사항 상세 설명

  1. run() 함수의 흐름 변경: run() 함수 내에서 데이터 수집(_collect_all) 이후, 실제 수집 결과를 처리하기 전에 _merge_pending() 함수를 호출하도록 순서를 조정했습니다.
  2. _merge_pending() 함수 추가: 이 함수는 self.json_saver.get_pending_jsons()를 통해 이전 실행에서 'no_draft'로 분류된 항목들을 가져옵니다. 가져온 항목들을 현재 수집된 ai_chat_jsons, baekjoon_jsons, commit_jsons 리스트에 중복되지 않도록 추가합니다.
  3. 총 처리 항목 수 로직 수정: total_new 변수명을 total로 변경하고, _merge_pending() 함수를 통해 병합된 항목 수를 정확히 반영하도록 수정했습니다. 이를 통해 "새로 수집된 데이터가 없습니다"라는 메시지 대신 "처리할 데이터가 없습니다"와 같이 보다 포괄적인 메시지를 출력하도록 변경했습니다.

변경된 파일 목록

  • core/orchestrator.py

추가/삭제된 코드 라인 수

  • 추가: 37 라인
  • 삭제: 4 라인

핵심 코드 설명

# ... (이전 코드) ...

        # 2. 수집 결과 요약 및 처리
-        total_new = len(ai_chat_jsons) + len(baekjoon_jsons) + len(commit_jsons)
-
-        if total_new == 0:
+        # 2. 이전 실행에서 실패한 pending 항목 병합
+        ai_chat_jsons, baekjoon_jsons, commit_jsons = self._merge_pending(
+            ai_chat_jsons, baekjoon_jsons, commit_jsons
+        )
+
+        # 3. 수집 결과 요약 및 처리
+        total = len(ai_chat_jsons) + len(baekjoon_jsons) + len(commit_jsons)
+
+        if total == 0:
             print("\n" + "=" * 50)
             print("📊 수집 결과")
             print("=" * 50)
-            print("  새로 수집된 데이터가 없습니다.")
+            print("  처리할 데이터가 없습니다.")
         else:
             if self.auto:
                 self._auto_process(ai_chat_jsons, baekjoon_jsons, commit_jsons)

# ... (이후 코드) ...

+    def _merge_pending(self, ai_chat_jsons, baekjoon_jsons, commit_jsons):
+        """이전 실행에서 초안 생성 실패한 pending 항목을 병합"""
+        pending = self.json_saver.get_pending_jsons()
+        no_draft = pending["no_draft"]  # [(subdir, filename), ...]
+
+        if not no_draft:
+            return ai_chat_jsons, baekjoon_jsons, commit_jsons
+
+        # 이번에 새로 수집된 파일명 (중복 방지)
+        new_set = set(ai_chat_jsons + baekjoon_jsons + commit_jsons)
+
+        pending_count = 0
+        for subdir, filename in no_draft:
+            if filename in new_set:
+                continue
+            pending_count += 1
+            if subdir == "ai_chat":
+                ai_chat_jsons.append(filename)
+            elif subdir == "baekjoon":
+                baekjoon_jsons.append(filename)
+            elif subdir == "commits":
+                commit_jsons.append(filename)
+
+        if pending_count > 0:
+            print(f"\n  📋 이전 실행에서 미처리된 항목 {pending_count}개 발견 (재시도 대상)")
+
+        return ai_chat_jsons, baekjoon_jsons, commit_jsons

위 코드 조각에서 볼 수 있듯이, _merge_pending 함수는 pending 데이터에서 no_draft 리스트를 순회하며 각 파일명을 new_set과 비교하여 중복을 피합니다. 만약 중복되지 않는다면 해당 파일명을 적절한 리스트(ai_chat_jsons, baekjoon_jsons, commit_jsons)에 추가합니다. 또한, 재시도 대상 항목이 발견되면 사용자에게 알림 메시지를 출력합니다.

기술적 의사결정

이번 작업에서 특별히 새로운 라이브러리나 프레임워크를 도입하지는 않았습니다. 기존에 구현되어 있던 json_saver의 get_pending_jsons() 함수를 활용하는 것이 가장 효율적이고 자연스러운 방법이라고 판단했습니다.

  • 기술/라이브러리 선택: 기존 json_saver 모듈 및 해당 모듈의 get_pending_jsons() 메서드 활용.
  • 선택 이유:
    • 기존 코드 활용: 이미 구현되어 있고 테스트된 기능을 재사용함으로써 개발 시간과 노력을 절감할 수 있습니다.
    • 데이터 일관성: json_saver는 파일 시스템과 직접 상호작용하며 데이터를 저장하고 관리하므로, draft_created 상태와 같은 메타데이터를 가장 정확하게 파악하고 제공할 수 있습니다.
    • 모듈화: json_saver는 데이터 관리의 책임을 명확히 분리하고 있으므로, 이 기능을 활용하는 것이 코드의 모듈성을 유지하는 데 도움이 됩니다.
  • 다른 대안:
    • 새로운 데이터 저장소 사용: 예를 들어, 데이터베이스나 별도의 상태 관리 파일을 사용할 수 있습니다.
    • 인메모리 상태 관리: 프로그램 실행 중에만 상태를 유지하는 방식입니다.
  • 대안과의 비교 및 장단점:
    • 새로운 데이터 저장소: 장점은 강력한 데이터 관리 기능과 확장성을 제공할 수 있다는 것입니다. 하지만 이는 새로운 인프라 구축 및 관리 부담이 따르며, 기존 json_saver의 기능과 중복될 수 있습니다.
    • 인메모리 상태 관리: 장점은 구현이 간단하다는 것입니다. 하지만 프로그램이 종료되면 상태 정보가 사라지므로, 재시도 로직에는 부적합합니다.
      따라서 기존 json_saver를 활용하는 것이 현재 프로젝트의 구조와 목적에 가장 부합하는 최적의 선택이라고 결론 내렸습니다.

배운 점 및 개선점

이번 작업을 통해 이전 실행에서 실패했던 항목들을 어떻게 효과적으로 재시도할 수 있는지에 대한 좋은 경험을 얻었습니다. 특히, draft_created와 같은 상태 정보를 활용하여 파이프라인의 복원력을 높이는 것의 중요성을 다시 한번 깨달았습니다.

배운 점

  • 프로세스 실패 시 재시도 메커니즘의 중요성
  • 기존에 잘 설계된 모듈의 기능을 적극적으로 활용하는 방법
  • _merge_pending과 같이 특정 로직을 별도의 함수로 분리하는 코드 구조의 유용성

앞으로 개선할 점

  • _merge_pending 함수에서 pending_count를 로깅하는 것 외에, 재시도되는 항목들의 구체적인 파일명을 기록하여 추후 디버깅에 활용할 수 있도록 개선할 수 있습니다.
  • get_pending_jsons()에서 반환되는 no_draft 항목에 대한 최대 재시도 횟수를 제한하여, 무한 루프에 빠지는 것을 방지하는 로직을 추가할 수 있습니다.
  • json_saver의 get_pending_jsons() 함수가 더 명확하고 일관된 방식으로 실패 정보를 반환하도록 개선될 여지가 있는지 검토할 수 있습니다.

다음 단계 계획

  1. 테스트 강화: _merge_pending 함수가 다양한 시나리오(예: 아무런 실패 항목이 없을 때, 여러 항목이 실패했을 때, 새로 수집된 항목과 겹칠 때)에서 올바르게 작동하는지 자동화된 테스트 케이스를 작성하여 검증합니다.
  2. 모니터링 및 알림: 재시도 로직이 제대로 작동하고 있는지, 혹은 재시도에도 불구하고 실패가 반복되는 항목이 있는지 지속적으로 모니터링하는 시스템을 구축합니다.
  3. 문서화: _merge_pending 함수의 역할과 작동 방식에 대한 설명을 코드 내 주석이나 별도의 문서에 추가하여, 다른 개발자들이 쉽게 이해하고 활용할 수 있도록 합니다.

참고 자료

  • claude.ai/code/session_011gagwizSUXWiCxmjL8HWZq (주어진 커밋 메시지 내 참고 링크)