Section C · Serving, quality & governance

Serving & the Semantic Layer

The gold marts from chapter 06 are correct, conformed, and joined — but no consumer wants them in that exact shape. This chapter covers the four interfaces that reshape modeled data for its audience (BI, reverse ETL, data APIs, and the semantic layer) and argues that one of them — the semantic layer — is the single most load-bearing component in the whole platform, because it is simultaneously the contract that makes self-serve safe and the grounding that makes AI accurate.

The serving problem: one mart, many shapes

By the end of chapter 06 we have gold marts — conformed dimensions and fact tables at well-defined grains, tested and documented. It is tempting to declare victory: the data is correct, so just point people at it. But "correct in the warehouse" and "useful to a consumer" are different problems. Each consumer from Start Here needs the same modeled data in a different shape, at a different latency, through a different interface:

  • A finance analyst wants a governed dashboard where "GMV" is a clickable metric, not a SQL expression they reconstruct (and get wrong).
  • The CRM / sales tooling wants a churn-risk score pushed into the operational tool next to the customer record — not in a dashboard nobody opens.
  • The marketplace product wants the reliability score served back to the public listing page in tens of milliseconds — the feedback loop from ch. 01.
  • An LLM agent in Slack wants a machine-readable definition of every metric so its generated SQL is correct.

Four needs, four serving interfaces. The job of this chapter's layer is to take gold marts and project them through whichever interface the consumer requires — without re-deriving the business logic each time.

┌──────────────────────────────┐ │ GOLD MARTS (ch. 06) │ │ conformed dims + fact tables │ └───────────────┬──────────────┘ │ ┌────────────────┬───────────┼────────────┬─────────────────┐ ▼ ▼ ▼ ▼ │ ┌────────────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────────┐ │ │ SEMANTIC LAYER │ │ REVERSE │ │ DATA API │ │ DIRECT BI EXTRACT│ │ │ (metrics layer)│ │ ETL │ │ low-lat. │ │ (live or cached) │ │ │ ★ the hinge ★ │ │ │ │ store │ │ │ │ └───────┬─────────┘ └─────┬──────┘ └────┬─────┘ └─────────┬─────────┘ │ │ compiles SQL │ push │ serve │ │ ┌───────┼─────────┐ ▼ ▼ ▼ │ ▼ ▼ ▼ ┌────────┐ ┌──────────┐ ┌──────────┐ │ ┌────┐ ┌─────┐ ┌──────┐│ CRM │ │ product │ │ dashboards│ │ │ BI │ │ APIs│ │ AI / ││ ops │ │ features │ │ (exec/ops)│ │ │ │ │ │ │NL-SQL ││ console│ │(public) │ │ │ │ └────┘ └─────┘ └──────┘└────────┘ └──────────┘ └──────────┘ │ PRODUCT · FINANCE · SUPPLY OPS · TRUST & SAFETY · ML · AI AGENTS ◀────┘ (the consumers)

Notice where the semantic layer sits: it is not a peer of the dashboards — it is a layer above the marts and below every consumer, including BI itself. That placement is the whole argument of this chapter, and we build up to it through the three simpler interfaces first.

BI & dashboards — the dominant consumer

For all the attention AI gets, the overwhelming majority of data consumption at a company like GridDP is still a human looking at a chart. BI is the default serving interface, and getting it right covers most of the value. Two axes shape the design.

Embedded vs exploratory

Embedded BI is a small set of curated, pixel-stable dashboards — the exec GMV view, the supply-ops fleet-health board — that load fast and never surprise the viewer. Exploratory BI lets analysts pivot, drill, and build new charts against the modeled data. The same tool can do both, but the governance posture differs: embedded boards are change-controlled and reviewed; exploratory workspaces are sandboxes. Conflating them is how an unreviewed analyst chart ends up screenshotted in a board deck.

Extract vs live query

