# QPS-first signal contract

**Status:** design agreed, **not implemented**. Blocked on
[`../latam/REQUEST_snapshot_signal_history.md`](../latam/REQUEST_snapshot_signal_history.md).
**Date:** 2026-08-26 · **Applies to:** EMEA · APAC · LATAM

> ⚠️ **The three regions share the model *contract*, but their deployed tier rules have diverged.**
> Verified 2026-08-26 by reading the actual scoring code in `pingcap-support` against live data in
> all three databases: EMEA has a bare `or error_message` hard gate into P1; **APAC does not** — 838
> APAC tenants carrying the same error class stay in P2. Do not assume a rule written for one region
> is live in another. Check the engineering repo per region before porting anything below.
**Companion to:** [`data-contract.md`](data-contract.md) — that file governs the *current-state row
per tenant*; this one governs the *history series per tenant*.

---

## 1. Why QPS carries more weight now

This is a forced reallocation, not a preference. Four of the signals in
[`aha-moments.md`](aha-moments.md) are unavailable because their upstream sources are broken or were
never built — all four are registered as **P0** in `GROWTH_DATA_BREAKS_AND_RESTORATION.md`
(2026-08-20, `0xF4ng/tidb-growth-data`):

| Signal | Upstream state | Item in that doc |
|---|---|---|
| Active-user detail (`logs_*`, `active_days_*`) | Stopped updating 2026-07-07 | 1 |
| Data import | Serverless-tier records zero since March; scope covers only the UI Import path | 2 |
| First DDL | No source covering all access paths | 3 |
| Daily tenant QPS (24h average) | Not delivered | 4 |

What remains collected daily, per tenant, in every region is `tenant_avg_qps`, `cluster_count`, and
`has_credit_card`. **The design question is therefore not "which new signal do we add" but "how much
more can be extracted from the three we already collect".** The answer is: considerably more, because
today all three are consumed as instantaneous scalars compared against fixed thresholds, and their
time dimension is discarded.

**Scope limit — read before building on this.** QPS is a *workload* signal. It says a database is
doing work; it does not say a human decided to adopt. It cannot substitute for import (migration
intent) or for the activation measures broken since 07-07, and this document does not propose that it
should. Items 1–4 above remain the correct fix. This contract is what we run in the meantime, and it
should be unwound as those land.

## 2. Measured baseline (2026-08-26, direct read of all three regional DBs)

| | EMEA | APAC | LATAM |
|---|---:|---:|---:|
| Tenants | 2,682 | 7,788 | 221 |
| `tenant_avg_qps` populated | 215 (**8.0%**) | 655 (**8.4%**) | 123 (**55.7%**) |
| QPS ≥ 1 | 26 | 76 | 21 |
| QPS ≥ 10 | 6 | 23 | 7 |
| QPS ≥ 50 | 2 | 7 | 1 |
| `company_ai_core` | 6 | 14 | **0** |

Two facts govern everything below.

**Coverage is the binding constraint, not thresholds.** In EMEA and APAC, ~92% of tenants have `NULL`
QPS. Any QPS-based rule selects from the 8% that are measured; the rest cannot qualify regardless of
their real workload. `NULL` here means *not measured* and must never be read as zero.

**LATAM's 55.7% is 7× the other two regions.** The newest region has the most complete collection.
Finding out what LATAM does differently is likely the cheapest route to raising EMEA/APAC coverage —
worth investigating before any larger collection work. **One lead:** LATAM carries only 4 HTTP-429
enrichment errors against EMEA's 219. Its enrichment pipeline is not being rate-limited, and its QPS
coverage is the highest. Whether that is one cause or two correlated symptoms of the same
provisioning headroom is unverified — but it is the first thing to check.

## 3. Tier A — computable today, no new data

These use only the current row and the existing events table. No dependency on
`REQUEST_snapshot_signal_history.md`.

| # | Rule | Fields | What it corrects |
|---|---|---|---|
| A1 | **Admit high-load tenants that fit gating excludes** | `tenant_avg_qps`, `entity_ai_category` | `QPS ≥ 10 AND ai_core` has a theoretical ceiling of 6 in EMEA and is **unreachable in LATAM** (`ai_core` = 0). Across three regions **25** tenants at QPS ≥ 10 sit outside P0 (EMEA 3 · APAC 16 · LATAM 6) — in EMEA, 14 of the 20 tenants in [1,10) sit in P2 with no human follow-up |
| A2 | **QPS × cluster shape** | `tenant_avg_qps` × `cluster_count` | High QPS on one cluster = a single application under load (capacity conversation). Low QPS across many clusters = environment sprawl (early). Currently indistinguishable |
| A3 | **Card/usage direction check** | `has_credit_card` × `tenant_avg_qps` | Card + zero QPS is paid-but-unused (churn risk) yet currently scores **+10**. QPS + no card is the real conversion target |
| A4 | **Error triage — filter our own errors first** ⭐ | `error_message` × `tenant_avg_qps` | In EMEA, **219 of 223 error rows (98.2%) are `domain intelligence failed` / HTTP 429** — our own enrichment pipeline being rate-limited, nothing to do with the customer. Yet `error_message` is a bare hard gate into P1, and also scores +10 urgency: the same failure counted twice. Of the 216 P1 tenants it pulls in, 142 have no cluster (132 `NULL` + 10 zero) and 132 have `NULL` QPS. **Step one is excluding internal error classes, not triaging customer errors.** Only after that does the customer-facing split (error + QPS collapsed = blocked, error + normal QPS = noise) mean anything |
| A5 | **Event follow-through** | `*_tenant_events` (`CreateImport`, `UpdateSpendingLimit`) × current QPS | Did QPS rise after the import completed, or after the spending limit was raised? A commercial event with no workload response is a weak signal, not a strong one |

