Our engine never sees your warehouse password
datahashi

BlogModeling & metrics

Fan-out-safe metrics explained

What fan-out risk looks like in metric queries and how to keep aggregates correct.

The datahashi team3 min read

Fan-out is what happens when a join multiplies rows before an aggregate runs. One order becomes three after a product-tag join. One customer becomes twelve after a historical address join. SUM and COUNT then look confident and wrong.

datahashi exists so teams can publish metric definitions once in YAML, lock them as an immutable version, and serve the same numbers to humans and agents. REST, the console, and MCP share the same request shape. MCP tools are list_catalog, describe_metric, run_semantic_query, drill_down, refine_semantic_query, and list_dimension_values, with no SQL in or out. Fan-out-safe modeling is part of making that promise real.

Why fan-out is easy to miss

Dashboards often hide the problem. A chart of revenue by month can look smooth even when a many-to-many join inflated every month by a similar factor. Cross-checks fail later: finance totals disagree, or a drill-down to a dimension that should partition the total exceeds the parent number.

The root cause is usually not the aggregate function. It is the row set fed into the aggregate. If the metric path joins a bridge table or a multi-valued attribute without collapsing it first, every measure rides the multiplied grain.

Correct aggregates need a correct grain

A fan-out-safe metric starts with an explicit grain and only joins dimensions that preserve it. When you need attributes that live at a finer grain, resolve them in the warehouse into a structure that is unique on the metric’s join keys, or define a separate metric at that finer grain.

Additive measures (order amount, event counts) are especially sensitive: every duplicated row adds again. Semi-additive and non-additive measures can also lie after fan-out, just in less obvious ways. Ratio numerators and denominators may inflate at different rates if they come from different join paths.

Design the metric path, not the ad hoc join

In a thin semantic layer, the metric definition owns the join path. Analysts and agents should not rebuild joins in prompts or notebooks and then call the result the official metric. That is how two “revenue” numbers appear for the same week.

Publish the definition. Review the YAML. Serve one version. datahashi metering helps you see usage of those published metrics. Rollups have shipped for repeated questions. A p99 target has not. Correctness of grain is still the contract.

How agents make fan-out worse (or better)

Unscoped warehouse access invites agents to invent joins. Invented joins invent fan-out. Governed MCP access flips that: the agent describes a metric, sees allowed dimensions, and runs a semantic query against the published contract. Humans using the console follow the same shape. Same metric, no hallucinated SQL.

If an agent cannot reach raw bridge tables, it cannot “helpfully” join them into a metric that used to be correct. That constraint is a feature.

Practical checks before you publish

  1. Compare the metric total with no dimensions to the sum of parts for each allowed dimension. Unexplained inflation means fan-out.
  2. Inspect uniqueness of every join key on the dimension side at the grain you claim.
  3. Prefer collapsing multi-valued attributes upstream rather than filtering them downstream.
  4. Document when a metric is only valid at certain grains so consumers do not combine incompatible slices.

Fan-out-safe metrics are boring in the best way: the undimensioned total matches the sum of honest parts, and every consumer sees the same definition. That is the bar datahashi is built to hold.

Product notes

An occasional email when we ship something or write something worth reading.