Approach: compute per-user rolling 7-day aggregates that remain correct despite late events up to 48h by using event-time windows with watermarking, partitioned storage, and incremental materialized views (or streaming upserts). Use daily partitions for input, incremental processing that reprocesses the last 3 days (48h + slack) and merges into a materialized daily-per-user base; derive 7-day rolling metrics by joining the last 7 daily buckets.Example (Spark SQL / Databricks / BigQuery-style):sql
-- 1) ingest events into partitioned table by event_date (YYYY-MM-DD) and append
CREATE TABLE events (
user_id STRING,
event_time TIMESTAMP,
value DOUBLE
) PARTITIONED BY (event_date DATE);
-- 2) compute daily per-user aggregates (materialized daily view) with idempotent upsert
MERGE INTO daily_user AS D
USING (
SELECT user_id, DATE(event_time) AS event_date,
COUNT(*) AS cnt, SUM(value) AS sum_value
FROM events
WHERE event_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 5 DAY) -- process window
GROUP BY user_id, DATE(event_time)
) S
ON D.user_id = S.user_id AND D.event_date = S.event_date
WHEN MATCHED THEN UPDATE SET cnt = S.cnt, sum_value = S.sum_value
WHEN NOT MATCHED THEN INSERT (user_id, event_date, cnt, sum_value) VALUES (...)
;
-- 3) rolling 7-day metric from daily_user
SELECT u.user_id,
SUM(u.sum_value) OVER (PARTITION BY u.user_id ORDER BY u.event_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS sum_7d,
SUM(u.cnt) OVER (PARTITION BY u.user_id ORDER BY u.event_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS cnt_7d
FROM daily_user u
WHERE u.event_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 6 DAY) AND CURRENT_DATE();
Key ideas and reasoning:- Windowing: aggregate at day granularity to reduce state; compute 7-day rolling using window functions on materialized daily rows.- Watermarking / Late data: accept late events up to 48h; reprocess/rewriter daily aggregates for the last 2–3 days (48h + guard) to correct prior days. In streaming engines, set watermark = event_time - 48h and use allowed lateness to emit updates.- Partitioning: partition events and daily_user by event_date for small reads and efficient reprocessing; cluster by user_id for faster per-user merges.- Incremental/materialized views: store daily_user as a materialized table (or Delta table) and perform idempotent MERGE upserts; only re-compute recent partitions to bound work.- Performance: process 100M events/day by parallelism, vectorized aggregation, and limiting reprocessing to last 3 days; keep daily aggregates small (one row per user per day).- Correctness validation: - Backfill tests: replay historical data and compare full recompute (golden) vs incremental results for a sample of users/dates. - Time-travel / snapshot comparisons (Delta/BigQuery) to ensure MERGE produced expected deltas. - Canary pipeline: run full-accuracy job daily on a small shard and validate against production. - Alerting: row-count, checksum, and distribution drift alerts; reconcile sums to raw-event totals.- Edge cases: out-of-order events beyond 48h (decide policy: drop, flag, or manual correction); users with timezone concerns—use UTC event_time; duplicates—dedupe using event_id and upsert idempotency.- Alternatives: keep per-event streaming state with sliding window aggregations (more real-time but high state), or use approximate structures (HyperLogLog) if counts can be approximate.This design balances correctness (by reprocessing bounded late windows), cost (small reprocess footprint), and latency (near-daily freshness with incremental updates).