Skip to content
downpipes docs

Predicting storage cost and avoiding bill shock

The cost calculator at /costs projects what a downpipe will cost to store at your chosen destination, so you can size a schedule and a retention policy before the bill arrives rather than after. This page is for the self-hoster who pays the destination invoice and wants a defensible planning number.

If you arrived here asking what downpipes costs, this is not the page that answers you, and it cannot be. The calculator is a projection over sources and a schedule you have already configured, so it has nothing to work from before you commit. The commercial answer, what you pay us and what Cloudflare bills you directly, is on licensing and editions, and the Cloudflare account costs it depends on are on the prerequisites. Come back here once you are running.

Two things matter before you read a single figure. The calculator is a planning estimate, never a spend cap, a budget, a quote, or a guarantee: it computes locally and enforces nothing. And its default growth model is a full per-run snapshot, so the lever that moves your storage bill is how often a downpipe runs, not how much your data changed between runs. Both facts are built into the maths described below.

What the calculator is, and what it is not

The screen runs entirely in your browser. It computes over sizes, counts, and the destination rates you enter, and it transmits nothing, logs nothing, and stores nothing. It makes no engine write of any kind. It does make a handful of read-only, best-effort engine calls that never block the calculator: a read of your run history to seed Observed mode, an unconditional read of your configured destination to prefill pricing, an unconditional read of your downpipes to map source types for the per-source-type breakdown and, in manual mode with no observed seed, a read of your account estate to seed an initial source size. Every one of these degrades gracefully on failure, the history read falling back to manual entry with an inline note, because a manual estimate needs no engine at all (CostView.load, fetchHistory, seedPricingFromDestination, fetchSourceTypes and seedManualFromEstate, console/src/screens/costs/view.ts).

The maths lives in a pure, deterministic library that the screen consumes rather than reimplements. The library has no DOM access, no engine import, no network, no wall-clock, and no randomness, so the same inputs always produce the same outputs (console/src/lib/cost-model.ts). It never touches a key, a secret, or archive content, only sizes and counts, which is why the calculator sits inside the same no-custody posture as the rest of the product.

Because it enforces nothing, the output is a number to plan against, not a limit the engine will hold you to. The console says so in three places at once: the page header, an empty-state note while nothing is seeded, and an always-available assumptions panel.

The calculator does not cap, throttle, or alert on spend. It is an estimate computed in your browser. Your real charges are set by your destination and your real workload, and the engine never stops a backup because a projected figure was exceeded.

Manual mode and Observed mode

The calculator has two modes, and switching between them never loses the figures you entered in either (CostView.switchMode, console/src/screens/costs/view.ts).

Mode Where the inputs come from When it is active
Observed Seeded from your real run history: source size, per-run archive bytes, segment size from observed bytes-per-object, overhead, and runs per month, all then editable The headline mode when at least two recent runs carry the per-run byte and segment counts
Manual Sensible defaults you adjust by hand; needs no engine at all When there is no run history, when runs lack the observed fields, or when the history read fails

Observed mode seeds from listAllHistory() and reads the per-run archiveBytesWritten and segmentsWritten from your actual runs, then lets you override every value. The screen states why a given mode is active in plain language, so you always know whether a figure rests on your own throughput or on defaults (CostView.load sets modeReason). The minimum is two seeding runs, so the churn estimate is a real per-run delta rather than a single point (MIN_OBSERVED_RUNS, console/src/screens/costs/helpers.ts).

Both modes take the same inputs: a source size with a stored ratio, a schedule cadence, a churn fraction, a retention depth, and a count of drills and restores per month. The destination pricing is shared across both modes, because a destination’s rates do not change with how you happened to seed the inputs.

The inputs accept the following values:

Input Accepted values
Source size The source’s logical size as a number plus a unit you pick beside it (MB, GB or TB); in Observed mode it is seeded from your run history, in Manual mode you enter it
Stored ratio A percentage from 1 to 100 (stored bytes as a share of logical size), default 60
Schedule cadence Every 15 minutes, hourly, every 6 hours, daily, weekly, or custom
Custom interval (minutes) A number of minutes greater than zero, shown only when the cadence is set to custom
Retention depth (runs) A whole number of runs, 0 or more; 0 keeps everything, so the retained curve equals accumulate
Retention window (days) A whole number of days, 0 or more; converted to a run depth at the current cadence
Growth model Per-run snapshots (current engine behaviour) or Cross-run dedup (future; applies only once it ships); anything else falls back to per-run snapshots
Churn per run A percentage from 0 to 100, default 5, with Low (1%), Expected (5%) and High (20%) presets beside it; no effect on stored bytes under per-run snapshots
Drills per month A count of zero or more
Restores per month A count of zero or more; in-account reads, with egress only when data leaves the account
The Inputs column: Source size with a unit selector, Stored ratio, Schedule cadence set to Custom interval with a derived runs-per-month line, Growth model set to per-run snapshots, Churn per run with Low, Expected and High presets, retention depth and window fields, and Drills per month and Restores per month.

