Back to Blog

System Design: A PDF Generation Microservice

engineering
system-designpdfawsbackend
248 words1 min read
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

  1. Browser pool: headless Chromium workers with lifecycle management, restart thresholds, and OOM guards.
  2. Job lifecycle state machine: queued → processing → success/failed → retry with backoff.
  3. Batched DOM injection: inject templates incrementally so large documents don't blow the worker heap.
  4. Tiered S3: hot results in standard, archived in Glacier.
  5. 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.