Agentic AI Design Patterns

CH03-AI 에이전트 병렬화 입문 | RunnableParallel·LangGraph·ParallelAgent·asyncio로 동시 실행 구현하기

빌드 피벗(Build Pivot) 2026. 6. 1. 22:35
반응형
AGENTIC AI DESIGN PATTERNS · 03
독립 작업을 동시에 — 병렬화(Parallelization) 완벽 정리
Fan-Out/Fan-In부터 동시성·병렬성, 4개 프레임워크, Agent가 아닌 하이브리드 병렬화, CRM·ERP·Groupware 적용까지
🟢 입문~중급⏱️ 약 16분⚡ RunnableParallel·LangGraph·ParallelAgent·asyncio🏢 엔터프라이즈

TL;DR — 병렬화는 서로 독립적인 작업을 동시에 실행해 전체 처리 시간을 단축하는 패턴입니다. 4개 작업이 각 2초라면 순차로는 8초, 병렬로는 약 2초(가장 느린 작업 시간)면 끝납니다. 핵심은 독립성·상태 충돌 방지(Reducer/output_key)·동시성 제어(Semaphore)이며, 실무에선 모든 걸 Agent로 만들지 않고 Function·Chain·Agent를 섞는 하이브리드로 LLM 비용을 최대 60%까지 줄입니다.

1. 왜 병렬화인가 — 기다림은 더해진다

1편 프롬프트 체이닝은 작업을 순서대로 이어 붙였고, 2편 라우팅은 입력에 따라 길을 나눴습니다. 그런데 현실에는 서로 의존하지 않는 작업이 많습니다. 기업 리서치를 한다고 해봅시다. 뉴스·주가·소셜·재무를 각각 2초씩 조회하는데, 이를 순서대로 호출하면 그냥 8초를 기다립니다. 네 번째 호출은 첫 번째 결과가 전혀 필요 없는데도요.

병렬화는 이렇게 독립적인 작업을 동시에 던지고 모두 끝나길 함께 기다립니다. 전체 시간은 합계가 아니라 가장 느린 작업 하나의 시간이 됩니다. 8초가 2초로 줄어드는 겁니다.

순차 실행 — 하나 끝나야 다음
입력
A 2초B 2초C 2초D 2초
총 8초
병렬 실행 — 동시에 시작
입력
A 2초
B 2초
C 2초
D 2초
수집
총 2초 · 4배 빠름
그림 1. 같은 4개 작업 — 순차는 시간이 더해지고, 병렬은 가장 느린 작업 시간으로 수렴한다
병렬화의 단 하나의 전제 — 독립성. 작업 B가 작업 A의 결과를 입력으로 쓴다면 병렬화할 수 없습니다(그건 체이닝의 영역). 다중 API 호출, 다중 LLM 분석, 멀티모달 처리처럼 서로 결과를 주고받지 않는 작업일 때만 효과가 납니다.

2. 병렬화란 — Fan-Out / Fan-In

병렬화의 대표 구조가 Fan-Out / Fan-In입니다. 하나의 시작점에서 여러 작업으로 분산(Fan-Out)하고, 작업들이 동시에 실행된 뒤, 결과를 하나의 집계 노드로 수집(Fan-In)합니다.

시작점 (START)
▼ Fan-Out · 작업 분산
Task A
독립·동시
Task B
독립·동시
Task C
독립·동시
Task D
독립·동시
▼ Fan-In · 결과 수집
집계 노드 → 최종 결과
그림 2. Fan-Out으로 흩뿌리고 Fan-In으로 모은다

병렬화의 4대 가치

  • 응답 시간 단축 — 합계가 아니라 최장 작업 시간으로 수렴
  • 리소스 효율 — I/O 대기 시간 동안 다른 작업을 진행
  • 사용자 경험 — 실시간에 가까운 종합 응답 제공
  • 처리량 증가 — 동일 시간에 더 많은 분석·요청 소화
🧩
패턴 조합. 병렬화는 단독으로도 쓰지만 보통 체이닝·라우팅과 결합합니다. 예: 입력 → 전처리(체이닝) → 분석 A·B·C 병렬 → 통합 → 출력. 또는 라우터로 분기한 뒤 각 경로 안에서 병렬 실행.

3. 동시성 vs 병렬성 — 헷갈리면 안 되는 구분

두 단어는 자주 혼용되지만 메커니즘이 다릅니다. 동시성(Concurrency)은 단일 스레드(asyncio)에서 I/O 대기 시간 동안 작업을 번갈아 처리하는 것이고, 병렬성(Parallelism)은 여러 CPU 코어(multiprocessing)에서 진짜로 동시에 연산하는 것입니다.

