Chapter 18: Parallelism and Performance
As your LangGraph workflows evolve from simple chatbots into massive Multi-Agent Systems, performance becomes a critical bottleneck.
If an Agent needs to search the Web, query a SQL Database, and read an internal PDF, executing those tools sequentially might take 15 seconds. If you execute them simultaneously, it takes 5 seconds.
In this chapter, we will master Parallelism. We will explore parallel graph branches, concurrent tool execution, asynchronous nodes, dynamic Map-Reduce routing, and caching strategies to build blazing-fast AI applications.
18.1 Parallel Branches (Fan-Out / Fan-In)
We briefly introduced Fan-Out topology in Chapter 5. Let's look at how to implement it for maximum performance.
When you draw an edge from one node to multiple destination nodes, LangGraph automatically executes the destination nodes concurrently using threads (or asyncio tasks if the nodes are async).
# 1. Register the heavy workers
builder.add_node("web_searcher", heavy_web_search)
builder.add_node("db_searcher", heavy_db_search)
# 2. Register the aggregator
builder.add_node("aggregator", combine_results)
# --- Topology ---
# FAN-OUT: Both searches start at the EXACT same time!
builder.add_edge("analyzer", "web_searcher")
builder.add_edge("analyzer", "db_searcher")
# FAN-IN: LangGraph automatically waits for BOTH searches
# to finish before routing to the aggregator!
builder.add_edge("web_searcher", "aggregator")
builder.add_edge("db_searcher", "aggregator")
LangGraph handles the synchronization locks entirely under the hood.
18.2 Dynamic Fan-Out (The Map-Reduce Pattern)
Static Fan-Out (drawing explicit edges) is great when you know exactly how many parallel nodes you need. But what if you want to analyze an unknown number of PDF chapters? If a PDF has 5 chapters, you want 5 parallel nodes. If it has 50 chapters, you want 50 parallel nodes.
You cannot draw 50 explicit edges. You must use the Send API (Map-Reduce).
Instead of returning a standard state update, a node can return a list of Send objects. Each Send object dynamically spins up a parallel instance of a target node.
from langgraph.types import Send
def routing_node(state: GraphState):
# Determine how many chapters we have
chapters = state["pdf_chapters"] # e.g., ["Chap 1", "Chap 2", "Chap 3"]
# Map Phase: Dynamically spawn a "summarize_node" for EVERY chapter simultaneously!
parallel_tasks = []
for chapter_text in chapters:
# Send(node_name, state_input_for_that_node)
parallel_tasks.append(Send("summarize_node", {"text_to_summarize": chapter_text}))
# LangGraph immediately spawns 3 parallel workers!
return parallel_tasks
Once all dynamically spawned summarize_node instances finish, they Fan-In, their state updates are merged via a Reducer, and the graph continues. This is how enterprise AI systems process massive datasets in seconds.
18.3 Async Nodes and Concurrent Tool Execution
To get the absolute best performance out of LangGraph, especially in web servers like FastAPI, you should write your nodes using Python's async def.
Async nodes prevent your application from blocking the main thread while waiting for OpenAI API responses or HTTP requests.
# A high-performance Async Node!
async def async_llm_node(state: GraphState):
# Use the ainvoke() method for non-blocking network calls
response = await llm.ainvoke(state["messages"])
return {"messages": [response]}
Concurrent ToolNode Execution
If your Agent requests three tools simultaneously in its tool_calls payload, the pre-built ToolNode is smart enough to execute them concurrently.
However, if your underlying Python @tool functions are synchronous def, the ToolNode must run them in a ThreadPoolExecutor. If you define your tools as async def, the ToolNode will automatically launch all three tools perfectly concurrently using asyncio.gather(), slashing your execution time.
Warning: When firing off 50 parallel asynchronous tools, you will likely hit rate limits on third-party APIs. Use asyncio semaphores inside your tools to gracefully throttle the concurrency.
18.4 Batch Processing (app.abatch)
What if you need to run your entire graph for 10,000 different users at the same time? Calling app.invoke() in a for loop is painfully slow.
LangGraph provides a Native Batch API.
inputs = [
{"messages": [HumanMessage("Research AI")]},
{"messages": [HumanMessage("Research Quantum Computing")]},
{"messages": [HumanMessage("Research BioTech")]}
]
# Runs all 3 graphs concurrently, managing the async event loop automatically!
results = await app.abatch(inputs, config={"max_concurrency": 10})
for res in results:
print(res["messages"][-1].content)
Batching is heavily used in backend data pipelines, CRON jobs, and massive document processing workflows.
18.5 Latency and Cost Optimization Strategies
Beyond structural parallelism, keep these optimization strategies in mind:
- Model Routing: Don't use
gpt-4oorclaude-3-opusfor simple formatting or data extraction nodes. Use smaller, 10x cheaper, and 10x faster models likegpt-4o-miniorclaude-3-haikufor basic tasks, and reserve the heavy models exclusively for the Supervisor routing and complex reasoning nodes. - Semantic Caching: If your Agent constantly searches the vector database for "Company Holiday Schedule", implement an LLM Cache layer (
langchain.globals.set_llm_cache). If the exact same question is asked, return the cached result instantly without invoking the LLM or the Graph. - State Trimming: The larger your
MessagesStatearray grows, the more tokens you send to the LLM on every turn. More tokens = higher latency and higher cost. Aggressively trim or summarize older messages in your state using atrim_messages_nodeto keep API calls lightning fast. - Profiling: Use Python's
time.perf_counter()inside your nodes, or leverage LangSmith (Chapter 20), to measure exactly how many milliseconds each node takes. You cannot optimize what you do not measure.
18.6 Summary
Performance engineering in LangGraph is all about maximizing concurrency. By utilizing Fan-Out topologies, dynamic Map-Reduce Send APIs, Async Python, and Semantic Caching, you can ensure your Multi-Agent Systems respond in seconds rather than minutes.
However, as you execute dozens of tools concurrently across the internet, things are mathematically guaranteed to fail. APIs will timeout. Databases will lock.
In the next chapter, we will bulletproof our applications by mastering Error Handling and Reliability.