Skip to main content
Your progress
0 of 6 complete0%
T4 / M4.6 / L5 OF 5 / Architect LEVEL / 9 min

Live computation, not stored

Outcome

By the end of this lesson, you will be able to defend the architectural choice to compute budget spend live (rather than store a current_spend_usd column), predict the performance characteristics, and reason about why this choice matches the two-source cost model.


PrerequisitesM4.6.L1-L4 (budget basics + signals) · T0.M0.4 (two-source cost model)
Time9 minutes

1. Concept

A budget’s spend so far is worked out fresh every single time it is asked for.

There is no stored total anywhere in the database. Every dashboard, every API call and every notification adds it up again from the underlying cost records, within that budget’s scope and dates.

That sounds wasteful and is deliberate. A stored total can be wrong; a total computed from the records cannot disagree with them.

Terminal window
ARCHITECTURE: LIVE COMPUTATION:
Read a budget
↓
SELECT SUM(costColumn(hasBilling))
FROM cost_records
WHERE (scope matches budget's filter)
AND (date in current budget period)
↓
Return the sum
ARCHITECTURE NOT USED: STORED CURRENT_SPEND:
Cron writes current_spend_usd column periodically
Read budget → return stored value
(the original design; the column was dropped in April 2026)

The live-compute architecture is what makes budgets always-current and always-correct. It is also what lets a change to a budget’s amount or threshold take effect on the very next read.

Why live computation

Four reasons, in order of importance:

Terminal window
1. ACCURACY
Always current. No stale data. New cost records show up in the
budget reading immediately (next read).
Stored values lag by however long the last cron ran.
2. SIMPLICITY
One source of truth (cost_records). No sync logic.
Stored values require maintenance:
- When cost records change, recompute
- When a group's membership changes, recompute
- Handle race conditions during recompute
- Detect and reconcile drift
None of this exists in the live-compute model.
3. FLEXIBILITY
Every target type (resource, group, account, AI provider) is
just a different filter on the same query. Stored would need
a maintained total per target, kept in step with every
membership change.
4. CORRECTNESS
No drift. The computation matches what reports show.
Stored values can diverge from actuals (rare, but possible
bugs cause this; we eliminate the class of bug entirely).

Performance characteristics

The concern with live computation is performance. Aren’t aggregate SQL queries slow?

Terminal window
TYPICAL BUDGET READ COSTS (with proper indexing):
Per-resource budget: ~5 ms (single resource_uid filter)
Per-group budget: ~10-25 ms (small to medium group)
Per-account budget: ~10-50 ms (one account filter)
Org-wide rollup: ~50-150 ms (full table scan with date filter)
BUDGET HEALTH WIDGET listing 12 budgets: ~200-400 ms total
Fast enough for sub-second page renders.

The indexing strategy is the key. The cost_records table is indexed on (org_id, date, scope_columns): the aggregate queries hit the index, not the full table.

Compare to the stored alternative:

Terminal window
STORED-COLUMN READ COSTS:
Per budget read: 1-2 ms (single row read)
But: more complex sync logic
Stored values lag behind actuals by minutes-to-hours
Re-computation on membership change adds latency to write

The 10-50 ms live-compute cost is the right trade for the simplicity gains. Sub-100ms is well within UI responsiveness budgets.

The costColumn() function

The SQL aggregation uses a helper, costColumn(), that picks the cost source per the two-source model (T0.M0.4):

Terminal window
costColumn():
always → COALESCE(actual_cost_usd, cost_usd)

It once took a hasBilling switch; since June 2026 it ignores it and always prefers the billed figure. In practice:

  • Where billing sync has filled in actual (post-discount, billing-data) cost for a day, that is used
  • Where it has not (no billing sync, or days not yet synced), the calculated rack-rate cost is used

The result: budgets reflect what the customer will actually pay (when billing data is available) and what we think they will pay (when it isn’t), automatically.

What live compute means for budgets

Terminal window
1. SPEND UPDATES IMMEDIATELY when new cost records land
- Daily: the cost-and-rollup job writes new rack-rate cost
records (19:55 UTC)
- Daily: billing sync updates actual_cost_usd (20:20 UTC)
- In both cases, next budget read picks up the new value
- Budget transitions from green to yellow to red as data arrives;
no batch processing delay
2. ONE NUMBER, NO COST-SOURCE LABEL
- The Budgets page shows a single spend figure per budget
- It does not say which days are billed and which are rack rate
- If billing sync has not run for recent days, those days are
rack rate (T0.M0.4); check the sync before arguing about cents
3. BUDGET CHANGES TAKE EFFECT IMMEDIATELY
- Threshold changed: next read evaluates status against the new
threshold
- Amount changed: next read re-evaluates % used and status
- No stored spend to "recompute": there is nothing to migrate
- The target is fixed: to watch a different resource, group or
account, create a new budget on it (one target per budget);
its spend is there on the first read

Notification firing

Alerts fire from the daily cost-and-rollup job (19:55 UTC), using the same live-computation logic:

Terminal window
DAILY COST-AND-ROLLUP JOB:
For each active budget:
spend = live_compute_spend(budget)
level = exceeded if spend >= 100% of the budget
warning if spend >= the budget's alert threshold
none otherwise
If a channel subscribes to budget.warning / budget.exceeded:
notify that channel (again on each daily run while the
level holds)
Else (no channel configured):
email once per level per month, recorded in
budget_alert_state.alerted_level; re-armed if spend
falls back below the level

The single stored field is that email bookkeeping flag, not the spend value. The spend itself is computed live. Alerts are not written to the audit log, which records API changes only.

Historical migration

ZopNight originally stored current_spend_usd in the cost service’s own budgets table. That table was dropped in April 2026.

Terminal window
MIGRATION (2026-04-30):
1. Move budget definitions to the configuration service and
convert all budget reads to live computation
Validate against the stored value (parity check)
No customer-visible change
2. Drop the old budgets table, and its stored column
The daily job stays: it still computes spend live
to fire alerts, but no longer writes spend back
CUSTOMER IMPACT: zero
Reads use the same underlying cost data, just computed live.
Performance is comparable (within 50 ms).
Accuracy improved (no stale-data window).
Audit log is unchanged.

The architecture is intentionally lean. Storing a value that can be computed from other values is duplication; duplication invites drift; drift causes audit headaches. Live compute eliminates the class.

When live compute would NOT be the right choice

A useful counterfactual:

Terminal window
LIVE COMPUTE BREAKS DOWN if:
- Underlying data is very expensive to query (billion-row scans
without indexing)
- Aggregation is recursive or graph-walking
- Realtime requirement is sub-millisecond (it's not for budgets)
For ZopNight's cost_records table:
- Indexed appropriately; queries hit indices
- Aggregation is straightforward SUM with date + scope filter
- 50 ms is fine for budgets
So live compute is the right answer here.

For a different domain (e.g., a real-time game scoreboard), the trade-offs might shift. For budgets specifically, live compute wins.

How ZopNight uses live computation

Every surface that displays budget spend reads it live: dashboard widgets, the Budgets page, the API (GET /orgs/{orgID}/budgets), and the daily alert job. All of them share the same live-spend computation: there is no second source-of-truth that could disagree.

For customers who want to export budget state, the API returns the computed value at the moment of the call. Snapshotting a budget’s spend at a specific time means calling the API at that time; the system does not retain historical “spend at point X” values (those can be derived from cost_records time-series if needed).


2. Demo

A threshold-and-amount change scenario:

Terminal window
SCENARIO: Customer tunes a budget mid-month.
T+0 Budgets page, dev-platform-eu group budget
Spend: $4,200 of $4,800 (87.5%, live computed)
Alert threshold: 90% → status green
T+30 s Edit the budget; set the alert threshold to 80%
Save
T+31 s Budgets page refreshes
Same spend, $4,200 (87.5%)
87.5% is now above 80% → status yellow
T+60 s Edit again; raise the amount to $6,000
Save
T+61 s Budgets page refreshes
$4,200 of $6,000 = 70% → status green
T+2 min The team also wants staging-eu watched
A budget's target cannot be edited, so create a new
budget on the staging-eu group: $2,600/month
First read: spend $1,950 (75%), computed on the spot
NO BATCH delay. NO cron wait. NO job to rerun.
This works because:
- The budget definition stores the target, amount and threshold
- Every read sums cost_records for that target
- Status is derived on the read from spend, amount and threshold
- No stored value to refresh
CONTRAST with stored-column architecture:
- Save would trigger a recompute job
- User refreshes; sees stale value until job completes
- User wonders if the change took effect
- Possible race condition between recompute and read

The user gets immediate feedback. The system gets simpler. Both win.


3. Hands-on (5 min)

Verify live computation:

Terminal window
1. Open the Budgets page in your org; pick one budget
Current spend (note value): $__________
% used and status: __________
2. Come back after the next daily cost run (after 20:20 UTC)
Refresh the Budgets page
Has the spend value changed? Yes / No
New value: $__________
3. Edit the budget's alert threshold to just below its current
% used. Did the status turn yellow on the next read?
Yes / No
Set the threshold back afterwards.
4. Audit observation:
Did the budget transition green/yellow/red during the month?
__________
5. Architectural check:
Call the API: GET /orgs/{orgID}/budgets
Your budget's entry should show the same computed spend
API response: $__________ (matches UI? yes / no)

If the API and UI values diverge, that’s a bug worth reporting: they should always match because they share the live-compute function.


4. Knowledge check

Q1

A budget’s current spend is:

A. Computed live on every read by aggregating cost_records for the budget’s scope and current period
B. Stored in a database column for fast reads and then refreshed on a nightly cron schedule
C. Read straight from the cloud provider’s own budget API on each and every request made
D. Cached for one hour after it has first been computed for that period, and then reused

Show answer

Correct: A. Always current; no stale data; no sync logic. The costColumn() helper prefers billed cost and falls back to rack rate per the two-source model. Live computation is the architectural choice. The current_spend_usd column was dropped in April 2026; the design is intentional.

Q2

A new cost record lands at 14:25 UTC. The budget reflects:

A. After the next cron run (could be hours)
B. After 24 hours
C. Immediately, on the next budget read
D. Only once the period rolls over to the next month

Show answer

Correct: C. Live computation means latency = SQL query time (~10-50 ms). The budget transitions across thresholds in near-real-time as cost records arrive. Immediate. There is no batch step between cost arrival and budget visibility.

Q3

Why no stored current_spend column?

A. Simplicity and correctness
B. Performance: stored would be slower
C. It would break the polymorphic budget scoping
D. Database constraints

Show answer

Correct: A. One source of truth (cost_records) Stored would require sync logic, risk drift, and add complexity. Live compute is fast enough (10-50 ms typical) for the UI responsiveness budget. The trade-off favors live compute decisively. Architectural decision for simplicity. Performance is comparable; correctness is improved.


5. Apply

The Budgets page and the Budget Health dashboard widget show live-computed spend. No additional configuration; the architecture handles it.

For customers debugging “why is my budget showing X” questions, the answer is always: look at the underlying cost_records for the budget’s scope and time window. The budget number is a SUM; the components are queryable.



Module quiz

Complete M4.6 → 10-question module quiz.


Start with the bill.

Foundations takes about five hours. The first lesson is nine minutes.

Free curriculum. No paywall. 290 lessons across 7 courses, plus an optional 24-lesson beginner course. Browse every course without an account; a free account opens the lessons, saves your progress and opens the exams for three publicly verifiable credentials. Read it on the train, take the exam on a Saturday, list the credential on your résumé Monday.

5h median time to finish Foundations
free account, every course, no paywall
3 certifications, each with a public verifier
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·