Anti-patterns
Common mistakes in Celiq semantic models and how to fix them.
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 bothorder.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:
node: orders
dataset: PUBLIC.ORDERS # warehouse-specific table referencedataset: 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:
node: orders
dataset: orders # logical dataset nameThen 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
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: measures:
- name: total_revenue
type: custom
sql: "SUM(revenue)::numeric" # Postgres-specific castor:
sql: "CAST(created_at AS DATE)" # works in some warehouses, not others::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 noai_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
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: Astaging 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 theANALYTICS 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 adddata_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 aregion 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 therolling_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.
Related pages
- Best Practices โ the positive counterpart
- Forge Validation โ catching modelling errors early
- Physical Layer Overview โ understanding datasets, deployments, and mappings
- Data Keys โ row-level security implementation
- Evals โ regression testing for Orion quality