The screenshots are the live calculator, seeded from a synthetic test estate’s run history; the screen carries no hostname or credential to redact.

The growth model: storage grows with runs, not churn

This is the load-bearing fact, and it is the one that surprises people who expect a backup tool to dedup across runs. The live engine derives its content-address key from the per-run master key, so dedup applies only within a single run. Every run stores a full snapshot of the source. Storage therefore grows as runs per period times archive bytes per run, regardless of how little changed between runs (perRunStoredBytes and storedBytesAccumulate, console/src/lib/cost-model.ts).

The calculator defaults to this snapshot growth model because it is what the engine does today (DEFAULT_INPUTS.growthModel, console/src/lib/cost-model.ts). Under it, the churn field has no effect on stored bytes, and the calculator’s hint and the churn sensitivity sweep both say so honestly rather than pretending churn moves the bill (churnField hint, console/src/screens/costs/inputs-section.ts; the churn sweep is provably flat under snapshot, sensitivity in console/src/lib/cost-model.ts).

A second growth model, churn-dedup, is offered under the growth-model control, but it is clearly labelled as a future projection. It models a world where content addressing spans runs so a run stores only its churn. That is a recorded design decision, not current behaviour, so any figure computed under it is a projection that applies only once cross-run dedup ships. When you select it, the results region prints a prominent note before any number, and an unknown growth-model value always degrades to the honest snapshot default, never silently to the future model (buildResults and withDefaults, source files above).

Reducing churn does not reduce your stored bytes today, and there is no cross-run dedup in the live engine. If you want a lower storage bill, the levers are a longer interval (fewer runs), a retention policy that prunes old runs, or a destination with cheaper storage. Churn only matters under the clearly-labelled future model.

Accumulate versus retained

The library projects two storage curves side by side so you can see the cost of keeping everything against the cost of a retention policy.

The accumulate curve is the default state: with retention enforcement off, nothing is pruned, so every run’s content persists and storage grows roughly linearly with the run count (storedBytesAccumulate). That is a policy setting, not a missing capability. The engine’s manifest-driven segment collection does run, and does delete unreferenced segments, once you enable enforcement on a retention policy; retention and pruning covers what it deletes and the safety rules it applies. The retained curve is the projection of that: what a policy would save by keeping only the most recent runs, bounding storage at a steady state (storedBytesRetained). The screen labels the accumulate figure as the headline because it is what you are billed for until you turn enforcement on, and shows the retained figure as the saving a policy would deliver.

The headline reading Estimated recurring monthly cost, projected month 12, with the split into destination storage, Cloudflare resources and plan base, a 20 per cent safety margin, and a line noting the accumulate basis, per-run snapshot accumulation, and that it is an estimate, not a quote.

The inline read-amplification guard

Storage is one half of the bill. The other half is operations, and the calculator is explicit that a workload of many small records is read-heavy in a way a naive size estimate misses.

Object counts are estimated from the writer’s segment size, which defaults to the engine’s single-segment plaintext ceiling of one gibibyte (DEFAULT_SEG_BYTES, console/src/lib/cost-model.ts; the ceiling is defined as SPEC 14.5’s per-segment cap in engine/src/dest/types.ts, distinct from the roughly 4.995 GiB R2 single-PUT object-size limit). The engine seals one segment, which is one destination object, per record, and does not pack small records together. So for many small records the derived object count is a lower bound on the true count, because each small record is still its own object. The assumptions panel states this plainly and tells you to override the segment size to match an observed bytes-per-object ratio when your workload skews small (assumptionItems, console/src/screens/costs/assumptions.ts).

This guard is a planning prompt about read amplification, not a runtime control. Nothing in the engine enforces a read budget; the calculator simply surfaces the risk so you can plan for it. That distinction, between an estimate that flags a risk and a runtime that enforces a limit, holds across the whole screen.

The other operations inputs are the drills and restores per month. Both are in-account reads and add no egress unless data actually leaves the account. A restore reads back the records it writes. A drill is lighter than it sounds: it re-verifies the whole archive’s keyed structural chain, but it only blind-restores a sample of records to prove they decrypt, up to eight at an even stride, or the whole run when the run holds eight records or fewer (DRILL_SAMPLE_MAX, engine/src/admin/drill.ts). So a monthly drill’s restore cost is bounded by that sample, not by the full archive size, even though its structural pass covers every record. Set the two counts to your real cadence, and the estimate adds their read cost to the storage half.

The downpipe editor carries its own inline count field, separate from this calculator. When you add or edit a KV downpipe, an Approximate record count projects the KV read cost live as you choose a cadence. It takes digits only, commas are ignored, and the count is used only for that local projection and is never sent to the engine.

