kone
ainovel

ainovel

시마린 번역기 메모리 누수 코드베이스 분석 요청해봄

02/13/2026, 07:48:46
잡담
1433 views · 2 likes

요약하자면 

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]

번역 파이프라인 오케스트레이션


🔴 위험도 높음: 핵심 메모리 누수 후보

1. EPUB 파일 콘텐츠 3중 복제

[!CAUTION] EPUB의 전체 ZIP 콘텐츠가 메모리에 최소 3번 복제됩니다. 이미지가 많은 책일수록 영향이 큽니다.

[epub.py#L84-L93]에서 ZIP의 모든 파일(이미지 포함)을 self._contents에 로드합니다:

python
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에서 챕터별로 복제:

python
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]

python
def run(self):
    self._execute(1)      # 1차: Epub 객체 생성 → 전체 메모리 로드
    time.sleep(10)
    self._execute(2)      # 2차: 또 다른 Epub 객체 생성 → 또 전체 메모리 로드

1차 _execute에서 생성된 book 객체가 명시적으로 해제되지 않으므로, 2차 실행 시 1차의 book이 아직 메모리에 남아있을 수 있습니다.

메모리 영향 예시:

  • EPUB 50MB → _contents + _original_contents + seamarine_originals/ ≈ ~100-150MB

  • 2차 _execute 시 추가 → 총 ~200-300MB


2. Divide & Conquer 재귀 깊이 미제한

[!WARNING] 특정 콘텐츠에서 API 오류가 반복되면 재귀적으로 JSON을 분할하여 메모리가 기하급수적으로 증가합니다.

