한줄 요약:
자동화 규모가 커지면 “하루 5개 URL”이 “하루 500개 URL”이 된다.
👉 이때 필요한 건 멀티스레드/비동기/작업 큐/캐싱/병렬 LLM 호출 최적화다.
🎯 목표
- 멀티스레드/비동기로 크롤링/HTML 처리 속도 3~20배 향상
- 작업 큐(Queue) 도입으로 안정성 & 처리량 상승
- 캐싱으로 같은 요청 중복 처리 방지
- LLM 호출 정렬(batch)·요약단위 조정으로 비용·속도 최적화
1. 병목(Bottleneck) 분석부터 시작
자동화 파이프라인은 보통 이렇게 생긴다:
(1) URL 요청 → (2) HTML 파싱 → (3) 요약 → (4) LLM 종합리포트 → (5) 포스팅
이중 가장 느린 부분은:
| 단계 | 병목(원인) |
|---|---|
| (1) HTTP 요청 | 네트워크 지연, 요청 수 많아지면 느림 |
| (3) Sumy/Yake | CPU 단일 스레드 |
| (4) LLM 호출 | 레이트 리미트, 응답시간 |
➡️ 그래서 병렬화·비동기 처리가 1·2·3단계에서 가장 큰 효과를 낸다.
2. 멀티스레드 크롤링(10~30배 빨라짐)
Python GIL 때문에 CPU에는 불리하지만 “I/O 작업(웹 요청)”에서는 거의 100% 효율
# fast_crawler.py
import concurrent.futures
import requests
def fetch(url):
try:
r = requests.get(url, timeout=5)
return url, r.text
except Exception as e:
return url, None
def fast_fetch(urls, max_workers=20):
results = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as ex:
for url, html in ex.map(fetch, urls):
results[url] = html
return results
20개씩 병렬 요청 → 20배 빨라짐
3. Async/Await 기반 대규모 크롤링(수백 단위 최적)
aiohttp + asyncio 기반 → 가장 빠른 방식
# async_crawler.py
import aiohttp, asyncio
async def fetch(session, url):
try:
async with session.get(url, timeout=8) as resp:
return url, await resp.text()
except:
return url, None
async def run(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, u) for u in urls]
return await asyncio.gather(*tasks)
def fetch_async(urls):
return asyncio.run(run(urls))
🚀 200개 URL도 5~8초 내 처리 가능.
4. CPU 작업(문서 요약/키워드) — 멀티프로세스
요약(Sumy) / 키워드(Yake)는 CPU 중심
➡️ 멀티스레드는 소용 없음, 반드시 멀티프로세스 필요
from multiprocessing import Pool
def summarize_block(text):
# sumy 요약 코드
return my_summary(text)
def multi_summarize(text_list):
with Pool(processes=6) as p:
return p.map(summarize_block, text_list)
CPU 코어 수만큼 병렬 요약 가능.
5. LLM 최적화 — “배치로 던져라”
대규모 데이터면 한 URL마다 LLM 호출하면 비용↑·속도↓
➡️ 여러 URL을 하나의 LLM 요청으로 묶는 방식 추천
# llm_batch_prompt.py
chunks = [
"\n".join(report_list[i:i+5]) # 5개 문서씩
for i in range(0, len(report_list), 5)
]
full_reports = []
for ch in chunks:
resp = client.responses.create(
model="o4-mini",
input=f"다음 소스 5개를 요약해줘:\n{ch}"
)
full_reports.append(resp.output_text)
효과:
- 호출 횟수 5배 감소
- 비용 절감
- 속도 상승
6. 큐 기반 처리(중규모~대규모 시스템 핵심)
자동화가 커지면 워크플로우는 “텍스트 파일들”이 아니라 “작업들(job)”이 된다.
추천 아키텍처
Producer(수집) → Queue(Redis/RabbitMQ) → Workers(요약/LLM) → Storage(DB) → Poster
파이썬 간단 버전:
from queue import Queue
from threading import Thread
q = Queue()
def producer(urls):
for u in urls:
q.put(u)
def worker():
while True:
url = q.get()
html = fetch(url)
summary = summarize(html)
save(summary)
q.task_done()
# 실행
for _ in range(10): # 워커 10개
Thread(target=worker, daemon=True).start()
producer(my_urls)
q.join() # 모든 작업 완료
효과:
- 워커 수 조절 → 처리량(Throughput) 조절 가능
- 중간 장애에도 큐에 남아 “재처리 가능”
- 대규모 시스템에서 업계 표준 패턴
7. 캐싱(중복 요청 방지)
동일 페이지 반복 크롤링 방지
➡️ 운영 시 비용·시간 절감
import hashlib, os
def get_cache_key(url):
return hashlib.md5(url.encode()).hexdigest()
def fetch_with_cache(url):
key = get_cache_key(url)
path = f"cache/{key}.html"
if os.path.exists(path):
return open(path, "r", encoding="utf-8").read()
html = requests.get(url).text
os.makedirs("cache", exist_ok=True)
open(path, "w", encoding="utf-8").write(html)
return html
8. 전체 성능 튜닝 조합 예시
| 단계 | 최적화 방식 |
|---|---|
| URL 크롤링 | ThreadPool(20~50) 또는 aiohttp |
| 본문 추출 | 멀티프로세스 Pool |
| 요약 | 멀티프로세스(코어 수 만큼) |
| LLM 종합보고 | Batch 처리 |
| 포스팅 | 단일(오류 시 재시도) |
| 전체 워크플로 | 큐 기반 + 장애복구 |
9. 실제 “최대 성능형” 워크플로 구조
[async crawler(200req/초)]
→ [멀티프로세스 요약 클러스터]
→ [LLM Batch Summarizer]
→ [큐 기반 Poster]
→ [WP REST API]
이 구성은
- 500~2000페이지 규모도
- 30~120초 내 처리 가능.
10. 체크리스트
- 크롤링: ThreadPool or aiohttp 비동기화
- 요약/키워드: 멀티프로세스
- LLM: Batch 호출
- 캐싱 적용
- 큐 기반 구조 도입
- throughput(처리량) 모니터링
- 병목 포인트 계속 측정
🎯 요약 한 줄
병목은 “네트워크·CPU·LLM”.
스레드 + 비동기 + 프로세스 + 배치 + 큐
이 5가지를 조합하면 자동화 시스템은 상업 서비스급 성능을 낼 수 있다 🚀
이전 강좌 👈 [운영#3] 장애 복구 자동화 (리트라이·백오프·대체 경로·휴면 후 재시도)
다음 강좌 👉 [운영#5] 비용 최적화 & 모델 선택 전략(o4-mini, gpt-4.1, embeddings, 캐시 전략)