동시성 · asyncio (단일 스레드)
1 ThreadABACBC
→ GIL 제약 없음 · I/O 바운드에 최적
병렬성 · multiprocessing (다중 프로세스)
Process 1
A — 코어 점유
Process 2
B — 코어 점유
Process 3
C — 코어 점유
→ CPU 바운드(이미지·대량 연산)에 최적
그림 3. 단일 스레드의 인터리빙(동시성) vs CPU 코어별 실제 동시 실행(병렬성)
작업 유형 권장 방식 이유
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]).
PYTHON
# ❌ 같은 키에 동시 쓰기 → 충돌(덮어쓰기) class State(TypedDict): result: str # 여러 노드가 동시에 쓰면 하나만 남는다 # ✅ Reducer: 병렬 노드가 각자 {"results":[x]}를 반환하면 자동 병합 class State(TypedDict): results: Annotated[list, operator.add] # 리스트에 누적
⚠️
에러 격리도 필수. 병렬 작업 하나가 실패했다고 전체가 무너지면 안 됩니다. asyncio.gather(..., return_exceptions=True)로 예외도 결과로 받아 부분 실패를 허용하세요(12장).

5. 구현 ① LangChain RunnableParallel

RunnableParallel딕셔너리에 체인을 담기만 하면 동시에 실행합니다. 가장 간단한 병렬화입니다. 아래는 하나의 주제에 대해 요약·질문·키워드·관련분야를 한꺼번에 만들고, 그 결과를 종합 체인으로 흘려보냅니다.

PYTHON
from langchain_core.runnables import RunnableParallel, RunnablePassthrough # 4개 체인을 딕셔너리로 묶으면 ainvoke 시 동시에 실행된다 parallel = RunnableParallel({ "summary": summarize_chain, "questions": questions_chain, "keywords": keywords_chain, "related_fields": related_fields_chain, "original_topic": RunnablePassthrough(), # 원본 입력 그대로 전달 }) # 병렬 분석 → 종합: LCEL 파이프로 연결 full_pipeline = parallel | synthesis_chain result = await full_pipeline.ainvoke({"topic": "인공지능의 미래"})

같은 작업을 await chain.ainvoke(...)로 하나씩 순차 실행하는 것과 비교하면 보통 3~4배 빨라집니다.

6. 구현 ② LangGraph Fan-Out / Fan-In

LangGraph는 그래프 엣지로 병렬을 표현합니다. START에서 여러 노드로 엣지를 그으면 그 노드들이 동시에 실행(Fan-Out)되고, 여러 노드가 한 노드를 가리키면 그곳에서 수렴(Fan-In)합니다. 병렬 결과는 Reducer로 안전하게 누적합니다.

PYTHON
class ResearchState(TypedDict): topic: str analyses: Annotated[list[dict], operator.add] # Reducer로 누적 final_report: str builder = StateGraph(ResearchState) for n in ["technical", "market", "social", "future"]: builder.add_node(n, globals()[f"{n}_analysis"]) builder.add_edge(START, n) # Fan-Out: 4개 노드 동시 분기 builder.add_edge(n, "aggregate") # Fan-In: 모두 aggregate로 수렴 builder.add_node("aggregate", aggregate_analyses) builder.add_edge("aggregate", END) graph = builder.compile()

각 분석 노드는 return {"analyses": [결과]}만 반환하면 됩니다. Reducer가 4개의 리스트를 자동으로 하나로 합쳐주므로 충돌이 없습니다.

7. 구현 ③ Google ADK ParallelAgent

ADK는 ParallelAgentsub_agents를 넣으면 동시에 실행하고, 각 에이전트의 output_key가 결과를 세션 상태에 저장합니다. 그 뒤 SequentialAgent로 통합 에이전트를 이어 붙이면 병렬 → 순차 파이프라인이 됩니다.

PYTHON
tech = LlmAgent(name="TechResearcher", output_key="tech_analysis", ...) market = LlmAgent(name="MarketResearcher", output_key="market_analysis", ...) # ... usecase, risk 동일 parallel = ParallelAgent(name="ParallelResearchTeam", sub_agents=[tech, market, usecase, risk]) # 동시 실행 synthesizer = LlmAgent(name="Synthesizer", instruction="기술:{tech_analysis} 시장:{market_analysis} ...") # 결과 참조 pipeline = SequentialAgent(sub_agents=[parallel, synthesizer]) # 병렬→통합

통합 에이전트는 {tech_analysis}처럼 중괄호로 이전 결과를 참조합니다. 각 작업에 서로 다른 output_key를 주는 것이 상태 충돌을 막는 핵심입니다.

8. 구현 ④ 순수 asyncio + Semaphore

가장 저수준이자 가장 유연한 방법입니다. asyncio.gather()로 여러 비동기 함수를 동시에 던지고, Semaphore로 동시 실행 수를 제한해 API Rate Limit을 보호합니다.

PYTHON
api_semaphore = asyncio.Semaphore(3) # 최대 3개 동시 실행 async def fetch_news(topic): async with api_semaphore: # 슬롯이 비어야 진입 return await call_news_api(topic) # 5개 도구를 동시에 — 부분 실패 허용 results = await asyncio.gather( fetch_news(t), fetch_stock(t), fetch_social(t), fetch_competitor(t), fetch_regulatory(t), return_exceptions=True, )

ADK에서는 도구를 async def로 정의해 FunctionTool로 감싸면, 에이전트(v1.10.0+)가 LLM이 요청한 도구들을 자동으로 병렬 실행합니다.

