요약하자면
1. 누더기 프롬프트가 특정 구문에서 검열 필터를 자극했고
2. 해당 청크가 무한 분할하면서 메모리가 폭발함
물론 나도 버그 재현해본건 아니라 틀릴 수도 있음
커뮤니티에서 보고된 "번역율 68%/82%에서 메모리 폭증" 이슈에 대한 코드 분석 결과입니다.
| 파일 | 역할 |
|---|---|
| [main_translator.py] | 메인 번역 루프 (2-Pass) |
| [translate_core.py] | Gemini API 호출 + Divide & Conquer |
| [epub.py] | EPUB 파일 로드/저장 |
| [translatable_xhtml.py] | 챕터별 HTML 파싱/번역 적용 |
| [home_view_model.py] | 번역 파이프라인 오케스트레이션 |
[!CAUTION] EPUB의 전체 ZIP 콘텐츠가 메모리에 최소 3번 복제됩니다. 이미지가 많은 책일수록 영향이 큽니다.
[epub.py#L84-L93]에서 ZIP의 모든 파일(이미지 포함)을 self._contents에 로드합니다:
with zipfile.ZipFile(file_path, 'r') as zin:
for name in zin.namelist():
self._contents[name] = zin.read(name) # 1차: 전체 ZIP 내용
그 후 [epub.py#L424-L437]의 _preserve_original_chapters에서 챕터별로 복제:
self._original_contents[chap] = self._contents[chap] # 2차: 원본 보관
self._contents[new_path] = original_html.encode('utf-8') # 3차: seamarine_originals/에 추가
그런데 _execute가 2번 호출됩니다 ([main_translator.py#L45-L55]
def run(self):
self._execute(1) # 1차: Epub 객체 생성 → 전체 메모리 로드
time.sleep(10)
self._execute(2) # 2차: 또 다른 Epub 객체 생성 → 또 전체 메모리 로드
1차 _execute에서 생성된 book 객체가 명시적으로 해제되지 않으므로, 2차 실행 시 1차의 book이 아직 메모리에 남아있을 수 있습니다.
메모리 영향 예시:
_contents + _original_contents + seamarine_originals/ ≈ ~100-150MB_execute 시 추가 → 총 ~200-300MB[!WARNING] 특정 콘텐츠에서 API 오류가 반복되면 재귀적으로 JSON을 분할하여 메모리가 기하급수적으로 증가합니다.
[translate_core.py#L246-L313]
def _divide_and_conquer_json(self, contents, level=0, ...):
json_dict = json.loads(contents)
mid = len(keys) // 2
first_half = {k: json_dict[k] for k in keys[:mid]} # 새 dict
second_half = {k: json_dict[k] for k in keys[mid:]} # 새 dict
# 재귀 — 깊이 제한 없음!
subresp_1 = self.generate_content(subcontents_1, level=level+1, ...)
subresp_2 = self.generate_content(subcontents_2, level=level+1, ...)
level 파라미터가 있지만 깊이 제한 조건이 없음[main_translator.py#L166-L176]
for future in as_completed(futures):
translated_text_dict.update(translated_chunk)
translated_text_dict = dict(sorted(translated_text_dict.items())) # 매번 새 dict!
with open(..., "w") as f:
json.dump(translated_text_dict, f) # JSON 직렬화 중 추가 메모리
# main_translator.py L116-L126
xhtmls: dict[str, utils.TranslatableXHTML] = {}
for chapter_file in chapter_files:
xhtmls[chapter_file] = utils.TranslatableXHTML(...) # 각각 BS4 DOM 보유
모든 챕터의 DOM이 동시에 메모리에 존재하며, get_translated_html()에서 추가로 DOM 전체를 복사합니다.
모든 청크를 한번에 submit하여 Future 객체와 완료된 결과가 as_completed로 소비되기 전까지 메모리에 누적됩니다.
키 로테이션 시 genai.Client()를 새로 생성하며, 이전 Client의 내부 HTTP 연결 풀이 즉시 해제되지 않을 수 있습니다.
| 커뮤니티 보고 | 코드 내 원인 | 위험도 |
|---|---|---|
| 68%에서 1차 폭증 | #1 + #3: dict 정렬 비용 증가 | 🔴 |
| 82%에서 2차 폭증 | #1: 2차 _execute 시 새 Epub 로드 | 🔴 |
| 누더기 프롬프트만 문제 | #2: 복잡 프롬프트 → D&C 재귀 다발 | 🔴 |
| 같은 레이블 다른 책 OK | 책 콘텐츠 특성이 D&C 유발 빈도 결정 | 🟡 |
_execute 간 book 명시적 해제: del book + gc.collect()level >= 5일 때 예외/빈 결과 반환