\Drupal\strata\Estimate PriceTable

What one provider charges, and what it gives away.

Object storage is priced on three axes and only one of them is bytes. Storage is charged per gigabyte-month, writes are charged per million (S3 calls them PUT, R2 calls them Class A), and reads are charged per million (GET, Class B). A backup system is unusual in that it writes constantly and reads almost never, so the write axis is the one that decides the bill - and it depends on the flush interval rather than on the size of the site.

The consequence is stated in one line: at a fifteen-second interval a site of any size performs about 201,635 writes a month, which is a fifth of R2's free allowance and about a dollar a month on S3. On R2 the free tier absorbs the whole thing; on S3 that dollar is a floor a small site cannot get under.

These are shipped defaults, not a promise. Published list prices as read on 2026-08-20, for R2 and for S3 Standard in us-east-1. A provider changes its prices and a different region charges differently, so every field is overridable and the estimate names the table it used.

Summary

Methods
Properties
Constants
__construct
r2
s3
s3InfrequentAccess
local
all
of
forProvider
storageCost
writeCost
readCost
egressCost
monthlyCost
hasFreeTier
jsonSerialize
id
label
storagePerGbMonth
writesPerMillion
readsPerMillion
egressPerGb
freeStorageBytes
freeWrites
freeReads
BYTES_PER_GB
REQUESTS_PER_UNIT
No protected methods found
No protected properties found
No protected constants found
No private methods found
No private properties found
No private constants found

Constant

BYTES_PER_GB

BYTES_PER_GB = 1000000000

Bytes in a gigabyte, decimal, as every provider bills.

Providers price per GB and mean 10^9, not 2^30. Using the binary value would understate a bill by 7.4%.

REQUESTS_PER_UNIT

REQUESTS_PER_UNIT = 1000000

Requests in the unit prices are quoted in.

Properties

$id

$id : string

Type

string

$label

$label : string

Type

string

$storagePerGbMonth

$storagePerGbMonth : float

Type

float

$writesPerMillion

$writesPerMillion : float

Type

float

$readsPerMillion

$readsPerMillion : float

Type

float

$egressPerGb

$egressPerGb : float

Type

float

$freeStorageBytes

$freeStorageBytes : int

Type

int

$freeWrites

$freeWrites : int

Type

int

$freeReads

$freeReads : int

Type

int

Methods

__construct()

__construct(string  $id, string  $label, float  $storagePerGbMonth, float  $writesPerMillion, float  $readsPerMillion, float  $egressPerGb = 0.0, int  $freeStorageBytes = 0, int  $freeWrites = 0, int  $freeReads = 0) : mixed

Constructs a table.

Parameters

string $id

Short lowercase identifier, matching the storage provider's own id where there is one.

string $label

Human-readable name.

float $storagePerGbMonth

Dollars per gigabyte-month of stored data.

float $writesPerMillion

Dollars per million write requests.

float $readsPerMillion

Dollars per million read requests.

float $egressPerGb

Dollars per gigabyte of egress. Zero on R2, which is the reason a restore is free there.

int $freeStorageBytes

Stored bytes included at no charge each month.

int $freeWrites

Write requests included at no charge each month.

int $freeReads

Read requests included at no charge each month.

Throws

\InvalidArgumentException

When the id is empty or any price is negative.

Returns

mixed —

r2()

r2() : \Drupal\strata\Estimate\PriceTable

Cloudflare R2.

Egress is free, which is what makes a restore cost nothing to pull, and the free tier is generous enough that a full year of database history for a large site fits inside it.

Returns

\Drupal\strata\Estimate\PriceTable —

The table.

s3()

s3() : \Drupal\strata\Estimate\PriceTable

AWS S3 Standard, us-east-1.

No free allowance beyond the first-year introductory tier, which is not modelled because an estimate that expires after twelve months is worse than one that does not exist.

Returns

\Drupal\strata\Estimate\PriceTable —

The table.

s3InfrequentAccess()

s3InfrequentAccess() : \Drupal\strata\Estimate\PriceTable

AWS S3 Standard-Infrequent Access.

Where file blocks belong. Cheaper to keep and dearer to read, which is the right trade for content that is written once and read only during a restore.

Returns

\Drupal\strata\Estimate\PriceTable —

The table.

local()

local() : \Drupal\strata\Estimate\PriceTable

A store on the server's own disk.

Priced at zero rather than excluded, so the estimator can compare a local target against a remote one without a special case. Disk is not free, but its cost is not per-request and is not something this module can read.

Returns

\Drupal\strata\Estimate\PriceTable —

The table.

all()

all() : array<string,\Drupal\strata\Estimate\PriceTable>

Every shipped table, keyed by id.

Returns

array

The tables.

of()

of(string  $id) : \Drupal\strata\Estimate\PriceTable

One shipped table by id.

Parameters

string $id

The id.

Throws

\InvalidArgumentException

When no shipped table has that id.

Returns

\Drupal\strata\Estimate\PriceTable —

The table.

forProvider()

forProvider(string  $id) : self

The table to cost a storage provider with, falling back rather than raising.

Four tables are shipped and the provider select offers every registered submodule, so azure, gcs, b2 and null all name a provider with no published prices - as does any id an add-on registers. PriceTable::of() is the right shape for a caller naming a table it knows exists; a caller naming whatever a site happens to have configured needs this one, because the throw lands on the settings page the operator would use to change the provider back.

Parameters

string $id

A storage provider id, which may be empty or unknown.

Returns

self —

The table for that provider, or the local one, which charges nothing.

storageCost()

storageCost(int  $bytes) : float

What storing this many bytes costs for a month, after the free allowance.

Parameters

int $bytes

Stored bytes.

Returns

float —

Dollars.

writeCost()

writeCost(int  $requests) : float

What this many write requests cost for a month, after the free allowance.

Parameters

int $requests

Write requests.

Returns

float —

Dollars.

readCost()

readCost(int  $requests) : float

What this many read requests cost for a month, after the free allowance.

Parameters

int $requests

Read requests.

Returns

float —

Dollars.

egressCost()

egressCost(int  $bytes) : float

What egressing this many bytes costs.

Only a restore or a verify egresses anything, so this is zero in the steady state and is shown for the restore estimate rather than the monthly one.

Parameters

int $bytes

Bytes read out.

Returns

float —

Dollars.

monthlyCost()

monthlyCost(int  $bytes, int  $writes, int  $reads = 0) : float

The monthly bill for a given usage.

Parameters

int $bytes

Stored bytes.

int $writes

Write requests per month.

int $reads

Read requests per month.

Returns

float —

Dollars per month.

hasFreeTier()

hasFreeTier() : bool

Whether this provider gives anything away.

Returns

bool —

TRUE when any allowance is non-zero.

jsonSerialize()

jsonSerialize() : array<string,mixed>

{@inheritdoc}

Returns

array

The table as data.