The safety margin

A persistent slider pads the whole headline figure so the estimate errs high rather than low, which is the safer direction to be wrong in when you are budgeting. It runs from zero to fifty per cent in steps of five, and starts at twenty per cent (SAFETY_MARGIN_SLIDER_MAX, SAFETY_MARGIN_SLIDER_STEP and SAFETY_MARGIN_DEFAULT, console/src/lib/cost-model-platform.ts). It multiplies the estimate; it changes nothing about your actual spend. As your real bill comes in over the first month or two, tune it down towards zero. The slider is deliberately capped well below the library’s own clamp, so the everyday control cannot reach the far edge the clamp guards.

Where the rates come from

The pricing presets are indicative public list prices, offered as a starting point you then replace with your own contracted rates. Every rate field is editable, in whatever currency your rates are quoted in, and editing any field switches the picker to Custom so it never claims a preset you have edited away from (patchPricing and applyPreset, console/src/screens/costs/pricing-section.ts).

The presets carry an honest note about egress. Cloudflare R2 lists no egress fees for data transfer out, so an offline recovery download is not separately billed for egress on R2, and the screen tells you to verify this against your own plan because terms can change. Amazon S3 and a custom destination bill egress at the rate you enter (updateEgressNote, console/src/screens/costs/pricing-section.ts). The calculator treats an in-account drill or restore as egress-free when the destination is in-account, while an offline recovery downloads the whole recoverable archive and always incurs egress at your entered rate (monthlyCost egress block, console/src/lib/cost-model.ts).

Deeper detail: what the figures are computed over, and what does not measure cost

The headline and the basis. The headline is the projected month-twelve recurring monthly cost: the destination-storage ledger plus the Cloudflare-platform cost to run the backup in your own account plus the Workers Paid plan base, padded by a safety margin you control (buildResults and recurringEstimate, console/src/screens/costs/results.ts and console/src/lib/cost-model-platform.ts). The storage / writes / reads / egress split is the destination-storage detail beneath it, all on the accumulate basis. Monthly storage is averaged at the month midpoint for the accumulate regime and at the bounded steady state for the retained regime, and the projection sums each month to the chosen horizon plus the one-off initial seal (monthlyCost and project, console/src/lib/cost-model.ts).

There are two ledgers. Alongside the destination-storage cost, the calculator estimates the Cloudflare-platform cost to run the backup in your own account, the operations a run spends across Workers, Durable Objects, D1, KV and R2 (platformCost and recurringEstimate, console/src/lib/cost-model-platform.ts). That figure is over-estimated from object counts until your runs report exact per-resource op counts, then it sharpens automatically. A per-source-type breakdown appears once there is run history, and a safety margin you control pads the headline so it errs high rather than under-quoting.

The sensitivity sweeps are honest about the model. The churn sweep is genuinely flat under the default snapshot model, because monthly cost is invariant in churn there, and the screen states that rather than hiding a zero-delta series. The retention sweep is always evaluated in the retained regime, because retention depth moves the footprint only under garbage collection (effectiveSensitivityRegime and sensitivity, console/src/lib/cost-model.ts).

This is not the engine’s metering hook. Do not confuse the calculator with engine/src/meter.ts. That hook is subrequest accounting for the sliced seal, primarily to keep a large run inside the platform’s per-invocation budget. Its optional per-resource op counts do feed the calculator’s Cloudflare run-cost ledger above, but it never measures your destination storage bill, which is set by what you store at S3 or R2.

This is not live billing. The calculator does not bill you, and nothing on this screen is connected to a payment processor. There is no payment processor in the product at all: paid plans are invoiced off-platform (see licensing and the control plane). The usage beacon is opt-in and off by default: the emitter (runBeaconEmitPass, engine/src/cron/beacon-emit.ts, wired into every cron tick at engine/src/cron/drive.ts) stays silent unless the operator sets both BEACON_URL and BEACON_INGEST_KEY, and once enabled it only POSTs a content-free, aggregate-only payload (counts plus engine and deploy version, never per-downpipe data) to the control-plane’s receiver. There is no customer-facing dashboard that surfaces this beacon data today. None of those surfaces produce a live bill; the calculator produces a local estimate.

Where this fits

The calculator pairs naturally with the parts of the product that set its inputs and live with its trade-offs. For how often a downpipe runs and the floor on that schedule, see the backing-up overview. For the retention policy whose saving the calculator projects, see retention and pruning. For the offline recovery path whose egress the calculator counts in full, see the restore flow. And for why the screen can compute all of this without sending anything anywhere, see the no-custody trust model. For what downpipes itself costs, as against what it costs to run, see licensing and editions, and for the Cloudflare account charges underneath every figure here, see the prerequisites.

Last updated .