Celiq
v0.12Open app โ†—
Docs/Analytics/Anti-patterns

Anti-patterns

Common mistakes in Celiq semantic models and how to fix them.

LuminaryWeaverUpdated June 2026 ยท 8 min read
โœฆ
In this section
Part of Celiq's semantic layer platform. Connect your warehouse, model your data once, query it everywhere.
๐Ÿ“
Note

This page documents patterns that consistently cause problems. Each entry describes what goes wrong, why it happens, and how to fix it. See Best Practices for the positive counterpart.

Modelling anti-patterns

Mixing granularities in one node

What it looks like: One node contains both order-level dimensions and order-item-level dimensions, then measures that SUM revenue. What goes wrong: When a user selects both order.id and order_item.product_id in a Reveal, the join inflates row counts. SUM(revenue) counts each order's revenue once per line item, not once per order. You get the classic fan-out bug. Fix: One node per grain. Create separate nodes for orders and order_items. Celiq's Forge validation warns on detected fan-out โ€” take those warnings seriously.

Using physical table names in dataset: fields

What it looks like:
cml
node: orders
  dataset: PUBLIC.ORDERS          # warehouse-specific table reference
What goes wrong: The dataset: field should reference a logical dataset name, not a physical table path. Using a physical name bypasses the Physical Layer entirely โ€” the node will query that exact table regardless of the active deployment, breaking multi-environment workflows. Fix:
cml
node: orders
  dataset: orders                 # logical dataset name

Then ensure the orders dataset has mappings for all deployments in the Physical Layer.


One domain for everything

What it looks like: A single domain with 30+ joined nodes, covering every business concept in the warehouse. What goes wrong:
  • Discover becomes unusable โ€” users face hundreds of fields with no clear starting point
  • Orion quality degrades โ€” it struggles to disambiguate metrics when everything is in scope
  • Performance suffers โ€” the planner has to consider all possible joins for every query
  • Model changes have blast radius across the entire analytics surface
Fix: Create focused domains per business area (e.g. ecommerce, marketing, support). Each domain should answer one coherent question area. Cross-domain analysis is possible via Reveals that combine multiple domains.

Hardcoding warehouse dialects in CML

What it looks like:
cml
  measures:
    - name: total_revenue
      type: custom
      sql: "SUM(revenue)::numeric"   # Postgres-specific cast

or:

cml
      sql: "CAST(created_at AS DATE)"    # works in some warehouses, not others
What goes wrong: The model breaks when you add a second warehouse type (e.g. BigQuery, Snowflake) to a deployment. ::numeric is a Postgres extension; CAST(x AS DATE) behaves differently across warehouses. Fix: Use plain SQL without dialect-specific syntax. Celiq applies dialect translation via applyDialect() at query time โ€” type casts for date fields, numeric precision, and quoting conventions are handled automatically. If you need a custom cast, use CML's type: field to declare the output type and let Celiq handle the cast.

Missing AI descriptions

What it looks like: Nodes, measures, and domains with no ai_description or description field. What goes wrong: Orion has no context about what the data means. It falls back to field names alone, which leads to mismatched entities, wrong metric selection, and hallucinated SQL. Fix: Add ai_description to every node and measure. The minimum useful description:
  • What one row represents
  • How the key measure is calculated
  • Any non-obvious date conventions or exclusions

Invest 10 minutes in good descriptions and Orion quality improves dramatically.


Overly broad suggested_attributes in Reveals

What it looks like: A Reveal with suggested_attributes: [] (empty โ€” use everything) or a very long list including user IDs, email addresses, and other high-cardinality fields. What goes wrong:
  • Discover tries to enumerate all values for filter dropdowns on high-cardinality columns โ€” expensive warehouse queries
  • Users are overwhelmed by irrelevant fields
  • Orion has too much surface area and picks wrong dimensions
Fix: Explicitly list the 5โ€“10 dimensions that are actually useful for this Reveal in suggested_attributes. Hide high-cardinality identifier columns unless they're genuinely needed for drill-through.

Physical Layer anti-patterns

Not validating mappings after schema changes

What it looks like: A column is renamed in the warehouse (user_email โ†’ email). Nobody updates the dataset. Compatibility stays at 92% for weeks. What goes wrong: Queries that select the renamed column fail with SQL errors. The bug is silent until someone hits that specific metric. Fix: After any schema change in the warehouse, run Validate All Mappings on affected datasets. Set up a periodic mapping health check โ€” the Deployments list shows a schema-drift indicator that makes this easy to scan.

Creating a new deployment without complete mappings

What it looks like: A staging deployment is created and immediately set as default, but only 3 of 12 datasets have mappings. What goes wrong: Discover queries against the 9 unmapped datasets fail with DATASET_NOT_MAPPED. The staging deployment looks broken even though the semantic model is fine. Fix: Never set a deployment as default until all datasets that your active nodes reference have valid mappings with 100% compatibility. Use the deployment detail page to confirm full coverage before promoting.

One connection shared between environments without overrides

What it looks like: Production and development deployments both use the same Snowflake connection, both resolving to the ANALYTICS database. What goes wrong: Dev queries hit production data. A Weaver testing a new node calculates metrics on real production records, potentially exposing data to users who shouldn't see it. Fix: Use separate connections for prod and dev, or use database_override on development dataset mappings to redirect to a non-production database (ANALYTICS_DEV).

Security anti-patterns

Testing security rules only as yourself

What it looks like: A Luminary configures Data Keys, checks the CML looks right, and marks it done. What goes wrong: Security rules can look correct in CML but behave unexpectedly at query time. Without executing a query as the restricted user, you don't know if the filter is actually applied. Fix: Always verify security rules by executing queries as a restricted user (Tracer role) or via sudo mode with a test account. Check the resulting SQL in Discover's SQL pane to confirm the WHERE clause was injected.

Applying Data Keys at the domain level instead of the node level

What it looks like: Trying to add data_keys: at the domain join level to restrict one path. What goes wrong: Data Keys in Celiq are node-level, not domain-level. A node's Data Keys apply on every query against that node, regardless of which domain is used. Attempting to scope security to a join path leads to confusion about when the filter applies. Fix: Put data_keys: on the node itself. Every query against that node will be filtered โ€” which is the correct security posture.

Using workspace separation where user-attribute RLS would do

What it looks like: Creating a new workspace every time a new region comes online to isolate regional data. What goes wrong: Each workspace is a completely separate Celiq instance. Users can't see across workspaces, model changes don't propagate, and maintenance overhead multiplies. Fix: Use Data Keys with a region user attribute. One workspace, one model, filtered per user. Reserve separate workspaces for truly separate business units with non-overlapping data and users.

Performance anti-patterns

Running table calculations on millions of rows

What it looks like: Running a "Running Total" or "Rolling Average" table calculation on a Reveal that returns 100K+ rows. What goes wrong: Table calculations are applied client-side after data is fetched. For large result sets, the browser struggles with the computation and the display becomes sluggish. Fix: For large datasets, switch to the rolling_sum or rolling_average intent โ€” these generate SQL OVER() window functions that compute in the warehouse. Alternatively, reduce the result set with narrower date filters.

Overly permissive LIMIT settings

What it looks like: Setting the row limit to 10,000 on every Reveal "just in case." What goes wrong: Large result sets cause slow warehouse queries, slow rendering in Discover, and large payloads for scheduled reports. Most analytical questions don't need more than 500โ€“1000 rows. Fix: Use appropriate row limits per Reveal. For time-series charts, 90โ€“365 rows (one per day/week) is usually sufficient. For tables with many dimensions, 100โ€“500 rows is a reasonable start. Users can always increase the limit when they need more.