An extract (Tableau hyper, a Power BI import, a cached Superset dataset) snapshots query results into a fast columnar cache — great latency, but stale and a second copy of the truth. A live query hits the warehouse on every interaction — always fresh, but every dashboard refresh is a billed query. The senior pattern: live-query the small business marts (cheap), and extract or pre-aggregate the telemetry-derived rollups (otherwise every fleet dashboard re-scans 150 M rows/day). Match the strategy to the grain, just like ingestion in ch. 04.

Governed dashboards beat a dashboard graveyard

Every BI tool accumulates thousands of abandoned dashboards, each encoding a slightly different definition of the same metric. The fix is not better naming conventions — it's removing the ability to redefine the metric in the dashboard at all. When the metric is defined once in the semantic layer (below) and the dashboard merely references it, a "dashboard graveyard" can't drift, because there's nothing local to drift.

StackPrimary BI toolsStrengths for GridDP
A — OSS lakehouseSuperset, MetabaseNo per-seat cost; live-query Trino; pairs with Cube as the metrics source
B — DatabricksDatabricks AI/BI (Dashboards + Genie), SigmaNative to Unity Catalog; Genie reuses Unity metrics for NL queries
C — SnowflakeSigma, Looker, SnowsightLooker's LookML and Snowflake Semantic Views give a governed metrics tier

Reverse ETL — operational analytics

The interfaces above bring the consumer to the data. Reverse ETL inverts the flow: it takes modeled data from the warehouse and pushes it back into operational tools where people already work. The platform computes a value once, then syncs it to the system that needs to act on it.

  • A churn-risk score (an ML output, ch. 12) lands as a field on the customer record in the CRM, so a sales rep sees "high churn risk" without opening a dashboard.
  • A fleet-health flag ("provider X's reliability dropped below threshold") appears in the supply-ops console, triggering an outreach workflow.
  • A spend tier or credit-balance attribute syncs to the marketing tool to gate a campaign.
When reverse ETL beats a dashboard

Reverse ETL wins whenever the consumer is a person mid-workflow in another tool or an automated action, not an analyst exploring. A dashboard asks the human to remember to go look; reverse ETL delivers the number to the point of decision. The rule of thumb: if the desired outcome is "someone does X in tool Y," push the data into tool Y. If the outcome is "someone understands a trend," build the dashboard.

Mechanically, reverse ETL (Hightouch/Census, or a hand-rolled sync job in Stack A) reads a gold mart on a schedule, diffs against the destination, and writes only changes through the operational tool's API. The critical design constraint: the definition of "churn risk" or "healthy fleet" must be the one canonical definition — which, again, points at the semantic layer. A score pushed into the CRM under a definition that disagrees with the churn number on the exec dashboard is worse than no score at all.

Data APIs — serving platform data back to apps

Sometimes the consumer is the product itself. Recall the feedback loop from Start Here: GridDP computes DLPerf and reliability scores from the telemetry firehose, and those scores are surfaced on the public marketplace to rank listings. That is not a dashboard or a back-office sync — it is a low-latency read path from the platform into the live product, in the critical path of every search.

telemetry firehose ─▶ rollups (ch. 05/14) ─▶ reliability/DLPerf model (ch. 12) │ ▼ ┌───────────────────────────┐ │ SERVING STORE (online) │ │ key-value / OLAP, p99<50ms │ │ e.g. Redis, DynamoDB, │ │ ClickHouse, KV cache │ └─────────────┬─────────────┘ │ data API (REST/gRPC) ▼ ┌───────────────────────────┐ │ MARKETPLACE PRODUCT │ │ ranks listings by score │ ◀─ public users └───────────────────────────┘

The defining requirement is latency and availability under product traffic — utterly different from analytics. You do not point the marketplace at the warehouse; a warehouse query taking 800 ms at p99 is fine for a dashboard and fatal on a search page. The pattern is a low-latency serving store: the platform computes the score in batch or streaming, writes it to a key-value or OLAP store optimized for point reads (Redis, DynamoDB, ClickHouse), and the product reads through a thin data API. This is the same offline→online split as the feature store in ch. 12, and the same store often does both jobs.

