Skip to content
About
Main content

The semantic (metrics) layer

Why is a metric a computation and not a value, and what does a semantic layer really enforce?

The constraint is categorical, and it is the reason this mechanism exists at all: a metric is a computation, not a value.

"Active customer" is a predicate (what counts as active), plus a window (the last 30 days? the calendar month?), plus a deduplication rule (one person who ordered three times counts once). It cannot be stored as a , because it is only defined relative to a grouping and a time frame, and both are chosen by the person asking.

Where does the definition live?

Therefore it lives somewhere. If it lives nowhere central, it lives in every consumer's query. And because the ambiguity is in the specification and not in the code, N independent implementations produce close to N distinct definitions. They do not differ randomly. They differ on the edge cases nobody wrote down:

  • Are refunded orders counted?
  • Are test accounts and employee accounts counted?
  • Which time zone defines "September"?
  • Is a half-finished period counted?
-- Marketing: any order in September (UTC), refunds included
SELECT COUNT(DISTINCT customer_id) FROM orders
WHERE order_ts >= '2025-09-01' AND order_ts < '2025-10-01';

-- Finance: paid, not refunded, test accounts excluded, shop time zone
SELECT COUNT(DISTINCT customer_id) FROM orders
WHERE status = 'paid' AND NOT is_refunded AND NOT is_test_account
  AND (order_ts AT TIME ZONE 'Europe/Berlin') >= '2025-09-01'
  AND (order_ts AT TIME ZONE 'Europe/Berlin') <  '2025-10-01';

Both look right. They are different metrics with the same name.

This is the law this mechanism contributes, and it is not about laziness:

What the layer really does

A is advertised as reuse. Its real function is the automated enforcement of the two rules a hand-written query forgets:

  1. A must not be summed over time (Additivity comes first).
  2. A join path that crosses a many-to-many needs an allocation (Many-to-many).

So a semantic layer is a fan-trap and additivity guard with a reuse benefit attached.

# A metric as code (illustrative format)
metric: active_customers
  type: count_distinct
  of: orders.customer_id
  filter: status = 'paid' AND NOT is_refunded AND NOT is_test_account
  time_dimension: orders.order_ts
  timezone: Europe/Berlin
  owner: finance-data

Metric definitions as code buy one property that matters more than version history: semantics become diffable. A change in "net revenue" appears as a reviewable diff, not as a step in a chart that somebody notices in a meeting.

The honest costs

  • The layer becomes a query compiler. It inherits every compiler concern from query optimization: a semantic layer that cannot push predicates down (apply filters as early as possible) is a correctness win and a performance disaster.
  • It : every definitional dispute now routes through one artifact and one set of reviewers. That is the point of it, and it is also the bottleneck.