Skip to content

Bandwidth metering, throttle & cutoff

Bandwidth on Imaginook is pooled per account — a monthly cap (25 GB by default) shared across all of a user's sites — and metered at the edge, since cache hits never reach the origin. This document covers how counts get in, how usage is graded into a state, and what each state does.

The ledger

Edge metering produces per-host byte counts per window. Those land in the bandwidth_samples table, keyed uniquely on (host, period_key) so re-ingesting the same window is idempotent. An account's bandwidth_used is the sum of its hosts' samples for the current period; BandwidthMeter::recompute() rolls it up.

Ingest (edge → origin)

The edge pushes counts in as JSON and the origin ingests them out-of-band — there is no public HTTP ingest endpoint (nothing browser-reachable can write usage):

php artisan imaginook:ingest-bandwidth --file=/path/to/edge-counts.json
# or stream on stdin:
cat edge-counts.json | php artisan imaginook:ingest-bandwidth

Each element is {"host": "alice.imaginook.com", "period": "2026-07-04T14", "bytes": 12345}. Malformed rows (missing host/period) are skipped so one bad line never aborts a batch. Wire this to a Cloudflare GraphQL Analytics pull (or nginx log rollup) on a short cron; the upsert makes overlapping windows safe.

States

BandwidthMeter::stateFor(used, cap) grades usage against the account's cap — users.bandwidth_bytes, a 25 GB default set by the column/factory, overridable per row:

StateRangeEffect
ok< warn% of poolserved normally
warningwarn% … 100%served normally; owner warned once
throttled100% … pool × cutoff_multiplierserved but rate-limited at the edge (courtesy slow-down)
cutoff≥ pool × cutoff_multipliertaken offline until the monthly reset (runaway ceiling)

Thresholds live in config/imaginook.php under bandwidth (warn_percent = 80, cutoff_multiplier = 5). A non-positive cap disables gating entirely (an account is never throttled).

Enforcement (HostMap)

The derived state is denormalised onto users.bandwidth_status and enforced through the nginx map files, mirroring how suspensions and noindex work:

  • throttled hosts are written to the throttle map (_nginx/throttle_map.conf, map $host $imaginook_throttle). The wildcard server block applies a courtesy limit_req / "over budget" page to matches. Sites stay online.
  • cutoff accounts have their hosts dropped from the served host map — the same mechanism a suspension uses, so nginx returns 404 until the reset.

BandwidthMeter applies this immediately on a state change (best-effort); imaginook:nginx-sync (HostMap::rebuild()) is the reconciling backstop and is itself bandwidth-aware, so the maps are correct even if a live signal is missed. The DB is always the source of truth.

Notifications

Each upward step into warning / throttled / cutoff notifies the owner once (debounced by the stored status, so a busy account isn't spammed):

  • in-app always, via Notification type bandwidth.{state};
  • email via the queued BandwidthThreshold mailable, gated by imaginook.bandwidth.notify. The email carries only the account's own figures and reset date — never any other user's data.

Monthly reset

imaginook:reset-bandwidth runs daily and, for any account past its period_reset_at, zeroes bandwidth_used, clears its ledger rows, rolls the period forward a month, resets bandwidth_status to ok, and restores any throttled/cut-off hosts to the served map. Only due accounts are touched, so it is safe to run every day.

AGPL-3.0 · built in the open.