장기 실행 스크래퍼에서의 세 가지 메모리 누수 패턴 (그리고 968번의 Trustpilot 실행 후 내가 이를 포착한 방법)

발행: (2026년 5월 18일 AM 11:52 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

Alex Spinov

Introduction

스크래퍼에서 메모리 누수는 실행을 중단시키지 않습니다.
조용히 Apify 메모리 제한을 1 GB → 2 GB → 4 GB 로 올리고, 실행당 비용을 두 배로 늘리며, 보통 청구서에 나타나는 컴퓨트‑유닛 비용을 몇 주 뒤에야 발견하게 됩니다.

968개의 Trustpilot 실행(페이지당 80–300개의 리뷰, 누적 150 k 페이지 조회) 후에 나는 매 1 000 페이지마다 RSS를 샘플링하기 시작했습니다. 성장 패턴은 로그와는 다른 이야기를 보여주었습니다. 아래는 제가 32개의 공개된 Apify 액터에서 본 누수의 약 90 %를 차지하는 세 가지 패턴입니다.


1. The unbounded asyncio queue

가장 흔한 패턴. 생산자 코루틴이 소비자보다 URL을 더 빨리 가져오면, 메모리 내 큐가 실행 시간에 따라 선형적으로 증가합니다.

# leaks at high concurrency
queue = asyncio.Queue()          # no maxsize

async def producer():
    async for url in source:
        await queue.put(url)     # never blocks

async def consumer():
    while True:
        url = await queue.get()
        await process(url)       # slower than source

process()source보다 느리면(대부분의 JS‑렌더링 사이트에 해당) 큐가 쌓이게 됩니다. 12 000개의 리뷰를 가진 회사를 크롤링한 Trustpilot 실행에서는 큐가 최대 9 500개의 URL를 보유했으며, 이는 약 380 MB의 바이트 문자열에 해당합니다.

Fix

queue = asyncio.Queue(maxsize=200)   # producer blocks at 200

크기 제한이 있는 큐는 생산자를 대기시킵니다. 메모리는 평탄하게 유지되고, 처리량은 약간만 감소합니다.

# Example: detect growth per 1 k pages
first, last = _samples[-3], _samples[-1]
growth_per_1k = (last[1] - first[1]) / ((last[0] - first[0]) / 1000)
if growth_per_1k > 50:  # >50 MB per 1 000 pages
    print(f"LEAK ALERT: +{growth_per_1k:.1f} MB/1k pages")

1 000 페이지당 50 MB라는 임계값은 보수적인 값이며, 정상 상태 실행에서 20 MB를 초과하면 조사할 가치가 있습니다. 출력은 Apify 데이터셋으로 파이프되므로, 실행 전체에 걸쳐 grep할 수 있습니다.

The cost angle nobody mentions

메모리 누수는 스크래퍼를 충돌시키지는 않습니다. 대신 액터의 메모리 설정을 올리게 만들죠:

  • 1 GB → 2 GB: 초당 컴퓨트‑유닛 소비가 두 배가 됩니다
  • 2 GB → 4 GB: 비용이 네 배가 되며, 등등

4 GB: quadruples it vs the 1 GB baseline

Apify 요금제에서 4 GB 실행이 $0.0004 /CU‑second 라면, 동일한 실시간 동안 적절히 튜닝된 1 GB 실행 대비 약 4배의 비용이 듭니다. 968개의 Trustpilot 실행을 기준으로 하면, 연간 ≈ $120 정도를 추가로 지불하게 되며, 이는 순전히 운영상의 낭비입니다. 아무도 RSS를 프로파일링하지 않았기 때문이죠.

Conclusion

위 세 가지 패턴은 **생산 환경에서 마주한 누수의 ~90 %**를 설명합니다. 모든 장기 실행 스크래퍼에 RSS 프로브를 추가하고, 누수 임계값을 50 MB / 1k 페이지로 설정하면 다음 청구 주기가 아니라 첫 번째 개발 주기에서 문제를 잡을 수 있습니다.

0 조회
Back to Blog

관련 글

더 보기 »

클릭 (2016)

Article Click 2016https://clickclickclick.click/ Discussion Hacker News threadhttps://news.ycombinator.com/item?id=48187054 – 194 points, 41 comments...