Proposal
Provision Cassandra storage on the Generated Data Platform (AQS) cluster, analytics keyspace, for the relative page-trending signal of the Attribution API.
A Flink job computes the signal in near-real-time and produces it to the Kafka stream pageview.trending.relative.v1 (schema traffic/pageview_trending_relative). For the MVP the data reaches Cassandra in hourly batches through the existing tooling (Kafka → Gobblin → HDFS/Hive → dbt/Airflow load into Cassandra). The writer logic is deliberately stateless — every event maps to a plain upsert with no per-row conditionals — so the batch load can later be replaced by a streaming Kafka → Cassandra writer (Flink) without changing the tables or their semantics.
Prep Pantry and design document.
The data we have
One event per page per sliding-window close, for every page above a minimum view count in the window, keyed by (wiki_id, page_id):
| Field | Type | Description |
|---|---|---|
| wiki_id | string | Wiki database name, e.g. enwiki |
| page_id | bigint | MediaWiki page ID |
| window_start_dt / window_end_dt | timestamp | Bounds of the sliding window; window_end_dt is the event time |
| view_count | bigint | Page views for this page in the window |
| view_count_avg / view_count_stddev / view_count_max | double / bigint | The page's hourly baseline, from the weekly pageview_stats dataset |
| z_score | double | (view_count − view_count_avg) / view_count_stddev — how unusual the current window is for this page |
| is_trending | boolean | Whether the page is considered trending in this window |
| window_baseline_duration | string | ISO-8601 duration of the baseline bucket, e.g. PT1H |
Only the current trending state per page needs to be served — historical lookback is out of scope — and consumers only need the boolean plus a timestamp, so the serving tables keep none of the statistical columns (those remain available in Kafka/Hive).
Proposed tables
Table 1 — per-page point lookup (the Attribution API's main read). Every event results in an upsert of the page's latest state, trending or not:
CREATE TABLE analytics.pageview_trending_relative_per_page ( wiki_id TEXT, page_id BIGINT, is_trending BOOLEAN, updated_at TIMESTAMP, PRIMARY KEY ((wiki_id, page_id)) ) WITH default_time_to_live = 172800; -- 48h
- One row per page; each load upserts the page's latest event (all columns, unconditionally).
- is_trending = false events overwrite a previous true row, so trending stops promptly on the point lookup.
- updated_at = window_end_dt of the latest event — a freshness indicator, regardless of trending state.
- Read semantics: live row with is_trending = true → trending; false row, missing row, or expired row → not trending.
- The TTL garbage-collects pages that stop emitting events entirely (pages below the stream's minimum view count produce no events at all); a page untouched for 48h is cleaned.
Table 2 — currently-trending pages per wiki (list/discovery read). Only events with is_trending = true are written:
CREATE TABLE analytics.pageview_trending_relative_per_wiki ( wiki_id TEXT, page_id BIGINT, last_trending_at TIMESTAMP, PRIMARY KEY ((wiki_id), page_id) ) WITH default_time_to_live = 21600; -- 6h — this TTL is the list's decay
- The table is the trending list: SELECT ... WHERE wiki_id = ? returns exactly the currently-trending pages of a wiki, no filtering needed.
- Because only trending events land here, last_trending_at (= the event's window_end_dt) is by construction the last time the page was trending.
- Rows leave the list by TTL expiry: a page that stops trending lingers here for up to the TTL (the per-page lookup in Table 1 still stops promptly).
Usage
Reads — via Data Gateway:
GET /public/analytics/pageview_trending_relative_per_page/:wiki_id/:page_id GET /public/analytics/pageview_trending_relative_per_wiki/:wiki_id
The first endpoint returns if a given page_id is trending or not.
The second endpoint returns the list of pages trending for a given wiki_id. The TTL on this table is 6h (Should we change it?) but the returned records have an updated_at field that tells the called how long ago the page was trending. On batches, this value is expected to be between 1h and 3h, if it's higher, it probably means the page is no longer trending. On streaming it will behave better and we could reduce the TTL.
The MVP process will send writes hourly from a dbt/Airflow schedule. For the future, we could replace this by a streaming process.
Data Size
The data is hard to estimate today. The current topic uses 4GB on one day, it might be bigger (8GB ~ 10GB), so I don't think we expect more than 20GB on a Table. The Flink app has many parameters that can be adjusted, and the data could grow exponentially if we start counting pages with less than 10 views per hour, but it isn't in scope.

