Skip to content

How it works

This system separates model compilation, which runs in the control plane, from query planning and execution, which run in the data plane.

A person authors a model. A controller compiles it and publishes the result. A stateless server turns each incoming question into exactly one SQL statement. Those three jobs belong to three different programs, and separating them is what makes the system safe to operate.

Model lifecycle and query serving are separate paths joined by one versioned artifact.

1 · Author · outside the clusterossiectlGlueDataHubPolaris / Hiveossiectlderive · validatemodel.yamlSemanticModelgit → kubectl2 · Reconcile · control planeSemantic Operatormanagervalidate · compiledrift · publishConfigMapversioned artifactwatchpublish3 · Serve · data planeSemantic ServerAgents · appsMCP · RESTserver × Nauthn · governplan · executeQuery engineStarRocks · Trinosemantic_viewsOPA · Rangeroptional external PDPwatch artifactone governed SELECTauthorize before plan or cacheview DDLdrift-check
Authoring happens once, by a person. Reconciliation and serving run continuously and independently. They coordinate through the Kubernetes API and never call each other, which is why queries keep being answered while the operator is upgrading.

Each has its own page with the detail.

  • ossiectl runs on a workstation. It generates a model from your catalog, validates it offline, and converts between the portable Apache Ossie document and the Kubernetes resource. It never runs in the cluster.
  • The operator is the control plane. It validates, compiles, drift checks, publishes, and creates governed views. It is the only component that writes to the query engine.
  • The semantic server is the data plane. It authenticates the caller, compiles the request, applies governance, and runs the statement. It is stateless and scales horizontally.

Their credentials differ sharply, which matters when you scope database grants. That is covered in Access and credentials.

When you apply a model, the operator drives it through five steps.

  1. Validate. Structural checks on the Ossie document. Names are unique, relationships point at datasets that exist, metric expressions parse under a small bounded grammar, and the join graph is a tree. This step is pure, so it never fails because of a network problem.
  2. Compile. The spec is frozen into an artifact. Its version is a hash of the spec, so applying the same model twice is a no operation.
  3. Bind and drift check. For every dataset the operator asks the engine what it can actually see. A missing table or column is drift. Drift blocks publication of the new version while the last good version keeps serving. A connection failure is not drift, so it retries instead.
  4. Publish. The compiled artifact is written to a ConfigMap owned by the resource and labelled with the model name and version.
  5. Create views. Each declared view is compiled through the same planner under its own role and applied to the engine. Views removed from the model are dropped. A finalizer cleans them up if the resource is deleted.

The operator checks the engine’s visible schema, not the catalog’s copy. See The operator for why that distinction matters.

The server watches those published ConfigMaps and keeps the compiled models in memory. A request then flows through five stages.

  1. Identity. The caller is resolved to a role, either from a verified bearer token or from a trusted header. An unverified caller is rejected with 401.
  2. External authorization. After the model is selected and request limits are applied, an optional OPA or Ranger policy receives the versioned semantic request and trusted identity context. It runs before cache access. A denial is 403. A timeout, unavailable provider, malformed result, or unsupported obligation is 503.
  3. Resolve and built-in authorization. Metric and dimension names are looked up in the compiled model. The role policy checks requested metrics, dimensions, filters, and every field inside every metric expression. A violation is 403.
  4. Plan. The planner picks the smallest join tree that connects the required datasets, handles ratio metrics that would otherwise be distorted by fan out, applies the role’s row filters, and renders everything through the engine’s dialect.
  5. Execute. The server runs one statement and returns the rows, the SQL, the model version, a request hash, and an external decision fingerprint when a provider applies.

The same request, from the same identity, against the same model version, produces byte identical SQL. Identity includes the principal, groups, roles, and configured claims because row filters may depend on them. That determinism makes plans cacheable and lets you trace a query in the engine’s log back to the request that caused it.

The operator and the server do not call each other. They coordinate through the Kubernetes API, with the compiled ConfigMap as the only handoff.

That decoupling buys three things. The server holds no state of its own, so replicas are interchangeable and scale horizontally. Serving continues normally while the operator is down, restarting, or being upgraded. And the operator can be a single active replica with a standby, which is the right shape for a controller.

Governance runs inside the compiler, before any SQL string exists.

  • The built-in role policy is compiled into the statement, so an unauthorized request fails to compile rather than being filtered after the fact.
  • An external provider, when a model names one, is a separate gate in the serving layer that runs before execution.
  • A denial from either happens before any query reaches the engine.

The semantic server covers how the two gates compose and what each one checks. Authorization shows the complete OPA and Ranger decision path.

The two places that accept SQL fragments from the model author, metric expressions and row-filter predicates, are parsed under small grammars rather than passed through. Anything outside those grammars fails at validation time, when a human is watching, rather than at query time. The accepted syntax is listed in Authoring a model.

Everything engine-specific sits behind two small interfaces, and everything else is engine-agnostic.

A query engine is an adapter pair: a dialect that renders SQL and a client that runs it and introspects schemas. StarRocks and Trino ship today. See Adding a query engine.

A catalog source supplies physical structure for model generation, and an enricher supplies business meaning. Neither is used at runtime, which is why the operator needs no cloud credentials. See Adding a catalog source.

The compiler deliberately leaves out several things:

  • multi-hop and cumulative metrics from full MetricFlow semantics,
  • federation across engines in a single query,
  • a bundled BI tool.

They stay out of scope so the compiler remains small enough to reason about.