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.
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.
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.
| Stack | Primary BI tools | Strengths for GridDP |
|---|---|---|
| A — OSS lakehouse | Superset, Metabase | No per-seat cost; live-query Trino; pairs with Cube as the metrics source |
| B — Databricks | Databricks AI/BI (Dashboards + Genie), Sigma | Native to Unity Catalog; Genie reuses Unity metrics for NL queries |
| C — Snowflake | Sigma, Looker, Snowsight | Looker'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.
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.
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 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:
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 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_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:
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.
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.
| Metric | Precise definition | Grain | Source fact |
|---|---|---|---|
| GMV | Gross client spend on settled rentals, net of credits; excludes cancelled. On-demand + interruptible. | per day | fct_rental_meter |
| Take-rate revenue | GridDP's cut: SUM(meter_usd × take_rate) on settled rentals. The company's actual top line. | per day | fct_rental_meter |
| Utilization % | GPU-seconds with gpu_util_pct > 5 ÷ GPU-seconds the card was rentable. Measures real compute, not allocation. | per GPU-hour | fct_telemetry_hourly (telemetry rollup) |
| Fill rate | Rented GPU-hours ÷ available (listed) GPU-hours. Marketplace liquidity from the supply side. | per day, per gpu_model | fct_telemetry_hourly |
| Active GPUs | Distinct 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-time | fct_telemetry_hourly + dim_instance |
| ARPU | Take-rate revenue ÷ active billing accounts in the period. | per month | fct_rental_meter + dim_account |
| Churn | Accounts with ≥1 rental in prior period and zero in current period ÷ prior-period active accounts. (Behavioral, not account-closure.) | per month | fct_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_model | fct_rental_meter + fct_payout |
| Bid clearing price | Lowest winning interruptible bid_usd_hr per gpu_model per hour — the market-clearing line. | per hour, per gpu_model | fct_bid_event (the bid grain) |
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.
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.
| Concern | Stack A — OSS lakehouse | Stack B — Databricks | Stack C — Snowflake |
|---|---|---|---|
| Metrics engine | dbt MetricFlow or Cube | Unity Catalog metric views | Snowflake Semantic Views |
| Compiles SQL to | Trino / Spark | Databricks SQL (Photon) | Snowflake warehouses |
| Serves BI via | Cube REST/SQL API → Superset | AI/BI Dashboards + Genie | Sigma / Looker / Snowsight |
| Serves AI via | self-hosted LLM → Cube API | Genie (Unity metrics) | Cortex Analyst (Semantic Views) |
| Definition lives in | Git (dbt/Cube YAML) | Unity Catalog + Git | Git + 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