PDFPipe

Operations / What it costs and what it can take

Working out what a generated document actually costs you

Adding up the real per-document cost, which is more than the render and is the number every capacity and caching decision depends on.

Why the default answer is wrong here

Teams know the render price and treat it as the cost. The render is usually the largest line and rarely the only one: there is storage for as long as the document is retained, egress every time it is downloaded, the delivery cost if it is emailed, and the compute that assembled the data. Without the whole figure, every decision that trades one against another, caching against re-rendering, retention against regeneration, is being made on a partial number.

The decisions

Reasons rather than a description of the code. Each has a default that is defensible in general and wrong for documents specifically.

  • Count every line: render, storage over the retention period, egress per download, delivery, and the compute that produced the data.
  • Separate the one-off cost from the recurring one. A document rendered once and stored for seven years has a cost that keeps accruing, and it is often larger than the render.
  • Measure downloads per document rather than assuming one, since a document retrieved fifty times has an egress cost fifty times what a naive model assumes.
  • Work out the break-even between caching and re-rendering from the actual numbers, because it is not obvious in either direction and it changes with retention.
  • Track it per document kind. A one-page receipt and a two-hundred-page statement are not the same product and averaging them hides both.
  • Revisit it when volume changes materially, since the mix of fixed and per-unit costs shifts with scale.

In practice

A fragment, with the thing that goes wrong kept in a comment where it is the more instructive half.

js
/* The whole figure, not just the render.

   Per invoice, retained 7 years, one download on average:

     render                    one call
     storage                   size x 84 months
     egress                    size x downloads
     delivery                  one email
     data assembly             a query and a template render

   The line people forget is storage, because it is small per month and
   the retention is long: 84 months of a small number is frequently
   larger than the render it is compared against. */

// Measure rather than assume, because the assumption is always one.
metrics.increment("document.downloaded", { kind, documentId });

// The break-even that caching decisions actually turn on.
function breakEven({ renderCost, bytesStored, storageCostPerBytePerMonth, months }) {
  const storeCost = bytesStored * storageCostPerBytePerMonth * months;
  return {
    cheaperToStore: storeCost < renderCost,
    // Below this many retrievals, re-rendering on demand is cheaper.
    retrievalsToJustifyStoring: renderCost / Math.max(storeCost, 1e-12),
  };
}

/* Per kind, always. A receipt and a statement are not the same product
   and an average of the two describes neither.                       */

What people do instead

Comparing caching against rendering on the render price alone. Storage looks free because the monthly figure is small, and over a multi-year retention it is frequently the larger number, which inverts the decision that was made.

What the symptom looks like

A cost per document that rises while volume rises means something is scaling worse than linearly, usually storage accumulating faster than it expires, and that is visible in the trend long before it is visible in the bill.

Frequently asked

Is this worth doing for a small volume of documents?

Some of it, and the cheap parts are the ones that matter. Allocating a document's identity before rendering it costs nothing and prevents duplicates forever. Logging the template version costs one field and answers most support questions. Queue design, backpressure and capacity planning are genuinely for scale and can wait until there is some.

Why is duplication treated as more serious than latency here?

Because a document usually carries an identity. A duplicated read is harmless and a duplicated invoice is a second numbered document for one event, which somebody has to reconcile by hand and which may already have been sent. That asymmetry is why the correctness half of this cluster is larger than it would be for most APIs.

How does this relate to the troubleshooting pages?

Those start from a symptom you are looking at right now and work back to a cause. These start from a decision made before the symptom exists. The two meet in the middle: a decision skipped here usually appears there some months later as a problem with no obvious explanation.

Related operational topics

The decisions that depend on each other, then the rest of the same group.

Most of these decisions are cheaper to make before the first production run than after the first incident, and none of them need a large system to be worth making.