TL;DR — 병렬화는 서로 독립적인 작업을 동시에 실행해 전체 처리 시간을 단축하는 패턴입니다. 4개 작업이 각 2초라면 순차로는 8초, 병렬로는 약 2초(가장 느린 작업 시간)면 끝납니다. 핵심은 독립성·상태 충돌 방지(Reducer/output_key)·동시성 제어(Semaphore)이며, 실무에선 모든 걸 Agent로 만들지 않고 Function·Chain·Agent를 섞는 하이브리드로 LLM 비용을 최대 60%까지 줄입니다.
1. 왜 병렬화인가 — 기다림은 더해진다
1편 프롬프트 체이닝은 작업을 순서대로 이어 붙였고, 2편 라우팅은 입력에 따라 길을 나눴습니다. 그런데 현실에는 서로 의존하지 않는 작업이 많습니다. 기업 리서치를 한다고 해봅시다. 뉴스·주가·소셜·재무를 각각 2초씩 조회하는데, 이를 순서대로 호출하면 그냥 8초를 기다립니다. 네 번째 호출은 첫 번째 결과가 전혀 필요 없는데도요.
병렬화는 이렇게 독립적인 작업을 동시에 던지고 모두 끝나길 함께 기다립니다. 전체 시간은 합계가 아니라 가장 느린 작업 하나의 시간이 됩니다. 8초가 2초로 줄어드는 겁니다.
2. 병렬화란 — Fan-Out / Fan-In
병렬화의 대표 구조가 Fan-Out / Fan-In입니다. 하나의 시작점에서 여러 작업으로 분산(Fan-Out)하고, 작업들이 동시에 실행된 뒤, 결과를 하나의 집계 노드로 수집(Fan-In)합니다.
병렬화의 4대 가치
- 응답 시간 단축 — 합계가 아니라 최장 작업 시간으로 수렴
- 리소스 효율 — I/O 대기 시간 동안 다른 작업을 진행
- 사용자 경험 — 실시간에 가까운 종합 응답 제공
- 처리량 증가 — 동일 시간에 더 많은 분석·요청 소화
3. 동시성 vs 병렬성 — 헷갈리면 안 되는 구분
두 단어는 자주 혼용되지만 메커니즘이 다릅니다. 동시성(Concurrency)은 단일 스레드(asyncio)에서 I/O 대기 시간 동안 작업을 번갈아 처리하는 것이고, 병렬성(Parallelism)은 여러 CPU 코어(multiprocessing)에서 진짜로 동시에 연산하는 것입니다.
| 작업 유형 | 권장 방식 | 이유 |
|---|---|---|
| LLM·API 호출, DB 쿼리 | asyncio (동시성) |
I/O 바운드 · 네트워크 대기 |
| 이미지·비디오, 대규모 데이터 | multiprocessing (병렬성) |
CPU 바운드 · GIL 우회 필요 |
AI 에이전트가 다루는 작업의 대부분은 LLM·API 호출 = I/O 바운드입니다. 그래서 이 글의 구현은 모두 asyncio 기반 동시성을 사용합니다.
4. 병렬화의 진짜 함정 — 상태 충돌
여러 작업이 같은 상태 키에 동시에 쓰면 결과가 덮어써집니다. 병렬화 코드의 버그는 대부분 여기서 나옵니다. 해법은 두 가지입니다.
- 고유 키 분리 — 각 작업이 자기만의 키에 쓴다(ADK의
output_key). - Reducer로 누적 — 여러 작업의 결과를 안전하게 리스트에 합친다(LangGraph의
Annotated[list, operator.add]).
asyncio.gather(..., return_exceptions=True)로 예외도 결과로 받아 부분 실패를 허용하세요(12장).5. 구현 ① LangChain RunnableParallel
RunnableParallel은 딕셔너리에 체인을 담기만 하면 동시에 실행합니다. 가장 간단한 병렬화입니다. 아래는 하나의 주제에 대해 요약·질문·키워드·관련분야를 한꺼번에 만들고, 그 결과를 종합 체인으로 흘려보냅니다.
같은 작업을 await chain.ainvoke(...)로 하나씩 순차 실행하는 것과 비교하면 보통 3~4배 빨라집니다.
6. 구현 ② LangGraph Fan-Out / Fan-In
LangGraph는 그래프 엣지로 병렬을 표현합니다. START에서 여러 노드로 엣지를 그으면 그 노드들이 동시에 실행(Fan-Out)되고, 여러 노드가 한 노드를 가리키면 그곳에서 수렴(Fan-In)합니다. 병렬 결과는 Reducer로 안전하게 누적합니다.
각 분석 노드는 return {"analyses": [결과]}만 반환하면 됩니다. Reducer가 4개의 리스트를 자동으로 하나로 합쳐주므로 충돌이 없습니다.
7. 구현 ③ Google ADK ParallelAgent
ADK는 ParallelAgent에 sub_agents를 넣으면 동시에 실행하고, 각 에이전트의 output_key가 결과를 세션 상태에 저장합니다. 그 뒤 SequentialAgent로 통합 에이전트를 이어 붙이면 병렬 → 순차 파이프라인이 됩니다.
통합 에이전트는 {tech_analysis}처럼 중괄호로 이전 결과를 참조합니다. 각 작업에 서로 다른 output_key를 주는 것이 상태 충돌을 막는 핵심입니다.
8. 구현 ④ 순수 asyncio + Semaphore
가장 저수준이자 가장 유연한 방법입니다. asyncio.gather()로 여러 비동기 함수를 동시에 던지고, Semaphore로 동시 실행 수를 제한해 API Rate Limit을 보호합니다.
ADK에서는 도구를 async def로 정의해 FunctionTool로 감싸면, 에이전트(v1.10.0+)가 LLM이 요청한 도구들을 자동으로 병렬 실행합니다.
| 프레임워크 | 병렬화 메커니즘 | 특징 |
|---|---|---|
| LangChain | RunnableParallel (딕셔너리) | 가장 간단·선언적 |
| LangGraph | 다중 엣지 + Reducer | 복잡한 워크플로우 시각화 |
| Google ADK | ParallelAgent + output_key | 멀티 에이전트 협업 |
| 순수 Python | asyncio.gather + Semaphore | 세밀한 제어·재시도·타임아웃 |
9. 핵심 — 병렬화 대상은 꼭 Agent가 아니다
“Agentic Design Pattern”이라는 말 때문에 모든 작업을 Agent로 만들어야 한다고 오해하기 쉽습니다. 하지만 병렬화의 대상은 추상화 수준에 따라 Agent · Chain · Function · Primitive I/O 네 단계로 나뉩니다. DB 조회에까지 LLM을 부르는 건 낭비입니다.
고객 문의 처리에 5개 작업(고객정보·주문이력·유사문의 = Function, 감정분석 = Chain, 응답초안 = Agent)이 필요하다고 합시다. 전부 Agent로 만들면 LLM을 5번 부르지만, 작업별 최소 수준으로 섞으면 LLM 호출이 확 줄어듭니다.
RunnableLambda로 일반 함수를 감싸 Chain·Function을 하나의 RunnableParallel에서 섞을 수 있고, LangGraph 노드는 애초에 어떤 함수든 될 수 있어 하이브리드를 기본 지원합니다.10. 엔터프라이즈 적용 — CRM · ERP · Groupware
병렬화는 “여러 데이터 소스를 동시에 모아야 하는” 엔터프라이즈 업무에서 즉각적인 효과를 냅니다.
레거시 전환은 한 번에 갈아엎지 않습니다. Strangler Fig 패턴으로 병렬화 어댑터를 앞단에 두고 처리 비율을 10% → 50% → 100%로 점진 확대하며, 부분 실패에 대비한 서킷 브레이커와 롤백 계획을 함께 둡니다.
11. 케이스 스터디
① CRM 고객 360° 뷰
기본정보·구매이력·상담이력·추천·신용점수를 순차로 모으면 12초가 걸립니다. 다섯 소스는 서로 독립적이므로 API Gateway에서 동시에 호출하면 가장 느린 소스(3초)만큼만 걸려 3초로 끝납니다(75%↓).
② ERP 월말 마감 (Fan-Out/Fan-In)
매출·비용·재고·감가상각 집계는 서로 독립적이라 병렬로 돌리고, 그 결과가 모두 모인 뒤에만 가능한 결산 분개 → 보고서는 순차로 잇습니다. 8시간 마감이 3.5시간(56%↓)으로 단축됩니다. “무엇이 독립이고 무엇이 의존인지”를 가르는 게 설계의 전부입니다.
③ Groupware 병렬 결재
하나의 기안서를 재무·법무·보안이 동시에 검토(ParallelAgent)한 뒤, 세 의견이 모이면 임원이 순차 승인(SequentialAgent)합니다. 검토를 한 명씩 돌리던 결재가 크게 빨라집니다.
12. 베스트 프랙티스 & 트레이드오프
병렬화 전에 던질 질문
- 정말 독립적인가? — B가 A의 결과를 쓰면 병렬 불가(그건 체이닝)
- LLM이 꼭 필요한가? — 아니면 Function 수준으로 (9장 하이브리드)
- Rate Limit은? —
Semaphore로 동시 수 제한(여유 50~70%) - 부분 실패는? —
return_exceptions=True+ 서킷 브레이커
디버깅 — 병렬은 추적이 어렵다
동시에 흘러가는 작업은 로그가 뒤섞여 원인 추적이 힘듭니다. 분산 트레이싱(Jaeger·Zipkin)과 상관관계 ID(Correlation ID) 전파, 구조화된 로깅을 처음부터 넣어두세요. 각 작업의 시작·완료·소요시간을 남기면 병목이 보입니다.
체크리스트
- ✅ 작업 간 독립성 검증 — 의존이 있으면 순차 또는 부분 병렬
- ✅ 상태는 고유 키 또는 Reducer로 — 같은 키 동시 쓰기 금지
- ✅ Semaphore로 동시성 제한 — API·비용·메모리 보호
- ✅ 부분 실패 허용 — 하나 죽어도 전체는 산다
- ✅ 모든 걸 Agent로 만들지 말 것 — 하이브리드로 비용 최적화
체이닝(1편)이 “순서대로 쪼개기”, 라우팅(2편)이 “입력에 따라 길 나누기”였다면, 병렬화는 “독립 작업을 동시에 던지기”입니다. 세 패턴은 경쟁이 아니라 조합됩니다 — 라우팅으로 분기하고, 각 경로에서 병렬로 모으고, 체이닝으로 다듬습니다. 그리고 잊지 마세요: 모든 것을 Agent로 만들 필요는 없습니다. 다음 편에서는 에이전트가 자기 출력을 스스로 검토·개선하는 리플렉션(Reflection)을 다룹니다.
'Agentic AI Design Patterns' 카테고리의 다른 글
| CH06-스스로 길을 그리는 AI — 플래닝(Planning) 패턴 완벽 정리 (0) | 2026.06.03 |
|---|---|
| CH05-AI 에이전트 도구 사용 입문 | @tool·ReAct·FunctionTool로 외부 API·DB 연동하기 (0) | 2026.06.03 |
| CH04-스스로 고쳐 쓰는 AI — 리플렉션(Reflection) 패턴 완벽 정리 (0) | 2026.06.02 |
| CH02-입력에 따라 길을 나눠라 — 라우팅(Routing) 패턴 완벽 정리 (0) | 2026.05.31 |
| CH01-프롬프트 체이닝 입문 | 단일 프롬프트의 한계부터 LangChain·LangGraph·Google ADK 구현까지 (0) | 2026.05.31 |