# Data Request: `tenant_daily_qps` — daily tenant-level QPS rollup

**Requested by:** Heidi Liu (Growth) · **Date raised:** 2026-08-13
**Blocking:** GAP-025 — Sustained workload qualification has been `Not available` in every bi-weekly edition to date
**Priority:** High — this is the single measurement gap that blocks four separate sections of the bi-weekly growth report

---

## 1. Why this is needed

The bi-weekly growth report cannot currently answer the most basic product question we have: **after a user signs up, do they ever run a real workload?**

Today we can measure that a tenant signed up, and we can measure that they were eventually billed. Everything in between is dark. Specifically we cannot answer:

- What share of new signups become durable workloads (vs. one-off experiments that never come back)?
- Which acquisition channels and which regions produce durable workloads, rather than just volume?
- Should a tenant be promoted in the PQL queue based on real workload intensity, rather than on log-row counts?

We have been substituting a **one-hour QPS point sample** taken at report-build time (most recently: 91 of 7,958 queued tenants at QPS ≥1). That sample is useful for triage but is not a trend, not a cohort measure, and cannot support any product or investment decision. It must not be used as an activation metric, and we have been careful not to.

**What we need is a daily rollup, not a live query endpoint.** We are not asking for a real-time dashboard. We need one row per tenant per day, retained historically, so cohorts can be evaluated after they mature.

---

## 2. What "QPS" means for this request

**QPS = database queries processed per second, averaged over a 24-hour calendar day, per tenant, summed across all clusters that tenant owns.**

Precise definition:

```
tenant_avg_qps_24h(tenant, date)
  = (total queries processed by all clusters owned by `tenant` during `date` in UTC)
  / 86400
```

Notes on the definition, in order of how likely they are to be misread:

- **Averaged over the full 24 hours, not over active seconds.** A tenant that runs 86,400 queries in one busy minute and nothing else has an average QPS of 1.0 for that day. This is intentional — we are measuring sustained workload, and burst-normalised averaging is what makes the threshold meaningful.
- **Summed across clusters, not per cluster.** The unit of analysis is the tenant (the commercial entity), not the cluster. A tenant with three clusters at 0.4 QPS each qualifies at 1.2 QPS.
- **UTC calendar day**, to match the rest of the growth data warehouse.
- **All query types count** — reads, writes, DDL. If it is a query the engine processed, it counts. We are measuring "is this database doing work", not "is this database doing valuable work".
- If Grafana/Prometheus stores this as a rate metric rather than a counter, the daily value should be the **time-weighted mean of the QPS series over the day**, which is equivalent.

---

## 3. Required output: one table, one row per tenant per day

Table name: `tenant_daily_qps`
Suggested location: `gcp-martech-infra.plg_dashboard.tenant_daily_qps` (this is where the growth dashboard already expects to find it)

| Column | Type | Description |
|---|---|---|
| `metric_date` | DATE | UTC calendar day |
| `tenant_id` | STRING | Must join to `gcp-digital-bi.dwh_cloud_platform.cloud_tenants.tenant_id` |
| `tenant_avg_qps_24h` | FLOAT64 | Per the definition in §2. `NULL` when not observable — never `0` as a substitute |
| `peak_qps_1h` | FLOAT64 | Highest 1-hour average QPS that day. Optional but valuable — separates spiky batch tenants from steady serving tenants |
| `cluster_count` | INT64 | Number of clusters contributing to the tenant's total that day |
| `source_status` | STRING | One of `ready` / `no_clusters` / `unavailable`. See §4 |

### 3.1 `source_status` — the most important column

This is what makes the table safe to build metrics on. Please do not omit it.

| Value | Meaning | `tenant_avg_qps_24h` |
|---|---|---|
| `ready` | Monitoring covered this tenant for this day; the value is real | a number, may legitimately be `0.0` |
| `no_clusters` | Tenant had no clusters that day — genuinely zero workload, correctly measured | `0.0` |
| `unavailable` | Monitoring did not cover this tenant-day (retention expired, exporter down, cluster in a region not scraped) | `NULL` |

**The distinction between `ready` + `0.0` and `unavailable` + `NULL` is the entire point of this request.** "We measured this tenant and it did nothing" and "we did not measure this tenant" are opposite facts. If they are collapsed into `0`, every downstream metric silently understates activity and we will report false declines. Our report has a standing rule that missing coverage is never presented as zero usage, and this column is what lets us honour it.

---

## 4. Retention and backfill