The data platform is now in the product's blast radius

The moment a data API serves the live product, an analytics outage becomes a product outage, and a bad score becomes a bad ranking that loses real revenue. Data APIs need product-grade SLAs, versioning, and rollback — and the score's definition must be governed, because a silent change to "reliability" reorders the entire marketplace. This is the strongest possible argument that metric definitions cannot live scattered in dashboards.

The metric-definition chaos

Now the problem this chapter exists to solve. The four interfaces above all need business metrics — GMV, utilization, churn, active GPUs. In a platform without a semantic layer, each metric gets re-derived in SQL by whoever builds each artifact. And those derivations quietly diverge.

Take "active GPU" — a number the CEO quotes in board meetings. Watch it fracture across five teams:

"How many ACTIVE GPUs do we have?" │ ┌───────────┬───────────────┼───────────────┬───────────────┐ ▼ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ SUPPLY │ │ FINANCE │ │ PRODUCT │ │ EXEC DECK │ │ ML TEAM │ │ "reported │ │ "billed │ │ "in an │ │ "listed on │ │ "util>5% │ │ in last │ │ for ≥1 │ │ active │ │ the market │ │ this hour"│ │ 10 min" │ │ hour" │ │ rental" │ │ right now" │ │ │ └────┬─────┘ └────┬─────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ │ │ │ │ 17,020 15,900 11,400 16,800 13,250 └────────────┴──────────────┴───────────────┴──────────────┘ ▼ FIVE DASHBOARDS, FIVE DIFFERENT NUMBERS "Why do the two dashboards disagree?" — every exec, eventually

Every one of those numbers is "correct" given its query — they are answering subtly different questions while wearing the same label. The same fracture happens to GMV (does it include credits? cancelled rentals? interruptible spend?), utilization (allocated time, or time the GPU was actually computing?), and churn (no rental in 30 days, or formally closed account?).

The executive-trust crisis

The damage isn't just a wrong number — it's the loss of trust in the platform itself. The first time two dashboards disagree in a leadership meeting, the next hour is spent debugging definitions instead of deciding anything, and from then on every number is met with "but is this one right?" A platform that can't give one trusted answer per metric has failed at its core job, no matter how clean the pipelines are. This is a governance failure, not a SQL bug — and you cannot conformance-test your way out of it after the fact.

The semantic layer (headless BI)

The fix is structural: define each dimension and metric exactly once, in a version-controlled, code-reviewed spec that lives above the warehouse, and have every consumer compile its query through that spec. This is the semantic layer (a.k.a. the metrics layer, or "headless BI" — BI logic decoupled from any single BI tool).

It is not another database. It is a thin compilation layer: a consumer asks for a metric (gmv) sliced by dimensions (by gpu_model, by week), and the semantic layer compiles that request into the one correct SQL against the gold marts and runs it on the warehouse. The business logic — the join, the filter, the aggregation — lives in the spec, not in the consumer.

semantic-layer: griddp / metrics.yml (MetricFlow / Cube style)
semantic_model:
  name: rental_economics
  defaults: { agg_time_dimension: rental_day }
  model: ref('fct_rental_meter')          # the gold fact from ch. 06

  entities:
    - { name: rental,   type: primary,   expr: rental_id }
    - { name: account,  type: foreign,   expr: account_id }
    - { name: gpu,      type: foreign,   expr: gpu_id }

  dimensions:
    - { name: rental_day, type: time, type_params: { time_granularity: day } }
    - { name: gpu_model,  type: categorical, expr: gpu_model }
    - { name: is_interruptible, type: categorical }

  measures:
    - { name: gross_spend_usd, agg: sum, expr: meter_usd }
    - { name: credits_usd,     agg: sum, expr: credit_applied_usd }
    - { name: take_rate_usd,   agg: sum, expr: meter_usd * take_rate }

