Changyu Lee

Dev Log [05.29.25] : Handling CPU-bound Tasks in FastAPI

Published at
2025/05/29
Last edited time
2025/05/28 17:03
Created
2025/05/28 17:01
Section
Dev Log
Status
Done
Series
Tags
Log
AI summary
Keywords
ASGI
FastAPI
ThreadPoolExecutor
Language
ENG
Week
1 more property

๐Ÿš€ Handling CPU-bound Tasks in FastAPI

Using ThreadPoolExecutor + uvicorn --workers for Effective Parallel Processing

๐Ÿง  Background

FastAPI is known for its powerful asynchronous capabilities, especially with I/O-bound tasks. However, when it comes to CPU-bound workloads (like OCR, image processing, or heavy computation), async def alone doesn't bring much performance gain.
This post walks you through how I improved the responsiveness and concurrency of my FastAPI app using:
โ€ข
ThreadPoolExecutor for CPU-bound operations
โ€ข
Uvicornโ€™s -workers option for true multi-processing

โš ๏ธ The Problem

While building an API for generating scripts using LLMs, I ran into performance bottlenecks due to:
โ€ข
Intensive OCR and image preprocessing (e.g., with PyMuPDF, OpenCV)
โ€ข
Running dozens of LLM summarization and generation calls
โ€ข
Handling large text files and saving structured data
These operations were slowing down requests and leading to poor concurrency.

โœ… Solution 1: ThreadPoolExecutor for CPU-bound Async

from concurrent.futures import ThreadPoolExecutor import asyncio executor = ThreadPoolExecutor() def heavy_cpu_task(data): # Do CPU-intensive work here ... loop = asyncio.get_event_loop() result = await loop.run_in_executor(executor, heavy_cpu_task, data)
Python
๋ณต์‚ฌ

Why it works:

โ€ข
Runs CPU-heavy functions in background threads
โ€ข
Keeps the FastAPI event loop responsive
โ€ข
Ideal for tasks like OCR, parsing, or heavy LLM formatting

โœ… Solution 2: Use -workers for Multi-Processing

Even with run_in_executor, a single FastAPI process may not be enough under heavy load. Thatโ€™s where multi-process scaling comes in.
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 8
Shell
๋ณต์‚ฌ

Why it helps:

โ€ข
Launches 8 independent processes, each handling requests
โ€ข
Bypasses the limitations of Pythonโ€™s GIL
โ€ข
Great for high concurrency or batch processing

๐Ÿ”ง Combined Architecture

[ Client ] โ†“ [ FastAPI (async endpoints) ] โ†“ [ ThreadPoolExecutor โ†’ background CPU-bound task ] โ†“ [ One of multiple Uvicorn workers (multi-process) ]
Plain Text
๋ณต์‚ฌ
This setup allowed:
โ€ข
Immediate response to the client
โ€ข
Non-blocking heavy computation
โ€ข
Stable parallelism even under high traffic

๐Ÿ—‚๏ธ When to Use What

Task Type
Recommended Approach
I/O-bound (HTTP, DB)
async def
CPU-bound
run_in_executor + ThreadPoolExecutor
High concurrency
uvicorn --workers

โœ… Result

After these changes, we achieved:
โ€ข
Better scalability under concurrent users
โ€ข
Faster response times with long-running tasks
โ€ข
More modular and maintainable service logic

๐Ÿ’ก Takeaway

Asynchronous programming in FastAPI is powerfulโ€”but not a silver bullet. When your app performs CPU-heavy tasks like LLM calls, image processing, or OCR, consider combining ThreadPoolExecutor with uvicorn --workers to unlock true parallel performance.
Home
Projects
Blog
Contact