DEV Community

YukiKobayashi880
YukiKobayashi880

Posted on

How to Use Python PDF Endpoints for SaaS Format Migration Under Load

Short answer: make PDF conversion an asynchronous, versioned boundary, and keep the source template under your team's control when fidelity is contractual. A synchronous endpoint is fine for a small preview, but production migration should return a job ID, record the renderer version, and expose a status endpoint whose latency you can measure separately from the conversion itself. That balance keeps operational complexity visible instead of hiding it in a timeout.

For a US/EU customer-support SaaS that turns scanned documents into searchable text, the expensive mistake is treating “PDF endpoint” as one operation. There are at least three bills hiding behind it: renderer CPU, temporary object storage, and retained output plus egress. Measure those terms from a representative queue before changing providers. In many systems, renderer CPU during bursty imports dominates; retaining every intermediate image then becomes the quieter, compounding cost.

I put one number on the first design review: a 10,000-document replay, split by page count and template family. It is not a benchmark claim. It is a reproducible workload. The review records p50 and p95 queue wait, conversion time, OCR time, output bytes, and deletion lag. Without that split, “fast under load” usually means someone measured only the warm, two-page sample.

Measure first.

Keep it boring.

How should a SaaS use PDF endpoints for document format migration?

Keep the public surface small and explicit. A conversion request can map to POST /v1/pdf/convert with a source object reference, template revision, locale, and idempotency key. It returns 202 Accepted with a job ID; GET /v1/pdf/job/get/{job_id} reports queued, running, succeeded, or failed, plus timestamps and an output reference when complete. Your own gateway can expose a download action without coupling clients to the renderer's storage details.

The preview path can return a bounded PDF directly. Do not let that convenience path silently become the bulk-import path; request timeouts, memory limits, and retry behavior are different. A client retrying a timed-out conversion must be safe, so the idempotency key belongs to the logical document revision, not to a network attempt.

Here is a minimal Python client. It deliberately treats 202 as a normal response, not an error, and keeps polling separate from conversion latency.

import time
import requests


def submit_and_wait(base_url, token, payload, key, timeout_s=900):
    headers = {
        "Authorization": f"Bearer {token}",
        "Idempotency-Key": key,
        "Content-Type": "application/json",
    }
    response = requests.post(
        f"{base_url}/v1/pdf/convert", json=payload, headers=headers, timeout=30
    )
    response.raise_for_status()
    job_id = response.json()["job_id"]
    deadline = time.monotonic() + timeout_s

    while time.monotonic() < deadline:
        status = requests.get(
            f"{base_url}/v1/pdf/job/get/{job_id}", headers=headers, timeout=10
        )
        status.raise_for_status()
        body = status.json()
        if body["state"] == "succeeded":
            return body["output_reference"]
        if body["state"] == "failed":
            raise RuntimeError(body.get("error_code", "conversion_failed"))
        time.sleep(min(8, 1 + body.get("poll_after_seconds", 2)))

    raise TimeoutError("job remained incomplete within the client deadline")
Enter fullscreen mode Exit fullscreen mode

The endpoint contract should include a correlation ID, input hash, template revision, renderer revision, and page count. Those fields let an operator answer whether a slow request waited in the queue, consumed CPU, or stalled on storage. They also make a migration rerunnable without guessing which output came from which template.

That trace is the handoff.

How do fidelity, latency, and template ownership interact under load?

Fidelity is not a single score. For support documents, define checks that matter to the agent: page count, text extraction, reading order, glyph coverage, image dimensions, and required fields in the OCR layer. Render a small golden corpus for every template revision. Compare extracted text and layout anchors, then send visual diffs to review; a byte-for-byte PDF comparison is too sensitive to metadata and too weak at detecting a shifted signature block.

Template ownership changes the operational shape. If your team owns the HTML/CSS or document template, it can pin a renderer version, review diffs, and roll back one revision. If a customer owns an opaque template, the endpoint should accept a declared compatibility profile and return a clear validation result before enqueueing thousands of jobs. Do not promise pixel identity across engines when fonts, color profiles, or embedded images differ.

Under load, protect the queue rather than hiding it. Bound concurrent renders per worker, reserve separate capacity for previews, and apply backpressure before the browser opens thousands of connections. A useful SLO is two numbers: queue wait and active conversion time. A single end-to-end p95 masks which control needs changing.

The failure mode I watch is retry amplification. A worker times out at 59 seconds, the client retries, and both conversions continue. Idempotency plus a lease on the job prevents that duplicate work. Your mileage may vary with the renderer; I am not sure a universal concurrency value exists, so measure CPU saturation and memory high-water marks on your own templates.

What should the retention and cost policy keep after conversion?

Keep the source scan, the searchable text, and the final PDF only as long as the support workflow and legal hold require. Delete rasterized page images and renderer scratch space promptly. The dominant term is workload-specific: if conversion CPU is the largest line item, reducing duplicate renders matters more than shaving a few kilobytes from metadata; if outputs are downloaded repeatedly, egress and cache policy deserve the first experiment.

Artifact Default retention question Failure if retained forever
Original scan Is it the audit record or a temporary upload? Storage and privacy exposure grow together
OCR text Can agents search it after the case closes? Stale text can be mistaken for current evidence
Final PDF Must the customer download the exact revision? Egress and access-control surface expand
Page images and scratch files Can a rerun recreate them? Usually pure storage without user value

Write deletion as an observable state transition. A successful conversion should not claim “complete” until the output is durable and the retention clock is recorded. A cleanup worker can then retry deletion idempotently, while a legal hold pauses that transition. The catch is that aggressive deletion is not suitable when an unresolved dispute needs the original pixels; stick with a documented hold policy in that case.

When is a synchronous PDF endpoint the wrong choice?

Synchronous conversion is reasonable for a user-triggered preview with a strict page limit and a bounded input size. It is not suitable for a backfill, a multi-hundred-page scan, or a queue that must survive regional traffic spikes. Move those jobs behind 202 Accepted, expose cancellation semantics, and make the UI show queue wait rather than pretending the renderer is still working. Especially during a format migration, the endpoint should make this choice explicit so operators can tune complexity and latency independently.

Test the boundary with malformed PDFs, missing fonts, oversized images, duplicate idempotency keys, client disconnects, and a worker restart after output creation. Return stable error categories, never raw stack traces, and keep the original input reference so an operator can replay the exact revision. A three-word rule helps: measure, then migrate.

The decision is therefore procedural: own templates where fidelity is a contract, isolate preview from batch traffic, report queue and conversion latency independently, and retain only artifacts with a stated purpose. That gives a migration team a defensible endpoint design without turning a renderer choice into a permanent dependency.

References

Top comments (0)