Skip to content

Observability: metrics and the Calcite parity guard

Mondrian emits a small set of OpenTelemetry metrics so you can watch query throughput, SQL load, cache locality, and — importantly for the Calcite backend — when the engine falls back to legacy SQL or, with the parity guard on, when the two backends disagree.

Wiring up an exporter

The instruments resolve against the global OpenTelemetry SDK. If your deployment already configures the OpenTelemetry Java agent or autoconfigure (e.g. OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_METRICS_EXPORTER), Mondrian’s metrics flow through it with no extra setup. If no SDK is registered, the instruments are no-ops — recording is always safe and never throws.

Metrics

MetricTypeMeaningKey attributes
mondrian.queries.executedcounterMDX queries completedmondrian.query.outcome = success | failure
mondrian.query.durationhistogram (ms)per-MDX-query latencysame outcome
mondrian.sql.statementscounterJDBC statements issuedmondrian.sql.kind (segment-load / member-read / drillthrough / other)
mondrian.sql.durationhistogram (ms)per-JDBC-statement latencysame kind
mondrian.cache.segment.hitscountercell requests served from the segment cache
mondrian.cache.segment.missescountercell requests that fell through to a SQL load
mondrian.calcite.fallbackcounterCalcite translation threw → fell back to legacy SQL…fallback.site, …fallback.exception
mondrian.calcite.divergencecounterparity guard found Calcite and legacy disagreed…divergence.site, …divergence.detail

A high cache.segment.hits / misses ratio means good cache locality. A non-zero calcite.fallback is expected and benign — it counts cases where Calcite correctly declined a shape and the legacy generator took over. The counter to alert on is calcite.divergence.

The Calcite parity guard

The exception-based fallback only catches “Calcite threw.” It cannot catch the dangerous class: valid-but-wrong SQL — a Calcite translation that runs without error but returns a different result than the legacy path. That divergence is invisible to the fallback safety net and surfaces only as a user-reported “no data” or a wrong total.

The parity guard closes that gap. When enabled, every eligible Calcite segment load also runs the legacy SQL for the same load and compares the two JDBC row-sets. A mismatch increments mondrian.calcite.divergence (with a low-cardinality detail category — row-count / cell-value, never raw values) and logs a WARN.

Enabling it

Terminal window
# record divergences to telemetry + logs; still returns the Calcite result
-Dmondrian.calcite.parityCheck=true
# additionally hard-fail (throw) on any divergence — for CI / pre-prod gates
-Dmondrian.calcite.parityCheck.strict=true

Both default to off. When off, the only cost is a single boolean read — there is no hot-path overhead.

What it never does (safety)

The guard is deliberately skipped for two classes of load, so a recorded divergence always means a real Calcite correctness bug — never a false alarm and never a security risk:

  • Predicate-secured loads. A measure group protected by a <PredicateGrant> is never compared against legacy SQL — the legacy generator drops the row-security filter, so running it would leak rows. Row security wins over diagnostics, always.
  • Calcite-only-correct aggregations. Measure-level distinct grain, median / percentile, and bridge / symmetric fan-out are correct by design in Calcite and different from what the legacy generator would compute (e.g. a distinct-grain measure returns the de-duped 450 where a naive legacy SUM would return the fanned-out 950). Comparing them would record a false divergence, so they are skipped.

In other words: mondrian.calcite.divergence > 0 is a high-signal alert that a query shape that should be identical across backends is not. Capture the WARN log’s load context and file it as a Calcite correctness bug.

  • mondrian.calcite.divergence rate > 0 (when the guard is enabled in staging/CI) → block the release; a silent wrong-result shape exists.
  • mondrian.calcite.fallback rate trending up → a dialect or schema shape Calcite stopped handling; worth investigating for lost pushdown, though results stay correct.
  • cache.segment.misses spiking → cache churn or a cold cube; consider cache warming.