System Design: A PDF Generation Microservice

TL;DR
This post walks through a system-design submission for a PDF-generation microservice: browser-pool workers, SQS-backed async jobs, tiered S3 storage, and a state machine that prevents OOM under load.
Requirements
- Accept HTML templates and data, return PDFs.
- Support both synchronous (<1 MB, quick) and asynchronous (large batches) flows.
- Scale horizontally during spikes.
- Stay cost-effective at rest.
High-level design
Client → API Gateway → Request Validator → Sync Worker / Async Queue
↓
SQS queue
↓
Browser-pool workers (EC2 Auto Scaling)
↓
Tiered S3 → CDN / webhook callback
Key components
- Browser pool: headless Chromium workers with lifecycle management, restart thresholds, and OOM guards.
- Job lifecycle state machine: queued → processing → success/failed → retry with backoff.
- Batched DOM injection: inject templates incrementally so large documents don't blow the worker heap.
- Tiered S3: hot results in standard, archived in Glacier.
- Cost math: EC2 spot for workers, SQS, S3 lifecycle rules to keep egress cheap.
Tradeoffs
- Sync path is simpler but couples latency to PDF size; we cap it aggressively.
- Async path adds complexity but handles batch loads and keeps APIs responsive.
- Browser rendering gives perfect fidelity but costs more than template-to-PDF libraries; we choose rendering because WYSIWYG contracts matter here.
What I'd change today
I'd evaluate serverless container workers for batch jobs to remove idle EC2 cost, and add a PDF-size predictor that routes oversized requests to async automatically.