metrics:
  - name: gmv
    label: "GMV (Gross Marketplace Value)"
    description: "Gross client spend on settled rentals, net of credits.
                  Excludes cancelled rentals. The ONE definition."
    type: derived
    type_params:
      expr: gross_spend - credits
      metrics:
        - { name: gross_spend_usd, alias: gross_spend }
        - { name: credits_usd,     alias: credits }

When any consumer — Superset, a Python notebook, a reverse-ETL job, or an LLM — requests gmv by gpu_model for last_week, the layer compiles it deterministically to:

compiled SQL (what the semantic layer generates)
SELECT
    gpu_model,
    SUM(meter_usd)            - SUM(credit_applied_usd) AS gmv
FROM analytics.fct_rental_meter
WHERE rental_day >= DATE_TRUNC('week', CURRENT_DATE) - INTERVAL '7 days'
  AND rental_day <  DATE_TRUNC('week', CURRENT_DATE)
  AND rental_status = 'settled'        -- the "excludes cancelled" rule, applied ONCE
GROUP BY gpu_model
ORDER BY gmv DESC;

Every consumer that asks for GMV gets this exact SQL shape. The "excludes cancelled" rule and the "net of credits" rule are applied in one place, by one definition. There is no longer a way for the finance dashboard and the exec deck to disagree, because neither one writes the SQL — the layer does.

Why it lives ABOVE the warehouse, not in it

You could bake metrics into a dbt model, but then only SQL-on-the-warehouse consumers benefit, and you re-materialize a table per slice. The semantic layer sits one level up so it is consumer-agnostic: the same gmv definition serves a BI tool, a REST/GraphQL metrics API, a reverse-ETL sync, and an LLM's tool call — each gets correct, governed SQL for free. One definition, every interface. That universality is exactly why it can be the hinge.

The implementations differ by stack but the concept is identical: dbt MetricFlow and Cube (Stack A), Unity Catalog metric views + Databricks AI/BI (Stack B), Snowflake Semantic Views consumed by Cortex Analyst (Stack C). All compile a declared metric to warehouse SQL on demand.

The GridDP metric catalog

Here is the catalog the semantic layer encodes for GridDP. Each metric has a precise definition, a grain, and a source fact (one of the three grains from Start Here). This table is the contract — getting it written down and agreed is half the battle.

MetricPrecise definitionGrainSource fact
GMVGross client spend on settled rentals, net of credits; excludes cancelled. On-demand + interruptible.per dayfct_rental_meter
Take-rate revenueGridDP's cut: SUM(meter_usd × take_rate) on settled rentals. The company's actual top line.per dayfct_rental_meter
Utilization %GPU-seconds with gpu_util_pct > 5 ÷ GPU-seconds the card was rentable. Measures real compute, not allocation.per GPU-hourfct_telemetry_hourly (telemetry rollup)
Fill rateRented GPU-hours ÷ available (listed) GPU-hours. Marketplace liquidity from the supply side.per day, per gpu_modelfct_telemetry_hourly
Active GPUsDistinct GPUs reporting telemetry in the last 10 min AND attached to a non-stopped instance. The one definition that ends the 5-way fight.point-in-timefct_telemetry_hourly + dim_instance
ARPUTake-rate revenue ÷ active billing accounts in the period.per monthfct_rental_meter + dim_account
ChurnAccounts with ≥1 rental in prior period and zero in current period ÷ prior-period active accounts. (Behavioral, not account-closure.)per monthfct_rental_meter + dim_account
Gross margin per GPU-hour(Take-rate revenue − payout to provider − attributable infra cost) ÷ rented GPU-hours.per GPU-hour, per gpu_modelfct_rental_meter + fct_payout
Bid clearing priceLowest winning interruptible bid_usd_hr per gpu_model per hour — the market-clearing line.per hour, per gpu_modelfct_bid_event (the bid grain)
The catalog is mostly a negotiation, not a coding task