**A4 then A1 are the two largest immediate opportunities in this document, and neither needs an
engineering change or any new data — both are rule changes.** A4 is larger in volume (it removes
~66% of EMEA P1 as artefact) and is EMEA-only; A1 is smaller but adds real, currently-unattended
opportunity in all three regions.

## 4. Tier B — requires the snapshot series

Blocked on [`../latam/REQUEST_snapshot_signal_history.md`](../latam/REQUEST_snapshot_signal_history.md)
(both changes: added columns **and** full-population daily write). Nothing here is computable on
current data — the history was never stored.

| # | Feature | Definition | Use |
|---|---|---|---|
| B1 | **Percentile tiering** | `max` or `p95` over trailing N days, replacing the same-day point value | Removes tier flapping. One EMEA tenant flipped P0↔P1 across five days on readings spanning three orders of magnitude; `max` holds it stable |
| B2 | **Workload shape** ⭐ | Coefficient of variation (σ/μ) + range span over trailing N days | Batch (high CV, multi-decade range) · steady-serving (low CV, sustained > 1) · trial (single spike then zero) · dormant. **Determines which statistic should tier that tenant** — see §5 |
| B3 | **Trend** | 7-day and 14-day change ratio; consecutive rising days | A tenant climbing 0.8 → 5 is a better target than one flat at 8 — the first is ramping. Direction is currently invisible |
| B4 | **First crossing** | First date `QPS > 0`, first date `QPS ≥ 1`, derived from the series | A real activation timestamp — partially replaces the first-query signal broken since 07-07 |
| B5 | **Decay / churn warning** | Was ≥ 1, now zero for N consecutive days | Supplies the **decay rule the queues lack**. Sizing, corrected 2026-08-26: EMEA P1 is **288 now**, measured on the full `emea_active_tenants` table, and ~66% of it is the A4 false positive — simulated removal drops it to ~99. An earlier “143 (08-12) → 288 (08-25), doubled in 13 days” framing is **withdrawn**: 143 is not reproducible from any source, and the two figures did not share a measurement basis. The only real series is the snapshot table, which covers ~261 of 2,682 tenants per day and puts P1 at **93 (08-12) → 105 (08-25), +13%** — growth, not doubling. **Fix A4 before adding decay**, or decay will be tuned against a population that is mostly artefact |
| B6 | **Cluster trajectory** | Day-over-day `cluster_count` difference | 1 → 3 is expansion. APAC forces P0 at `cluster_count ≥ 50` but reads only the static value |
| B7 | **Card timing vs. load curve** | `payment_method_added_at` aligned to the QPS series | Used-then-paid (healthy) vs. paid-then-idle (risk) are opposite customers on identical current-row values |

## 5. Tiering statistic follows workload shape

The core design rule, and the reason B2 ranks above the rest:

| Shape | Statistic to tier on | Rationale |
|---|---|---|
| Batch (high CV) | `max` or `p95` over the window | The mean is meaningless when readings span decades; the peak is the real capacity |
| Steady-serving (low CV) | `median` or trailing mean | Stable by definition; the average is representative |
| Trial (single spike, then zero) | Not tierable on QPS alone | One spike is not a workload |
| Dormant (was active, now zero) | Trigger B5 decay | Downgrade, do not tier up |

A single global threshold applied to a single daily reading — today's design — is correct for none
of these four.

## 6. Limits that must be carried into any downstream claim

1. **One sample per tenant per day** (a one-hour Prometheus rate at 11:00). Day-level trend,
   dispersion, range and drop-to-zero are all sound. **Intra-day periodicity is not recoverable**
   and must not be claimed.
2. **`QPS > 0` does not yet strictly mean "user active"** — the 2026-07-30 audit flags possible
   non-user traffic in the metric, with IT confirmation still pending.
3. **`NULL` ≠ `0`.** Missing coverage is never rendered as zero usage, in any feature, at any stage.
4. **Regional P0/P1 counts are not comparable** and must never be summed or placed side by side
   without stating the per-region definition and date.

## 7. Sequence

1. **A4 — exclude internal enrichment errors from the P1 gate.** Largest single correction available,
   EMEA-only (APAC does not have this gate). Removes ~66% of EMEA P1 as artefact. Rule change, no new data.
2. **A1 — admit the high-load tenants that fit gating excludes.** 25 tenants across three regions at
   QPS ≥ 10 sitting outside P0 (EMEA 3 · APAC 16 · LATAM 6). Rule change, no new data.
3. **Decay rule** — after step 1, so it is tuned against a real population rather than the artefact.
4. **Ship `REQUEST_snapshot_signal_history.md`** — unblocks all of Tier B; 14 days of accumulation
   before the first features compute.
5. **B1 + B5** — smallest Tier B change; fixes flapping and gives decay a measured basis.
6. **B2** — the real quality gain, and the one worth validating carefully before it drives tiering.
7. **Raise QPS coverage** — start with the LATAM 429 lead in §2.
8. **`tenant_daily_qps`** ([pql-infrastructure#18](https://github.com/pingcap-inc/pql-infrastructure/issues/18))
   — remains the correct end state for a true 24h-average measure. Steps 1–7 do not wait on it.

Steps 1–3 need no engineering change and no new data. They are the only part of this document that
can move today.

Validate on EMEA before the other two: it holds the longest snapshot history (18,927 rows across
2,686 tenants, 2026-05-25 → 08-25) — but it is **sparse**: ~261 rows/day against 2,682 live
tenants, so any snapshot count is roughly a 10% sample and must never be compared directly
against a full-table count. LATAM is in burn-in with small volume and noisy early counts.
