Resource Consumption Dashboard Aggregation Race Under Streaming and REST Query Conflicts
Problem Statement
The resource consumption aggregation in src/components/dashboard/ResourceAggregator.tsx computes running totals for water, energy, and bandwidth consumption. The aggregator receives data from two sources: a WebSocket stream (live readings, 10/s) and a REST API query (historical catch-up, every 60s). When the REST query returns historical data overlapping with live stream data, both sources attempt to update the same aggregation buckets simultaneously. The WebSocket handler adds a new reading to the current hour's bucket. The REST handler reads historical data for the past 6 hours and overwrites the current hour's bucket with the REST result (which doesn't include the latest streaming readings). The aggregator loses the last 30 seconds of streaming data every 60 seconds, producing a 0.5% undercount in the displayed total.
State Invariants & Parameters
- WebSocket stream rate: 10 readings/second
- REST catch-up interval: 60 seconds
- Aggregation bucket: hourly, for past 6 hours
- Data loss per cycle: up to 300 readings (30s × 10/s)
- Invariant:
displayed_total == Σ(live_readings) + Σ(historical_readings) without overlap
Affected Code Paths
src/components/dashboard/ResourceAggregator.tsx:70-115 — Dual-source update race
src/hooks/useResourceStream.ts:50-75 — WebSocket handler
src/hooks/useHistoricalQuery.ts:45-70 — REST query handler
Resolution Blueprint
- Implement a last-write-wins with hybrid timestamp: both sources tag their updates with a hybrid logical clock (HLC). The aggregator discards updates with stale HLCs, ensuring the streaming data (more recent) always wins over REST catch-up (older).
- Use differential updates instead of bucket overwrites: the REST handler computes the delta between the REST data and the current live data, only adding missing readings rather than replacing the bucket.
- Add a source priority system: WebSocket updates have priority 1, REST updates priority 2. Priority 1 writes are never overwritten by priority 2 writes to the same bucket.
- Add a concurrent dual-source test with streaming data at 10/s and a REST catch-up overlapping by 5 minutes, verifying zero data loss.
Labels
Complexity: Hardcore
Layer: Core-Engine
Type: Race-Condition
Resource Consumption Dashboard Aggregation Race Under Streaming and REST Query Conflicts
Problem Statement
The resource consumption aggregation in
src/components/dashboard/ResourceAggregator.tsxcomputes running totals for water, energy, and bandwidth consumption. The aggregator receives data from two sources: a WebSocket stream (live readings, 10/s) and a REST API query (historical catch-up, every 60s). When the REST query returns historical data overlapping with live stream data, both sources attempt to update the same aggregation buckets simultaneously. The WebSocket handler adds a new reading to the current hour's bucket. The REST handler reads historical data for the past 6 hours and overwrites the current hour's bucket with the REST result (which doesn't include the latest streaming readings). The aggregator loses the last 30 seconds of streaming data every 60 seconds, producing a 0.5% undercount in the displayed total.State Invariants & Parameters
displayed_total == Σ(live_readings) + Σ(historical_readings)without overlapAffected Code Paths
src/components/dashboard/ResourceAggregator.tsx:70-115— Dual-source update racesrc/hooks/useResourceStream.ts:50-75— WebSocket handlersrc/hooks/useHistoricalQuery.ts:45-70— REST query handlerResolution Blueprint
Labels
Complexity: HardcoreLayer: Core-EngineType: Race-Condition