LangChain RunnableParallel
RunnableParallel({...})
딕셔너리 병렬
LangGraph Fan-Out/In
add_edge(START,n)+Reducer
상태그래프 분기
Google ADK ParallelAgent
ParallelAgent(sub_agents=)
네이티브 에이전트
순수 Python asyncio
asyncio.gather(*tasks)
저수준 제어
그림 4. 같은 병렬화, 네 가지 구현 — 추상화 수준만 다를 뿐 원리는 같다
프레임워크 병렬화 메커니즘 특징
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을 부르는 건 낭비입니다.

추상화 4수준 — 위로 갈수록 추론↑·오버헤드↑, 아래로 갈수록 LLM 의존↓
Agent 복잡 추론 · ReAct · Plan-Execute
Chain 프롬프트 → LLM → 파서
Function / Tool DB·API (LLM 불필요)
Primitive I/O 쿼리·HTTP
“각 작업에 필요한 최소 추상화 수준을 선택” (YAGNI)
그림 5. 추상화 피라미드 — 모든 것을 Agent로 만들 필요는 없다

고객 문의 처리에 5개 작업(고객정보·주문이력·유사문의 = Function, 감정분석 = Chain, 응답초안 = Agent)이 필요하다고 합시다. 전부 Agent로 만들면 LLM을 5번 부르지만, 작업별 최소 수준으로 섞으면 LLM 호출이 확 줄어듭니다.

방법 A · All-Agent$0.05
모든 작업을 LLM으로 (3개는 불필요)
LLMLLMLLMLLMLLM
LLM 5회 호출
방법 B · Hybrid$0.02
Function 3 + Chain 1 + Agent 1
FuncFuncFuncChainAgent
LLM 2회 호출
💡
하이브리드 효과 — LLM 호출·비용 최대 60%↓, 응답 시간 33%↓. 월 100만 요청 기준 All-Agent $50,000 vs Hybrid $20,000으로 연간 약 $360,000을 아낍니다. LangChain은 RunnableLambda로 일반 함수를 감싸 Chain·Function을 하나의 RunnableParallel에서 섞을 수 있고, LangGraph 노드는 애초에 어떤 함수든 될 수 있어 하이브리드를 기본 지원합니다.
PYTHON
full_parallel = RunnableParallel({ "customer_info": RunnableLambda(async_customer_info), # Function (DB) "order_history": RunnableLambda(async_order_history), # Function (API) "similar_cases": RunnableLambda(async_similar_cases), # Function (Vector) "sentiment": sentiment_chain, # Chain (LLM) "keywords": keyword_chain, # Chain (LLM) }) # Function + Chain을 한 번에 병렬 — LLM은 2개 체인만

10. 엔터프라이즈 적용 — CRM · ERP · Groupware

병렬화는 “여러 데이터 소스를 동시에 모아야 하는” 엔터프라이즈 업무에서 즉각적인 효과를 냅니다.

CRM고객 관계 관리
고객 360° 뷰12초 → 3초 (75%↓)
실시간 추천병렬 추천 에이전트
다채널 응대동시 처리
ERP전사 자원 관리
월말 마감8시간 → 3.5시간 (56%↓)
보고서 생성30분 → 8분 (73%↓)
부서별 집계Fan-Out/Fan-In
Groupware협업 시스템
스마트 알림5초 → 1초 (80%↓)
병렬 결재재무·법무·보안 동시검토
문서 협업동시 처리
도입 효과 · ROI
−75%
응답 시간
고객 360 기준
3.5배
일 처리량
10만→35만건
11.7개월
투자 회수
투자 17.5억
208%
3년 ROI
연효과 18억
그림 6. 시스템별 병렬화 적용과 도입 효과(ROI)

레거시 전환은 한 번에 갈아엎지 않습니다. Strangler Fig 패턴으로 병렬화 어댑터를 앞단에 두고 처리 비율을 10% → 50% → 100%로 점진 확대하며, 부분 실패에 대비한 서킷 브레이커와 롤백 계획을 함께 둡니다.

11. 케이스 스터디

① CRM 고객 360° 뷰

기본정보·구매이력·상담이력·추천·신용점수를 순차로 모으면 12초가 걸립니다. 다섯 소스는 서로 독립적이므로 API Gateway에서 동시에 호출하면 가장 느린 소스(3초)만큼만 걸려 3초로 끝납니다(75%↓).

PYTHON
async def get_customer_360(cid): async with semaphore: # 동시 호출 수 제한 results = await asyncio.gather( call_legacy("/customer/basic", cid), call_legacy("/order/history", cid), call_legacy("/support/tickets", cid), call_ai_recommendation(cid), call_external_credit(cid), return_exceptions=True, # 한 소스 실패해도 나머지 표시 ) return aggregate(results) # 12초 → 3초

② 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)을 다룹니다.

#병렬화#Parallelization#FanOut#RunnableParallel#LangGraph#GoogleADK#asyncio#AgenticAI
반응형