[translate_core.py#L246-L313]

python
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 파라미터가 있지만 깊이 제한 조건이 없음

  • 400/500 에러 반복 시 계속 재귀 → 중간 결과가 스택에 누적

  • "누더기 프롬프트"가 이 경로를 빈번하게 트리거할 가능성이 높음


3. 번역 루프 내 반복적 dict 정렬/재생성

[main_translator.py#L166-L176]

python
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 직렬화 중 추가 메모리
  • 매 청크 완료마다 전체 dict의 새 복사본 생성

  • 번역 후반부(68%, 82%)에서 dict가 커질수록 영향 증대


🟡 위험도 중간

4. 챕터별 BeautifulSoup DOM 전체 보존

python
# 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 전체를 복사합니다.

5. ThreadPoolExecutor Future 결과 누적

모든 청크를 한번에 submit하여 Future 객체와 완료된 결과가 as_completed로 소비되기 전까지 메모리에 누적됩니다.

6. genai Client 재생성

키 로테이션 시 genai.Client()를 새로 생성하며, 이전 Client의 내부 HTTP 연결 풀이 즉시 해제되지 않을 수 있습니다.


📊 커뮤니티 이슈 증상과의 매핑

커뮤니티 보고

코드 내 원인

위험도

68%에서 1차 폭증

#1 + #3: dict 정렬 비용 증가

🔴

82%에서 2차 폭증

#1: 2차 _execute 시 새 Epub 로드

🔴

누더기 프롬프트만 문제

#2: 복잡 프롬프트 → D&C 재귀 다발

🔴

같은 레이블 다른 책 OK

책 콘텐츠 특성이 D&C 유발 빈도 결정

🟡


💡 개선 제안 요약

  1. _execute 간 book 명시적 해제del book + gc.collect()

  2. D&C 깊이 제한level >= 5일 때 예외/빈 결과 반환

  3. dict 정렬은 루프 종료 후 1회만 수행

  4. EPUB 이미지 lazy loading: 번역 시 이미지는 메모리에 로드 불필요

  5. 챕터별 스트리밍 처리: 모든 DOM 동시 유지 방지

  6. Future batch 실행: 한번에 모든 chunk submit 대신 batch로 나눠 실행

2
0 comments
미국, 가로채기부터 시작되는 자유로운 인생 1-174
[패러디]
kone1133
02/20 3785 49
환생어촌_마을 미녀 아향을 가로채는 것부터 시작한다_重生渔村_从截胡村花阿香开始 1-1646(완결 중복수정)
[일반]
kone1133
02/20 3389 53
복구_초신기계사 1-1463외전-2(완)
[복구]
nekoko01
02/20 1992 15
신비제왕 패러디 복구 몇개
[복구]
ddt1412
02/20 3554 45
[목소리의형태]내 소꿉여자친구는 니시미야 쇼코我的青梅女友是西宫硝子 1-421
[패러디]
hyun6871
02/20 3941 40
피난처 진화론:살점과 뼈의 피난처 1~635
[일반]
Aiaks
02/20 3929 47
내 연인의 어머니는, 사실 현역 J컵 AV 여배우고, 딸의 남친과 하고 싶어 하는 씹변태 암컷이었다3
[야설]
greener-straper
02/20 12244 39
스피드 쌀먹 공유 모음 20260220
[일반]
tassdar
02/20 6565 92
나, 손산, 과거我,孙山,科举1-1101
[복구]
ks9683022
02/20 3435 18
내 애인의 어머니는 사실 현역 J컵 AV 여배우로, 딸의 남자친구와 하고 싶어 안달 난 암컷이었다 1~2 권
[복구]
ks9683022
02/20 2426 15
스톰윈드 노동실록 검수판
[복구]
tassdar
02/19 3426 34
[몬헌x다중]몬스터헌터와 몬스터에게 헌팅당하는 자들怪物猎人与怪物虐人 1-471 (완결)
[패러디]
hyun6871
02/19 3584 37
[다중]내 2차원 환생이 어째서 괴담 미스터리지 -3권 번외1 12장
[패러디]
whenevereven
02/19 3072 55
스피드 쌀먹 공유 모음 20260219
[일반]
tassdar
02/19 7866 121
이것은 우리의 워해머 여정이다 这是我们的战锤之旅 1-560
[패러디]
basileus31
02/19 2699 40
[타입문] 성배전쟁에서 시작되는 구원1-192
[패러디]
金日成綜合大學
02/19 2772 29
[나루토] 마다라의 젊음 회복과 코노하 반격을 돕는 이야기1-207
[패러디]
金日成綜合大學
02/19 3356 20
성진지주 641장 1241화
[복구]
wlsdnr13
02/19 2090 21
https://www.ciweimao.com/ 추출 툴(무식하게 OCR로 박는 용)
유틸
naxlamas
02/18 9087 17
신화 해석 줄거리를 아는 내가 무적이 되다 1-1462
[일반]
almost
02/18 3872 60
사이버펑크에서 시작하는 차원 공학 1-1015 完 (주인공 이름 '추가' 수정)
[패러디]
nzoe123
02/18 3455 46
[붕괴 스타레일]선주 나부에서 모바일 게임을 개발합니다 1-1061
[패러디]
tarclu
02/18 4767 33
쌀!!!!! 비싼 2알!!!!
[선협]
afjtj4bgjejwnf
02/18 13747 98
[명일방주] 누가 욕구불만이라는거야说谁X压抑呢!1-242
[패러디]
naxlamas
02/18 3391 56
나는 제2차 세계대전에서 문호가 되었다. 1~193
[일반]
241241135
02/18 3232 32
스피드 쌀먹 공유 모음 20260218
[일반]
tassdar
02/18 6688 101
[타입문] 아이언맨 토오사카 토키오미 1-141
[패러디]
金日成綜合大學
02/18 2837 17
금풍세우루 루주로 시작해서, 검 한자루로 천하제패 444-1800
[일반]
quer99
02/18 2458 17
[봇치더록]봇치짱을 괴롭히고 싶어我好想欺负波奇酱 (완결)
[복구]
hyun6871
02/18 3128 20
사이버펑크2077 게임 모드 설치 1-173(완결)
[패러디]
kone1133
02/18 3024 40