Skip to main content

ADR-003: PDF Lambda -- One Invocation per Student

Status: Accepted | Date: 2026-03-13

Context

SA3 generates 500--1,500 PDF report cards per term using @react-pdf/renderer. This library has a documented behaviour: renderToBuffer() calls accumulate heap memory within a single Node.js process and do not fully release it between calls. Processing multiple students per invocation causes progressive heap growth and reliably produces OOM errors at batch scale.

Decision

Each SQS message contains exactly one studentId. The Lambda event source mapping sets batch_size = 1. Each invocation renders one PDF, uploads to S3, writes GeneratedReport and GeneratedReportScore rows to RDS, and exits.

Lambda reserved concurrency is set to 10, allowing parallel rendering. At 10 parallel invocations, 1,500 reports complete in approximately 10--15 minutes.

Rationale

  1. Memory leak avoidance -- each invocation starts with a fresh heap.
  2. SQS fan-out is the natural model for per-student parallelism.
  3. Per-student retry semantics -- if one student's PDF fails, only that message is retried (up to 3 times) before going to the DLQ.
  4. DLQ visibility -- sa3-pdf-generation-dlq captures failed messages for inspection and replay.
  5. Reserved concurrency prevents Lambda from consuming the full account concurrency budget.

Consequences

Positive: No OOM failures, per-student retries, DLQ for failure visibility, predictable memory usage (512MB per invocation).

Negative: Cold start penalty (~200--500ms per invocation). Lambda is a second deployment artifact. SQS visibility timeout (360s) must exceed Lambda timeout (300s).