- **Retention: minimum 120 days**, ideally 400. The qualification measure (§5) needs a 30-day observation window on a cohort that has already matured, so at any moment we are querying data that is 30–60 days old. A 30-day retention window makes the metric permanently uncomputable.
- **Backfill: as far back as the monitoring system allows**, ideally to 2026-04-01 (the growth report's `overall` window start). If backfill is impossible, that is acceptable — say so explicitly and we will start history from the first available date rather than inferring anything about earlier periods.
- **Refresh: daily.** The growth dashboard treats the table as stale if `MAX(metric_date)` is more than 2 days old.

---

## 5. What we will compute from it

The governed metric this unblocks, already defined in our metric registry as `sustained_qps_ge_1_3d_30d`:

> **Sustained workload qualification** — the share of a mature signup cohort that reached a 24-hour average QPS of at least 1 on at least 3 distinct days during signup day 0 through day 30.

```sql
-- covered_days = 31 requires complete observation across the whole day 0–30 window.
-- A tenant with any `unavailable` day is excluded from the denominator rather than
-- being counted as a failure — this is why source_status must be present.
COUNTIF(covered_days = 31 AND qualifying_days >= 3)
  / COUNTIF(covered_days = 31)
```

The thresholds (≥1 QPS, ≥3 days, 30-day window) are deliberately low. This is not a measure of a large customer — it is the minimum bar for "this tenant is running something real and repeatedly, not kicking the tyres once".

---

## 6. Tiering — consistent with our existing PQL model

We already run a PQL priority model in production (`pql_v0_2026_06_24`, serving the APAC and EMEA sales queues) which tiers tenants P0/P1/P2 using `logs_28d` and `active_days_28d` as its usage-intensity signals. Those are **log-row counts**, which is a weak proxy: log volume varies by workload type and tells us nothing about whether the database is actually serving traffic.

The intent is to graduate the PQL model from log-row proxies to real workload intensity. So please make the following thresholds separable in the output (the three `qps_tier` bands below), because they map onto how we already think about priority:

| Band | Threshold | Current PQL analogue | Meaning |
|---|---|---|---|
| **Qualified** | 24h avg QPS ≥ 1 | the `p1_query_usage` tier | Real, repeated workload — the minimum bar |
| **Substantial** | 24h avg QPS ≥ 10 | `p0_high_sustained_activity` | Production-shaped traffic; a genuine sales-handoff signal |
| **Heavy** | 24h avg QPS ≥ 50 | above any current tier | Material workload; should trigger proactive account attention |

We do not need these as pre-aggregated columns — a raw `tenant_avg_qps_24h` per tenant-day lets us derive all three, and lets us change the thresholds later without another request. The bands are given so you can sanity-check that the metric's resolution is adequate at the low end: **precision below 1.0 QPS matters to us.** A metric that rounds to integers is not usable, because the entire qualification threshold sits at 1.

---

## 7. Where the data probably lives

We do not know the internal topology and defer entirely to your judgement here. From the growth side, the likely candidates are:

- **Grafana / Prometheus** — if cluster QPS is already scraped for the SRE dashboards, a recording rule aggregating to daily per-tenant is likely the shortest path. Note Prometheus default retention is usually far shorter than the 120 days we need, so this probably requires a remote-write target or a daily export into BigQuery rather than querying Prometheus directly.
- **TiDB Cloud internal telemetry / metering** — if per-cluster query counts are already collected for billing or capacity planning, that source is preferable, because it is already reconciled and retained.
- If neither exists at tenant grain, a per-cluster daily table plus the existing cluster→tenant mapping is equally fine; we can do the tenant-level aggregation ourselves.

**We do not need a new pipeline built if one of these already produces the numbers.** Pointing us at an existing table with the right grain and retention fully resolves this request.

---

## 8. Acceptance check

We will consider this delivered when all of the following are true:

1. `tenant_daily_qps` exists and is queryable by the growth service account (`sa-plg-growth@gcp-martech-infra.iam.gserviceaccount.com`).
2. `MAX(metric_date)` is within 2 days of today, and stays that way on a daily refresh.
3. At least 60 days of history are present (120+ preferred), so a mature cohort can be evaluated immediately.
4. `source_status` is populated, and `unavailable` tenant-days carry `NULL` rather than `0.0`.
5. Spot check: pick 3 tenants we know are running production workloads and 3 we know are dormant; the values are directionally correct and the dormant ones show `ready` + a near-zero value, not `unavailable`.

---

## 9. What we will stop doing once this lands

- Reporting `Not available` for sustained workload qualification in the bi-weekly report (currently in its 1st consecutive edition).
- Using a one-hour point sample as operating context, which readers persistently over-read as an activation rate despite the caveats.
- Tiering the PQL sales queues on log-row counts, which we believe is producing both false P0s and missed P0s in the APAC and EMEA queues.

---

**Contact:** Heidi Liu, Growth — heidi.liu@pingcap.com
**Consuming systems:** `growth-reports-n-analysis/bi-weekly` (report runner), `tidb_growth_data/plg-dashboard-next` (the `/api/workload-activation` route already implements the §5 query and currently fails closed because the table is absent)
