Registry views¶
views
¶
Analyst-facing SQL views over the registry.
The three-tier registry stores raw rows; a data scientist reviewing a run shouldn't have to
re-derive the same roll-ups every time. These views are the curated read surface — the point
where "rows in BigQuery" become "assets you review". They are pure CREATE OR REPLACE VIEW
strings (no client), so they render + snapshot-test offline exactly like the table DDL, and
registry.tables.ensure_views executes what this renders.
Five views, matched to the questions a run prompts:
v_run_summary— how did each run go, and how efficiently? One row per run: the scaling knobs (n_series,n_models), the header's whole-runruntime_seconds, and a time ledger rolled up from the run's job rows:n_jobs,longest_job_seconds(the critical-path job),jobs_seconds(every job's wall added up — families run in parallel, so this exceeds the run's own wall when the DAG overlapped),jobs_span_seconds(first job start to last job end), andoverhead_seconds = jobs_span_seconds − longest_job_secondswith its shareoverhead_fraction. That overhead is the wall inside the run's job window that the slowest family did not account for: the barrier ensemble, the native BigQuery job, family start skew, and — under Composer — the scheduler's gaps between tasks. It means the same thing whichever tier finalized the header, which is why it is derived from the job rows rather than from header columns: the header'sruntime_secondsis the launcher's wall undermain.runbut the slowest job under Composer (airflow_tasks.finalize_run), and the platform wall a family batch stamps on the header ($.total_wall_s) is one family's, not the run's — the earliertotal_wall_s − runtime_secondsdefinition compared those two and came out ≤ 0 on nearly every run.
Three rules make the ledger exact rather than approximately right. Every term is measured on the
job rows' own started_at → ended_at timestamps (not on runtime_seconds, which the
launcher reads off a monotonic clock — mixing the two left overhead a few milliseconds negative
under clock slew), so a span contains its longest member and overhead_seconds is ≥ 0 by
arithmetic; the bracket exceeds the row's runtime_seconds by the row-write latency, which is
the only difference between them. The rows are the current attempt per family (the same
highest-attempt rule v_run_jobs applies) written after the header row — the summary
describes the header's own launch, so a run resumed hours later is not charged the idle time
between its launches. And repair rows are excluded (families ending in
registry.ids.REPAIR_SUFFIX): a repair is filed days later under its own token, writes no
header, and would otherwise stretch the span to cover the gap. A header with no job row in its
launch (a run that failed before its first family launched, or one written by an older release)
carries NULL in the ledger and still renders.
The executor columns (executor_instances / executor_cores / max_executors /
executor_memory / executor_memory_overhead), dcu_milli_seconds, and
runtime_version are unpacked from the header's job_telemetry, and those keys are written
by the run's Dataproc Serverless batches, last to finish wins — see
batch_telemetry._stamp_job_telemetry, which merges by key; a Ray job or a Dataproc cluster job
stamps other keys. So they are the echoed shape of a family batch: exact for a single-family
Serverless run, one family's answer for a mixed run, and NULL for a run that submitted no
Serverless batch.
The per-family decision — one entry per family under $.sizing — is the whole story, left as
raw JSON because its interesting parts are nested: read a run's shape from the scalar
columns, read why, per family, with JSON_QUERY(sizing, '$.deep_learning')
(resources.audit.sizing_telemetry). NULL on a run submitted before this existed, or one that
left the platform's own defaults standing. capacity is the same shape for the other run-level
wait: one attempt ledger per shared cluster, keyed by service
(shared_clusters.shared_capacity_path), recording every region tried before one had room. A
per-family walk is on the family's row instead — see v_run_jobs. A forced re-run of an
unchanged config appends a second header row under the same run_id; the view keeps only the
latest (QUALIFY ROW_NUMBER() … ORDER BY created_at DESC NULLS LAST = 1) so one run is always
one row.
-
v_run_jobs— what jobs ran for this run, on what runtime/hardware, and how did each fare? One row per(run_id, family)= the run's DAG as executed: the deterministicjob_id, the resolvedruntime/spark_mode/hardware/gpu_type, the platform's ownsystem_job_id, the per-jobstatus, itsstarted_at/ended_atbracket andruntime_seconds— the launcher's wall around the platform job, from submit to terminal, which is whatv_run_summary's ledger is built from.provision_secondsandexecute_secondssplit that wall the way the platform saw it: how long it took to find and start capacity before any of our code ran, and how long the work ran once it had (job_telemetry.$.platform, stamped byjob_launch.launch_family_jobfrom one probe read of the finished job — seebatch_telemetry.platform_phases). They are populated for Dataproc Serverless and VertexCustomJobfamilies, whose APIs expose the start transition, and NULL for the rest; a longprovision_secondsnext to a shortexecute_secondsis the signature of a family that should move runtime, region, or machine shape.failure_reasonsays why a FAILED row failed (CAPACITY_EXHAUSTEDis the first token) andcapacitycarries the whole attempt ledger — every candidate tried, its verdict, and the cloud's verbatim message. Both are NULL for a job that never had to wait, which is nearly all of them.device_verdictis the one word that says whether the accelerator on this row'shardwaredid any work (device_audit) — NULL for every CPU family, soWHERE device_verdict != 'ENGAGED_UTILISED'is the "what did I pay for and not use" query — anddevice_usebeside it carries the counts and the peak byte figure the word was decided from. The platform's DCU and cluster-shape figures are not on this row: the batch telemetry lands on the header (above), and a column that unpacked it from here would read NULL on every job. A--forcere-run appends a higher-attemptjob under the same(run_id, family); the view keeps only the current one (QUALIFY ROW_NUMBER() … ORDER BY attempt DESC = 1), so the forwardrun_id → current jobmap is one row per family. -
v_model_leaderboard— which model won, per run? One row per(run_id, model_type, ensemble_id): cell counts, the error rate (a model failing every cell — the libgomp/lightgbm class of problem — shows aserror_rate = 1.0), median fit time, and the mean decision metrics where a backtest populated them. The entry point for "is this model worth keeping" before ensembling.ensemble_idis NULL for base models (so they group exactly as before) and theEnsembleConfigdigest for ensemble pseudo-models — so two ensemble configs scored under onerun_idkeep theirensemble_<strategy>rows distinct instead of collapsing into one. Result writes are append-only and at-least-once (a task retry or a--forcere-run can re-append a cell), so — like the two views above — the leaderboard first collapses to one row per cell (ROW_NUMBER() … PARTITION BY run_id, ts_id, model_type, fold_id, ensemble_id ORDER BY created_at DESC NULLS LAST = 1, latest write wins) before aggregating; otherwise a duplicated cell would double-count and skewmean_wape/mean_mae/n_cells.mean_staleness_gapis non-NULL only under the frozen backtest schemes, and reads as "what this model loses, in the run's decision metric, if it is never refit" — the column that turns refit cadence from a guess into a number.refit_modesbeside it says whether the cohort earned that ranking the way the run asked; see the note underv_model_leaderboard_comparable, which carries the same column. -
v_backtest_coverage— how much of the panel did each model actually get scored on? The questionv_model_leaderboardcannot answer, and the one that decides whether its ranking means anything. A ragged panel does not give every series the same number of folds:backtest.make_foldsdrops the oldest folds a short series cannot afford, so one model'smean_wapecan be an average over ten folds of two thousand series while its neighbour's is an average over one fold of two hundred. One row per(run_id, model_type, ensemble_id, backtest_status, n_folds_achieved)with the series count and its share of that model's panel — long-format rather than one wide row per model, because the achieved-fold histogram has no fixed width and a wide shape would need a self-join that breaks on the NULLensemble_idof every base model.backtest_statusisfull/reduced/unscored/failed, or NULL where the run never asked for a backtest at all. Read it beside the leaderboard: a model whose panel is mostlyreducedwon on an easier question.
backtest_refit is in the grouping for the same reason n_folds_achieved is — it is the
other way two rows of the same leaderboard can be answers to different questions. Under
expanding_frozen a model without the re-conditioning seam falls back to a fresh fit per fold
and lands here as unsupported; that row's error is a refit model's error sitting next to
frozen ones. Under the two refit schemes every row reads per_fold and the column adds
nothing, which is the correct amount for it to add. mean_staleness_gap rides along on the
same rows: what that cohort loses, in the run's decision metric, when the model is never refit.
It is populated on the frozen schemes and on any refit scheme that set
backtest.control_arm, so a per_fold row carrying a gap is not a contradiction — it is a
refit run that also scored the never-refreshed counterfactual.
v_model_leaderboard_comparable— which model won, holding the question fixed? The same ranking asv_model_leaderboard, rebuilt so the numbers are comparable across models rather than merely present. Two differences, and both are the point. First, it is restricted to the holdout fold — the newest one,MAX(fold_id) OVER (PARTITION BY run_id), which every series that achieved any fold achieved, becausebacktest.make_foldskeeps a survivor's originalfold_idand drops from the oldest end. That is derived from the rows rather than from the config on purpose: the view has no config to read. Second, the error is pooled, not averaged —SUM(|y_true − yhat|) / SUM(|y_true|)over every series at once, so a fleet number is one WAPE of the whole panel instead of the mean of per-series WAPEs, where a single near-zero series can dominate. It readsbacktest_oofrather thanforecast_metadatabecause that is the only table holding per-fold truth;forecast_metadatastores one rolled-up row per cell and cannot be restricted to a fold after the fact. Ensembles appear here beside the base models becauseensemble_runwrites its blended OOF into the same table (keyed byensemble_id). Carryn_seriesinto any comparison you draw from it — equaln_seriesacross the rows is the evidence that the models answered the same question, and unequaln_seriesis a finding. Like the views above it collapses to one row per cell before aggregating, because a task retry can re-append rows and a partial duplication skews a pooled ratio (a uniform one does not — it doubles both sides). Every cell-table writer now stampscreated_at, so theORDER BY created_at DESC NULLS LASTtiebreak is a real newest-wins rule rather than an arbitrary pick: a repaired cell re-fits the model, and a stochastic learner does not reproduce its old numbers to the bit, so the duplicate pair is a genuine conflict and the repair has to win it.NULLS LASTis what makes that hold across the seam — rows written before the stamp existed carry NULL and must lose to any row that carries a timestamp.
refit_modes is on both leaderboards, as a sorted STRING_AGG(DISTINCT …) rather than in
the grouping. It is the answer to "was every row in this ranking scored the same way?" without
splitting a model into two rows to say so: recondition means the whole cohort was frozen,
recondition,unsupported means some of it refit instead and this ranking is mixing two
questions. The comparable view has to reach into forecast_metadata for it — backtest_oof
is per-row truth and carries no per-cell refit mode — and joins on COALESCE(ensemble_id, '')
because ensemble_id is NULL on every base model and NULL never equals NULL. A LEFT join
so a model whose metadata row is missing still ranks, with a NULL refit_modes rather than
vanishing from the leaderboard.
JSON_VALUE reads scalars straight out of the native JSON job_telemetry column (the
registry is native BigQuery, so the column is the real JSON type — JSON_VALUE works on it
unchanged; see ddl.py). Views tolerate a NULL job_telemetry (runs before this column,
or whose telemetry capture was skipped): the unpacked fields come back NULL, the row still renders.
Public surface: VIEW_NAMES, render_create_views.
render_create_views
¶
Render {view_name: CREATE OR REPLACE VIEW statement} for the analyst views.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
dataset ref, |
required |
Each statement is CREATE OR REPLACE (idempotent — safe to re-run on every ensure).
v_run_summary's ledger excludes repair rows by the same registry.ids.REPAIR_SUFFIX the
id scheme files them under, substituted here rather than spelled in the SQL so the two cannot
drift.