The hard part of this table is not the YAML — it's getting finance, product, and supply ops to agree that "active GPU" means the one thing in row five. The DPE's role is to force that conversation, write down the winner, and make the spec the single source. Once agreed, encoding it is an afternoon; the agreement is what was missing for years.

Why the semantic layer is THE hinge

This is the chapter's thesis. The semantic layer is not just a nice-to-have de-duplication of SQL — it is the single component that two of the platform's highest-value capabilities both depend on, and neither works without it:

  • Self-serve (ch. 09) is only safe because of it. Letting hundreds of analysts and PMs query freely is dangerous if "query freely" means "write your own GMV SQL." With a semantic layer, self-serve means "pick from governed metrics and dimensions" — the user composes, the layer guarantees correctness. The layer is what makes self-serve safe instead of a chaos multiplier.
  • AI / text-to-SQL (ch. 13) is only accurate because of it. An LLM pointed at raw tables hallucinates joins and invents metric logic — it will happily compute GMV five different wrong ways, just like the five teams above. Pointed at the semantic layer, the LLM doesn't write the metric SQL at all; it picks metrics and dimensions by name and the layer compiles the correct query. The semantic layer is the grounding that turns a plausible-but-wrong text-to-SQL toy into something an exec can trust in Slack.
One contract, two superpowers

The same artifact — a version-controlled definition of dimensions and metrics — is simultaneously the contract that makes human self-serve safe and the grounding that makes AI self-serve accurate. That is why it is the hinge of Section C and D both. Skip it, and self-serve degrades into a definition free-for-all and your AI confidently returns wrong numbers. Build it, and you've solved the trust problem for every consumer at once — human and machine. No other component in the platform has that leverage.

Semantic layer per stack

Same concept, three implementations — consistent with the stack table in Start Here.

ConcernStack A — OSS lakehouseStack B — DatabricksStack C — Snowflake
Metrics enginedbt MetricFlow or CubeUnity Catalog metric viewsSnowflake Semantic Views
Compiles SQL toTrino / SparkDatabricks SQL (Photon)Snowflake warehouses
Serves BI viaCube REST/SQL API → SupersetAI/BI Dashboards + GenieSigma / Looker / Snowsight
Serves AI viaself-hosted LLM → Cube APIGenie (Unity metrics)Cortex Analyst (Semantic Views)
Definition lives inGit (dbt/Cube YAML)Unity Catalog + GitGit + Snowflake objects

The decision driver is the same as elsewhere: Stack A maximizes portability (Cube/MetricFlow are vendor-neutral and front every engine), while B and C trade some portability for a metrics layer that's native to their BI and AI surfaces, so Genie and Cortex Analyst inherit the definitions automatically. In practice a hybrid keeps the canonical definition in Git (dbt/MetricFlow) and projects it into whichever native surface each consumer uses.

Takeaway

  • Gold marts are correct but shapeless; four serving interfaces reshape them per consumer — BI (the dominant one), reverse ETL (push into operational tools), data APIs (low-latency serving back to the product), and the semantic layer.
  • Without a semantic layer, every metric — active GPUs, GMV, utilization, churn — gets re-derived per artifact and silently diverges, producing the executive-trust crisis where two dashboards disagree.
  • The semantic layer defines each dimension and metric once, in version-controlled YAML above the warehouse, and compiles every consumer's request to the one correct SQL.
  • It is the platform's hinge: the same contract makes human self-serve safe and AI text-to-SQL accurate. Skip it and both fail; build it and you solve trust for every consumer at once.

With one trusted definition per metric in place, we can finally hand the keys to the consumers without fear. Next: turning that foundation into a real self-serve practice — contracts, documentation, and the cultural shift from ticket queue to platform. → Self-Serve & Enablement