Semantic Model Authoring
MicrosoftOfficialPublished by Microsoft
- Skill
- Tool integration
- Data and AI
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The semantic-model-authoring skill provides a structured framework for managing the full lifecycle of Power BI semantic models. It supports creating, inspecting, modifying, and deploying models, including handling DAX, relationships, field parameters, and storage modes like Import, DirectQuery, and Direct Lake. The skill prioritizes the use of the powerbi-modeling-mcp for live model interactions, with fallback procedures for TMDL-based editing in Fabric workspaces or PBIP projects. It enforces strict adherence to best practices for modeling, DAX performance, and AI readiness, ensuring models are robust and optimized for both human and AI-driven consumption.
What it does
- Creates and configures new semantic models from scratch.
- Inspects and discovers metadata for tables, columns, measures, and relationships.
- Modifies existing models by adding or editing objects and refactoring DAX code.
- Optimizes DAX performance using diagnostic traces and established patterns.
- Prepares semantic models for AI integration with Microsoft Fabric Copilot and Data Agents.
- Manages deployment, refresh, and connection binding for models in Fabric workspaces.
When to use it
- When you need to build or update a semantic model's schema or business logic.
- When you need to audit a model against performance or modeling best practices.
- When preparing a model for conversational BI or Copilot experiences.
- When you need to programmatically bind a model to a specific Fabric data connection.
Advantages
- Provides a unified, tool-agnostic workflow for diverse model environments.
- Enforces consistent modeling and DAX coding standards.
- Prioritizes high-efficiency MCP-based editing to prevent desync issues.
- Includes specialized workflows for modern features like Direct Lake and Field Parameters.
Usage examples
- “Create a new semantic model for my sales data in the 'SalesWorkspace' workspace.”
- The agent will guide you through requirements gathering, storage mode selection, star schema design, and deployment to the specified workspace.
- “Optimize the 'Total Revenue' measure in my semantic model.”
- The agent will use the DAX performance decision guide to diagnose the measure and suggest optimizations based on established performance patterns.
- “Prepare my semantic model for use with Fabric Copilot.”
- The agent will evaluate your model against the AI readiness checklist and provide actionable steps to improve metadata and business context.
- “Bind my semantic model to the 'ProductionGateway' connection.”
- The agent will list the model's data sources and execute the necessary API calls to bind them to the specified gateway connection.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/microsoft/semantic-model-authoring/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip semantic-model-authoring.zip -d .agents/skills
Documentation by Microsoft
Telemetry — MANDATORY. Every
api.fabric.microsoft.comcall must carryx-ms-fabric-skill: semantic-model-authoring(az rest:--headers "x-ms-fabric-skill=semantic-model-authoring"), including every LRO poll,fabric_lroand retry. Snippets omit it — add it anyway.
CRITICAL NOTES
To find the workspace details (including its ID) from workspace name: list all workspaces and, then, use JMESPath filtering
To find the item details (including its ID) from workspace ID, item type, and item name: list all items of that type in that workspace and, then, use JMESPath filtering
Always consider the Tool selection priority when choosing which tool to use for each operation. Do not default to TMDL edits or
az restif MCP is available and connected to the target model.
Power BI Semantic Model Authoring — CLI Skill
Workflow Selector
Use this decision tree to route to the correct workflow based on user intent:
| User wants to... | Workflow |
|---|---|
| Create a semantic model from scratch | Create new semantic model |
| Inspect or discover model metadata (tables, columns, measures, relationships) | Discover Semantic Model Metadata |
| Add/edit semantic model objects (e.g. measures, tables, columns, relationships) | Modify an Existing Model |
| Write or refactor DAX code | Modify an Existing Model |
| Create/edit a field parameter | Author a Field Parameter |
| Improve DAX query or measure performance | Optimize DAX Performance |
| Analyze semantic model against best practices | Analyze Best Practices |
| Prepare a semantic model for AI consumption (Copilot / Data Agents) | Semantic Model AI Readiness |
| Deploy a model to a Fabric workspace | Deploy to Fabric |
| Refresh a semantic model | Refresh Semantic Model |
| Configure data sources, parameters, or permissions | Manage Semantic Model in Fabric |
| Bind a semantic model to a Fabric connection (or unbind) | Bind Semantic Model to a Connection |
| Export / Get semantic model definition as PBIP | Export to PBIP |
Table of Contents
Load these references on demand when a workflow step requires them. Do not load all at once.
| Topic | Reference | When to load |
|---|---|---|
| Modeling Best Practices | modeling-guidelines.md | Before creating or editing any model |
| Naming Conventions | naming-conventions.md | When naming or renaming tables, columns, measures |
| Direct Lake Modeling | direct-lake-guidelines.md | When model connects to OneLake |
| TMDL Editing | tmdl-guidelines.md | Before generating or editing any TMDL file |
| PBIP Projects | pbip.md | When working with PBIP folders |
| DAX Language | dax-guidelines.md | When writing or reviewing any DAX code |
| Field Parameters | field-parameters.md | When creating/editing a field-parameter table (metric selector, dynamic view) |
| Metadata Discovery (DAX INFO functions) | metadata-discovery.md | When discovering model metadata via DAX INFO functions (see Workflow: Discover Semantic Model Metadata) |
| DAX Performance Decision Guide | dax-perf-decision-guide.md | Start here when optimizing DAX |
| DAX Performance Pattern Catalog | dax-perf-patterns.md | Load on demand after the decision guide identifies candidate patterns |
| Semantic Model AI Readiness | semantic-model-ai-readiness.md | When preparing a model for Copilot or Data Agents |
| Semantic Model REST API | semantic-model-rest-api.md | When using az rest for TMDL CRUD, refresh, parameters, permissions, or property retrieval |
| Connection Binding | connection-binding.md | When binding/unbinding a semantic model to a Fabric data connection (gateway, cloud, VNet, automatic, none) |
| Finding Workspaces/Items | COMMON-CLI.md | When resolving workspace/item IDs |
| Fabric Control-Plane API | COMMON-CLI.md | When using az rest patterns, LRO, pagination |
| Authentication | COMMON-CLI.md | When authenticating with az login |
| Authentication & Token Acquisition | COMMON-CORE.md § Authentication & Token Acquisition | Wrong audience = 401; read before any auth issue |
| Core Control-Plane REST APIs | COMMON-CORE.md § Core Control-Plane REST APIs | Includes pagination, LRO polling, and rate-limiting patterns |
| Definition Envelope | ITEM-DEFINITIONS-CORE.md | When building TMDL definition payloads |
| Examples | Examples | Reference end-to-end walkthroughs. |
Tool Selection Priority
Priority order (highest first):
Tier 1 —
powerbi-modeling-mcpMCP is registered -> Use MCP for authoring (new or edit) operations against the model from any source: Power BI Desktop, Fabric workspace, or local PBIP folder. MCP is the most reliable and full-featured way to edit semantic models, with immediate effect on the live model and no risk of TMDL desync.Important: In case of dynamic search tools is available (e.g.
tool_search_tool_regex) search for an available MCP server matching the patternpowerbi-modeling-mcp.This includes BOTH writes AND reads/inspection.
To inspect or verify changes -> use the corresponding MCP operations (List / Get).
Anti-pattern: opening,
view-ing,glob-ing or otherwise reading TMDL files (*.tmdl) while MCP is connected. The MCP-loaded model is the source of truth - the on-disk TMDL is stale. The only exceptions is when the user explicitly asks to work with the TMDL files.
Tier 2 — MCP not registered + PBIP folder or Fabric workspace -> Edit TMDL files directly. Load tmdl-guidelines.md and pbip.md. When the source is a Fabric workspace, use
az restto round-trip the TMDL (load semantic-model-rest-api.md):getDefinition-> edit TMDL locally ->updateDefinition.
Fallback — none of the above available (e.g., Power BI Desktop with no PBIP and no MCP) -> STOP. The agent cannot author the model in this configuration. Instruct the user to either (a) install and register the powerbi-modeling-mcp MCP server, or (b) save the PBIX as a PBIP project, then restart the workflow.
All workflows below are tool-agnostic. Workflow steps describe the intent (connect, create, edit, save, deploy, refresh). The tool used to perform each step is determined here. Always select the highest-priority tool available for the current environment; do not mix tools when a higher-priority option works. Some workflows OVERRIDE this default priority, always check the workflow's own tool-selection rules before defaulting to Tier 1.
Connecting to a Semantic Model
A semantic model can live in three locations. Resolve the connection per Tool Selection Priority:
Power BI Desktop: Locate the running Power BI Desktop instance and connect to its local model.
Fabric workspace: First, find the workspace and semantic model using the Finding Workspaces and Items pattern: list workspaces to resolve the workspace ID by name, then list items of type
SemanticModelin that workspace to resolve the model ID by name. Then connect to the model (live) or export its TMDL definition for local editing.PBIP project: Connect to the
[Name].SemanticModel/definitionfolder. Load pbip.md to understand the PBIP folder structure - only load the[Name].SemanticModel/definitionfolder that includes the TMDL code.
Saving Changes to a Semantic Model
How changes are persisted depends on where the model lives and which tool tier (per Tool Selection Priority) is in use:
Live connection (Tier 1 - MCP against Desktop or Fabric workspace):
Changes are applied immediately as each operation executes against the live model. No explicit save step is needed.
PBIP project (live via MCP): Serialize the model back to the
[Name].SemanticModel/definitionfolder at the end of the session. If the PBIP folder does not exist yet, follow Export to PBIP to create the full structure first.
Local TMDL editing (Tier 2 - direct file edits or az rest round-trip):
PBIP project: Changes are already written to the TMDL files during editing. No additional save step is needed.
Fabric workspace: Changes were made to local TMDL files exported from the service. Re-deploy the model (load semantic-model-rest-api.md for the
updateDefinitionflow) to push changes back to the workspace.
Workflow: Create new Semantic Model
When this applies: User asks to create a new semantic model from scratch.
Steps:
Gather requirements - interview the user until both reach a shared understanding of: purpose of the model, data source connection details and schemas, and key business entities/facts. If data source information is not available, STOP and use
ask_user. Do not guess or fabricate.Determine storage mode - data source is Fabric OneLake -> Direct Lake; otherwise default to Import. Only use DirectQuery when the user explicitly asks for it.
Design star schema - identify fact and dimension tables and relationship keys.
If fact table includes date field(s), create a separate date dimension table and link it to the fact with a relationship. If not explicitly requested, use PowerQuery/M partition instead of DAX calculated table.
Load applicable guidelines - MANDATORY: load modeling-guidelines.md before building any object (including the Tier 1 MCP path below); also load direct-lake-guidelines.md if Direct Lake. Don't build from memory.
Build - follow Tool Selection Priority: prefer the modeling MCP to build the whole model when one is available; otherwise use the TMDL code-edit path. Create an empty database (compatibility level 1702+), then for each table follow the execution order from Modify an Existing Model (partitions -> columns -> relationships -> measures). Storage-mode specifics:
Import / DirectQuery - create M parameters for the data source (
Server,Database, ...) and reference them in partition M code; ensure properdataTypeandsourceColumnmapping on columns.Direct Lake - create a shared named expression for the Direct Lake connection using the
AzureStorage.DataLakeconnector; useEntityPartitionSourcewithdirectLakemode mapped to the lakehouse table columns.
Build the whole model in one pass, then deploy once. Add every table, column, relationship and measure before deploying - don't deploy a partial model and edit-redeploy. On the MCP path, keep the model in the session end to end.
Deploy or save - Fabric workspace available -> Deploy to Fabric; otherwise -> Export to PBIP. See Saving Changes to a Semantic Model. Deploy through the same path you built with.
Validate - run Validation Checklist.
Workflow: Discover Semantic Model Metadata
When this applies: User asks to inspect, list, or discover the model's structure - tables, columns, measures, relationships, hierarchies, partitions, roles, or storage internals. Also used internally by other workflows (Modify, Analyze Best Practices, AI Readiness) to inventory the model before editing.
Pick a discovery method (highest priority first):
powerbi-modeling-mcpTOM inspection (List / Get) - the default whenpowerbi-modeling-mcpis registered and connected to the target model with Write access. It returns the structured object model directly and stays in sync with pending edits, so it is preferred while authoring.DAX
INFO.VIEWfunctions - query the model'sINFO.VIEW.*metadata rowsets. MANDATORY: before writing or running ANYINFO-function DAX, you MUST load metadata-discovery.md first. Do NOT composeINFOqueries from memory; load the reference and use its patterns. Prioritize this method when any of the following is true:You lack Write permission on the model.
powerbi-modeling-mcpList / Get operations require Write; with Build access, useINFO.VIEWfunctions executed via thedax_query_operationstool.powerbi-modeling-mcpis not registered or not available. Use Tier 2: export the model definition (getDefinition) and inspect the.tmdlfiles for schema metadata.
Start narrow: run the scope-estimation and INFO.VIEW.* queries first, then project/filter to only the objects relevant to the task (see metadata-discovery.md).
Workflow: Modify an Existing Model
When this applies: User asks to add/edit/remove measures, tables, columns, relationships, write DAX code, refactor with UDFs, or edit TMDL directly.
Steps:
Connect & discover - per Connecting to a Semantic Model and Discover Semantic Model Metadata. List tables, relationships, existing measures, and identify storage mode (it dictates which guidelines apply).
Load applicable guidelines - modeling-guidelines.md always; direct-lake-guidelines.md if Direct Lake; tmdl-guidelines.md when editing TMDL directly; dax-guidelines.md for any DAX changes (includes UDF refactoring).
Plan changes - identify exactly what to add, modify, or remove. Check for naming conflicts and duplicates.
Execute in correct order:
Adding tables - partitions -> columns -> relationships -> measures.
Adding relationships - ensure key columns exist on both sides with matching data types;
Adding measures - verify referenced columns/tables exist;
Save & validate - per Saving Changes to a Semantic Model and Validation Checklist.
Workflow: Author a Field Parameter
When this applies: User asks to create, edit, reorder, rename, or delete a field parameter. This is a self-contained model-side calculated table; the matching report slicer and visual are separate report work and out of scope here.
Load field-parameters.md before starting and follow the workflow it defines.
Steps:
Connect & discover - per Connecting to a Semantic Model. Detect any existing field parameter (a column carrying
ParameterMetadata"kind": 2) before creating a duplicate.Route to the matching sub-workflow in field-parameters.md:
Create a new parameter -> Workflow: Create a field parameter (
table_operationsCreateFieldParameterat Tier 1).Edit an existing parameter (add / remove / reorder / relabel fields) -> Workflow: Edit a field parameter (partition rewrite via
partition_operations; there is noUpdateFieldParameteroperation).Rename or delete -> Workflow: Rename or delete a field parameter.
Save & validate - per Saving Changes to a Semantic Model and Validation Checklist. Confirm the three columns and
ParameterMetadata"kind": 2are intact so Desktop recognizes the table as a field parameter.
Workflow: Optimize DAX Performance
When this applies: User asks to improve DAX query performance, diagnose slow measures, or optimize calculations.
Hard requirement: Requires a trace-capable client (MCP preferred))
Load dax-perf-decision-guide.md first and follow the framework defined there. Load dax-perf-patterns.md only when applying candidate optimization patterns. The framework includes:
Tier model for categorizing optimization effort
Trace diagnostics to identify bottlenecks
Pattern catalog with candidate optimization techniques to test and validate
Workflow: Analyze Best Practices
When this applies: User asks to review, audit, or analyze a semantic model against best practices.
Steps:
Connect & inventory - per Connecting to a Semantic Model and Discover Semantic Model Metadata. Capture all tables, columns, relationships, measures, and storage mode.
Load applicable guidelines - modeling-guidelines.md always; direct-lake-guidelines.md if Direct Lake; naming-conventions.md when assessing naming; dax-guidelines.md when assessing DAX.
Evaluate - compare the model against the loaded guidelines (star schema, naming, relationship cardinality and cross-filter, explicit measures with
formatString, column data types andsourceColumn, hidden FK columns, calculated-column-vs-measure choices, Direct Lake constraints, etc.).Present findings grouped by severity (critical, recommended, optional). For each item state the rule violated and the proposed fix. Wait for user approval.
Apply approved fixes via Modify an Existing Model.
Save & validate - per Saving Changes to a Semantic Model and Validation Checklist.
Workflow: Semantic Model AI Readiness
When this applies: User asks to make a semantic model ready for Microsoft Fabric Copilot, a Power BI Data Agent, or any conversational BI experience. Triggers include "Copilot readiness", "AI readiness", "Prep for AI", "prepare model for Copilot".
Load semantic-model-ai-readiness.md before starting.
Steps:
Confirm scope & gather context - via
ask_user, confirm consumption mode (reports only / conversational BI / both) and model stability per the When to Apply section. Collect business context (process, key metrics, common natural-language questions, vocabulary). Do not invent.Connect & inventory - per Connecting to a Semantic Model. Capture model contents and the source location (PBIP / Fabric workspace / Desktop-only).
Evaluate & route - walk the Readiness Checklist in order; for each gap, classify the fix per Supported Editing Routes (agent-editable TOM metadata vs AI-specific artifacts the user configures in the Power BI "Prep data for AI" UI).
Present findings grouped by severity, each tagged with routing (agent-applicable vs user-action-required). Wait for approval.
Apply approved changes - apply TOM metadata fixes via Modify an Existing Model; for AI instructions, AI Data Schema, and Verified Answers, instruct the user to configure them in the Power BI "Prep data for AI" UI and, only if the user agrees, offer suggestions per the readiness reference; Desktop-only PBIX -> instruct user.
Save, validate, recommend live testing - per Saving Changes to a Semantic Model and Validation Checklist; advise the user to test representative natural-language prompts in Copilot or the Data Agent and iterate.
Workflow: Export to PBIP
When this applies: User asks to export or save a semantic model to a PBIP project folder, or there is no Fabric workspace available to deploy to (e.g., after building a model in-memory).
Key fact: Exporting a model only produces the TMDL definition files. It does NOT create the surrounding PBIP folder structure (Report folder,
definition.pbism,definition.pbir,.pbipentry point). The agent must scaffold these before exporting, otherwise the result cannot be opened in Power BI Desktop.
Load pbip.md before starting and follow the PBIP folder structure defined there.
Steps:
Determine target - via
ask_user, get the target folder path and the semantic model name. If only a folder is provided, use the model's database name as the semantic model folder name.Scaffold the PBIP structure - per pbip.md, ensure
<Name>.SemanticModel/(withdefinition/anddefinition.pbism),<Name>.Report/(withdefinition/anddefinition.pbirusing abyPathreference), and<Name>.pbipexist. Create any missing piece.Export TMDL into
<Name>.SemanticModel/definition/, per Tool Selection Priority:Tier 1 (MCP) - use the MCP export/save operation against the live model.
Tier 2 (Fabric workspace, no MCP) - call
getDefinition(load semantic-model-rest-api.md) and write the returned parts.Local TMDL files already on disk - copy or move them into the
definition/folder.
Validate - confirm the
definition/folder contains at minimummodel.tmdland table.tmdlfiles; confirmdefinition.pbism,<Name>.Report/definition.pbir(with correctbyPathto../<Name>.SemanticModel), and<Name>.pbipexist and reference each other correctly.
Workflow: Deploy to Fabric
When this applies: User asks to deploy or publish a semantic model to a Fabric workspace.
Hard rule — this workflow OVERRIDES the default Tool Selection Priority. Do not default to MCP just because it is available. The deployment path is determined by the source of the model, not by which tools are connected. If the source is PBIP/TMDL files on disk, you MUST use the Fabric REST API even when an MCP session is active.
Decision tree (pick exactly one — top-down, first match wins):
Are there PBIP / TMDL files on disk that need to be deployed? -> YES — use Fabric REST API. Call
az restwithcreateItemWithDefinition(new model) orupdateDefinition(existing model). Load semantic-model-rest-api.md.Rationale: deploying TMDL files directly via the Fabric API is more reliable, faster, and avoids unnecessarily loading the model into MCP only to push it back out.
Do NOT open the PBIP in MCP first and then deploy via MCP. That is an explicit anti-pattern for this workflow.
Is the model already loaded in a live MCP session (e.g., just built in-memory, or currently being edited via MCP) with no PBIP/TMDL files involved? -> Use MCP tool to deploy with the target workspace and semantic model name.
Is the model live in Power BI Desktop with no PBIP saved? -> Use MCP Deploy if MCP is connected to Desktop. If MCP is not available, instruct the user to save as PBIP first, then restart this workflow at step 1.
Verify deployment succeeded by listing workspace items of type SemanticModel.
Deploy exactly once; the deploy is NOT idempotent. A retried deploy creates a second model with the same name, after which the deploy fails with
There are multiple datasets named '<name>'. If you hit that error, do NOT retry blindly: list the workspace's semantic models, delete every duplicate with that name, then deploy once from the clean state. If deploying a model that may already exist, delete the existing same-named model(s) before the single deploy.
Workflow: Refresh Semantic Model
When this applies: User asks to refresh data in a semantic model.
Refresh is only possible when working against a live model in Desktop or Fabric Service. If working with local TMDL files, deploy the model first.
Trigger a refresh per Tool Selection Priority:
Power BI Desktop: Tier 1 (MCP) only — use the MCP Refresh operation.
Fabric Service: Tier 1 (MCP Refresh operation) or fallback to the Power BI Enhanced Refresh API (load semantic-model-rest-api.md).
If the refresh fails with a credential error, stop immediately and instruct the user to configure the data source connections manually in Power BI Service. Do not attempt to retry or work around credential errors programmatically.
Workflow: Manage Semantic Model in Fabric
When this applies: User asks to configure data sources, update parameters, or manage permissions for a semantic model in Fabric Service.
Hard rule — this workflow OVERRIDES the default Tool Selection Priority. Do not default to MCP (Tier 1) prefer using
az restand REST APIs.
Data Sources & Parameters
Get/update data sources and parameters via Power BI REST API. Load semantic-model-rest-api.md.
Permissions
List/grant/update dataset user permissions via Power BI REST API. Load semantic-model-rest-api.md.
Connection Binding
Load connection-binding.md and follow it. The reference covers prerequisites, the bindConnection endpoint, the discover -> match -> bind -> validate steps, all connectivityType values, the unbind pattern, and troubleshooting.
Key rules (full details in the reference):
Use the Fabric Bind Semantic Model Connection REST API (supersedes the legacy Power BI
BindToGateway).One bind request per data source reference - the API does not support bulk binding.
Discover the model's data source references via
List Item Connections, then matchconnectionDetailsagainstList Connectionsto find the targetid. Create a connection first if no match exists.Validate by re-listing item connections and triggering a refresh.
Validation Checklist
Run after any model creation or modification:
Always (works with PBIP, Desktop, and Fabric Service):
Check the PBIP structure - if the model is sourced from a PBIP folder, ensure the folder structure and files are correct (see pbip.md).
Verify against modeling guidelines - re-check every change against modeling-guidelines.md (and direct-lake-guidelines.md for Direct Lake models).
Only when connected to an Analysis Services database (Power BI Desktop or Fabric Service):
Test new measures - for each new measure, run a simple DAX query to validate it returns expected results (e.g.,
EVALUATE { [Measure Name] }). Skip this step when working with local TMDL/PBIP files only.Test table refresh - when new tables were created, trigger a refresh to verify that partitions, data source expressions, and column mappings are correct. A failed refresh typically indicates mismatched
sourceColumnnames, invalid M expressions, or incorrect Direct Lake entity references. Skip this step when working with local TMDL/PBIP files only.
If any check fails, fix the issue and re-run validation.
Must/Prefer/Avoid
MUST
Understand the data source schema before starting - analyze source tables, columns, and data types before designing or modifying the model.
Follow modeling guidelines - load modeling-guidelines.md before creating or editing any model; apply star schema design, naming conventions, and column/measure rules
Follow Tool Selection Priority - always pick the highest-priority tool tier available for the current environment; do not mix tiers when a higher-priority option works
PREFER
Star schema over snowflake or flat tables - denormalized dimensions with single-column relationship keys
Consistency with existing model patterns - when editing an existing model, match its naming conventions and structure rather than imposing new ones
TMDL format over TMSL - text-based, diff-friendly, preferred for Fabric
Validate after changes - use Workflow: Discover Semantic Model Metadata to confirm objects landed.
AVOID
Hardcoded workspace/item IDs - resolve dynamically via API
Reading TMDL files when MCP is connected -
view/globon*.tmdlwhile a Tier 1 MCP session is live is an anti-pattern (see Tool Selection Priority).Hand-authoring TMDL files when MCP is registered - using
create/edit/file-write tools to scaffoldmodel.tmdl,database.tmdl,relationships.tmdl, ortables/*.tmdlis a Tier 1 anti-pattern, including for brand-new models. Build and export via MCP tools.
DENY
Manage RLS/OLS role membership - do not propose REST calls,
az restURLs, MCP operations, or TMDL changes to add/remove users or groups from a security role. Refuse the request as out-of-scope here and redirect the user to the Power BI portal.
Examples
Scope note — examples use
az restfor discovery to resolve ID's and discover Fabric metadata (see COMMON-CLI.md § Finding Workspaces and Items). Authoring of the semantic model definition is routed through Tool Selection Priority: Tier 1 MCPpowerbi-modeling-mcpwhen available, Tier 2 TMDL editing viagetDefinition/updateDefinitionotherwise.
Example 1: Modify an Existing Semantic Model
Prompt: "Create base measures for all aggregable columns in the semantic model Sales in workspace Marketing."
Agent response - follows Workflow: Modify an Existing Model.
Discover IDs via
az rest.Connect to the model per Connecting to a Semantic Model and Tool Selection Priority. With
powerbi-modeling-mcpregistered, connect MCP directly to the Fabric workspace model (Tier 1). Otherwise fall back to Tier 2 (getDefinition-> edit TMDL locally).Inspect & plan - list tables and columns via the active tool tier; identify aggregable columns (numeric, not foreign keys, not hidden surrogate IDs) and decide on
SUM/AVERAGE/MIN/MAXper column following modeling-guidelines.md and naming-conventions.md. Load dax-guidelines.md before writing DAX.Add measures per Workflow: Modify an Existing Model.
Tier 1 (MCP): call tool create for each new measure with
expression,formatString, and target table. Do not hand-author TMDL.Tier 2 (no MCP): edit the table's
.tmdlfile directly and round-trip viaupdateDefinitionREST API.
Save & validate per Saving Changes to a Semantic Model.
Example 2: Create a New Semantic Model from a Fabric Lakehouse
Prompt: "Create a new Power BI semantic model in workspace Marketing, using the SalesLakehouse in the same workspace as the data source."
Agent response - follows Workflow: Create new Semantic Model.
Discover workspace + lakehouse IDs via
az rest.Discover lakehouse schema - list tables and columns via the Lakehouse OneLake APIs or the SQL Endpoint of the lakehouse. Capture table names, column names, and data types - these drive semantic model table, column definitions and partition metadata.
Select tool tier per Tool Selection Priority: prefer Tier 1 (
powerbi-modeling-mcp) for the full build. Tier 2 (PBIP/TMDL scaffolding then deploy) only if MCP is not registered.Build the model following Workflow: Create new Semantic Model - data source is OneLake so the storage mode is Direct Lake (load direct-lake-guidelines.md and modeling-guidelines.md). Design a star schema from the lakehouse tables, then:
Create the empty database.
Create one shared named expression for the lakehouse connection.
For each table: add
EntityPartitionSource(modedirectLake) -> columns mapped to lakehouse columns -> relationships -> measures.
Deploy per Workflow: Deploy to Fabric:
Validate per the Validation Checklist - confirm the model appears in the workspace and trigger an initial refresh to verify Direct Lake column mappings resolve.
Example 3: Bind a Semantic Model to a Fabric Connection
Prompt: "Bind semantic model Sales in workspace Marketing to the sql-prod connection."
Agent response - follows Workflow: Manage Semantic Model in Fabric § Connection Binding. No MCP - this workflow overrides Tool Selection Priority and is REST-only.
Discover workspace + model IDs via
az rest.Discover the target connection in Fabric by matching the server name:
SERVER="sql-prod" CONN_ID=$(az rest --method get --resource "https://api.fabric.microsoft.com" \ --url "https://api.fabric.microsoft.com/v1/connections" \ --query "value[?connectionDetails.path | contains(@, '$SERVER')] | [0].id" -o tsv)If no connection matches, stop and instruct the user to create one first per connection-binding.md.
Execute the bind per Workflow: Manage Semantic Model in Fabric § Connection Binding - load connection-binding.md and follow the discover -> match -> bind -> validate flow:
List the model's data source references (
List Item Connections).For each reference whose
connectionDetailsmatches$CONN_ID, call the FabricbindConnectionendpoint one request per data source reference (no bulk binding).
Validate - re-list item connections to confirm the binding, prompt the user to trigger a refresh per Workflow: Refresh Semantic Model. Credential errors -> stop and direct the user to the Service portal (per TROUBLESHOOTING).
TROUBLESHOOTING
| Symptom | Fix |
|---|---|
| MCP connection failure | Fall back to TMDL editing (see Tool Selection Priority). Inform the user about the fallback. |
| TMDL validation errors | Read error details, fix syntax, re-validate. Load tmdl-guidelines.md. |
403 Forbidden / identity None | User needs Contributor+ role - stop immediately. Do not retry. |
401 Unauthorized | Correct the --resource audience once (see semantic-model-rest-api.md). If it persists after the audience is right, it is missing permissions - stop and tell the user. Do not retry-loop. |
202 Accepted but no result | Poll LRO to completion. |
| Parts missing after updateDefinition | Must include ALL parts - modified + unmodified. |
| Refresh credential error | Direct user to configure in Service portal. Do not retry. |
| DAX errors in measures | Check column/table name references (case-sensitive). Verify referenced objects exist. |
| Deployment failure | Check workspace permissions, model compatibility level, and Direct Lake expression source references. |
| Missing data source | Verify M parameters or named expressions are correctly defined. |
Files
- ATTRIBUTION.mdAdded by AutoGineer893 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md40.9 KiB
- apm.yml679 B
- references/connection-binding.md10.6 KiB
- references/dax-guidelines.md11.1 KiB
- references/dax-perf-decision-guide.md32.2 KiB
- references/dax-perf-patterns.md30.0 KiB
- references/direct-lake-guidelines.md3.2 KiB
- references/field-parameters.md9.5 KiB
- references/metadata-discovery.md7.1 KiB
- references/modeling-guidelines.md16.3 KiB
- references/naming-conventions.md4.3 KiB
- references/pbip.md3.9 KiB
- references/semantic-model-ai-readiness.md13.5 KiB
- references/semantic-model-rest-api.md19.8 KiB
- references/tmdl-guidelines.md17.9 KiB
references/dax-perf-decision-guide.md
DAX Performance Decision Guide
Decision framework for optimizing DAX query performance: workflow phases, engine internals, trace diagnostics, and routing into the on-demand pattern catalog.
Reading Guide
Must Read — Every Optimization
Always read these sections fully before starting any optimization session:
Optimization Framework — tiers, autonomy rules, tool requirements
Phase 1: Establish Baseline — measure resolution, model context, run protocol
Phase 2: Optimization Iterations — apply, test, compare, iterate
Section 1: How the Engine Works — FE/SE architecture, xmSQL, segments, fusion
Section 2: Trace Diagnostics — metrics, event waterfall, signal interpretation
Consult When Needed
Read these only when directed by the Decision Guide or after Tier 1 is exhausted:
Section 3: Tier 1 — DAX Patterns — DAX001–DAX021 — load only routed candidate patterns first
Section 4: Tier 2 — Query Structure — QRY001–QRY004 — requires user approval before applying
Section 5: Tier 3 — Model Changes — MDL001–MDL009 — high caution, user approval, suggest model copy
Section 6: Tier 4 — Direct Lake — DL001–DL002 — high caution, user approval, requires ETL/pipeline changes
Decision Guide
Use this table as a router into Section 3. Route by trace evidence when available; otherwise route by DAX shape and treat the match as a hypothesis until execution results confirm it. Load only the routed candidate patterns first; read the rest of Section 3 only if no signal matches or routed patterns are exhausted. Sections 4–6 signals are escalation triggers; consult those sections only when the signal appears.
Section 3 — Where to Start (read all of §3)
| Route when trace shows | Or DAX shape shows | Start With |
|---|---|---|
CallbackDataID / callback-like FE row work | IF/SWITCH or DIVIDE() inside row iterators; per-row context transition inside iterators; ADDCOLUMNS/SUMMARIZE extension patterns | DAX002, DAX007, DAX008, DAX018 |
| High FE time, many short SE events, or repeated cache hits | repeated measure/expression references; SUMX(VALUES(col), CALCULATE(...)); high-cardinality iterator with low-cardinality dependency | DAX003, DAX006, DAX015 |
| SE rows far exceed result rows, or FE filters a broad SE result | FILTER(Table, ...) as filter argument; combined predicates; complex SUMMARIZE source; filters inside SUMMARIZECOLUMNS | DAX001, DAX005, DAX009, DAX010 |
| Multiple SE scans over the same fact with similar joins | sibling time-window measures; slice measures; SWITCH/IF branches choosing measures | DAX019, DAX020, DAX013 |
| Near-identical SE scans differ only by filter value | sibling measures differ only by per-measure filters on the same fact | DAX017 |
Large IN/INB, ININDEX, or compound tuple predicates | TREATAS/IN re-filters the same fact with computed keys | DAX021 |
DCOUNT in xmSQL | DISTINCTCOUNT, including distinct count over known unique key | DAX011, DAX014 |
| Result changes with grouping/filter context, or repeated predicates appear | ALLEXCEPT; ALL/REMOVEFILTERS + VALUES; duplicate predicates; redundant key-set filters | DAX012, DAX004 |
| Unexpected joins or expanded bridge/M2M paths | bidirectional/M2M relationship in filter path; TREATAS/CROSSFILTER in measure | DAX016 |
No signal matches? Read all of §3 — patterns DAX001–DAX021 cover the full range.
Sections 4–6 — Escalation Triggers
Only consult these sections if the corresponding signal is present. All require user approval before applying changes.
| Signal | Escalate To |
|---|---|
__ValueFilterDM in generated query | §4 → QRY002 |
Groupby column is high-cardinality (e.g., Calendar[Date]) | §4 → QRY003 |
| Tier 1 patterns exhausted; output change acceptable | §4 → QRY001–QRY004 |
| Few SE queries, low parallelism, clean xmSQL, high SE duration | §5/§6 → data layout |
| Many-to-many or bidirectional relationship overhead | §5 → MDL001 |
| Direct Lake model + low parallelism or cold cache | §6 → DL001–DL002 |
Optimization Framework
Tiers and Autonomy
| Tier | Scope | Autonomy |
|---|---|---|
| Tier 1 — DAX Patterns | Rewrite measure/UDF definitions | Auto-apply. Keep EVALUATE/grouping identical. |
| Tier 2 — Query Structure | Modify EVALUATE, grain, filters | Present recommendation. Wait for explicit user approval. |
| Tier 3 — Model Changes | Relationships, columns, agg tables, data types | High caution. Discuss trade-offs. Suggest model copy. Warn downstream risk. |
| Tier 4 — Direct Lake | OneLake layout, V-ordering, rowgroup sizing | High caution. Requires ETL/pipeline changes outside the model. |
Success criteria — Tier 1: Query duration improvement AND semantic equivalence (same row count, column count, data values). Success criteria — Tier 2/3/4: Query duration improvement AND explicit user approval of output or structural changes.
Requirements
Semantic model connection — Any client that satisfies the Trace capture and Model metadata requirements below. See Trace Capture Methods for capability comparison across common clients.
Trace capture — Ability to execute DAX queries with server timing trace capture. See Trace Capture Methods.
Model metadata — Ability to read measure definitions, function definitions, calculation group expressions, table metadata, and relationship metadata from the model.
Trace Capture Methods
| Method | Scope | Capture mode | How you drive it | Notes |
|---|---|---|---|---|
powerbi-modeling-mcp | Local PBI Desktop + remote (Fabric XMLA) | Live trace subscription | Tool calls (agent-friendly) | Returns pre-calculated FE/SE split, peak memory, and result rows. |
| TOM Trace API (ADOMD.NET / PowerShell) | Local PBI Desktop + remote (Fabric XMLA) | Live trace subscription | PowerShell / .NET scripts | Subscribe to QueryEnd, VertiPaqSEQueryEnd, VertiPaqSEQueryCacheMatch and derive FE/SE manually (FE = TotalDuration − union(VertiPaqSEQueryEnd intervals); SE wall-clock is the union of overlapping intervals, not the sum). Direct Lake databases are not exposed via the PBI Desktop local AS proxy — connect to the Fabric workspace XMLA endpoint instead. |
| DAX Studio | Local PBI Desktop + remote (Fabric XMLA) | Live trace subscription | Interactive UI | Server Timings pane shows pre-calculated FE/SE. Best for hands-on investigation. |
Fabric Workspace Monitoring (SemanticModelLogs Eventhouse table) | Fabric workspaces (Workspace Monitoring enabled) | Logged events, queried after the fact | KQL queries against the Eventhouse | Per-row OperationName, DurationMs, CpuTimeMs; correlate events for one query via OperationId. Best for after-the-fact production analysis at scale; not suited for tight iterate-and-rerun loops. |
Phase 1: Establish Baseline
Step 1: Resolve All Measure and Function Definitions
Before optimizing, fully resolve every DAX expression in the query. Partial visibility leads to incorrect or incomplete optimizations.
Identify measure references in the user's query — any
[MeasureName]pattern.Retrieve each measure's expression — read the measure definition (name, table, DAX expression) from the model.
Recursively resolve dependencies — read each expression, find nested
[OtherMeasure]calls, fetch those too.Retrieve user-defined functions if referenced.
Build a DEFINE block that explicitly inlines all resolved measures and functions.
Check for active calculation groups — list all calculation groups in the model, retrieve their calculation item expressions. Note any that may be active in the query context as they affect query plans for every intercepted measure.
Example: If [Profit Margin] = DIVIDE([Total Profit], [Total Revenue]), retrieve all three definitions and build:
DEFINE
MEASURE 'Sales'[Total Revenue] = SUM('Sales'[Revenue])
MEASURE 'Sales'[Total Profit] = SUM('Sales'[Revenue]) - SUM('Sales'[Cost])
MEASURE 'Sales'[Profit Margin] = DIVIDE([Total Profit], [Total Revenue])
EVALUATE
SUMMARIZECOLUMNS ( 'Product'[Category], "Profit Margin", [Profit Margin] )Step 2: Gather Model Context
List all tables — understand table structure and storage modes (Import, DirectQuery, Direct Lake).
List all relationships — understand join paths and filter propagation.
This context helps distinguish model design issues (missing star schema, bidirectional relationships) from DAX expression problems.
Step 3: Execute Baseline (1 warm-up + 2+ measured runs)
For each run:
Clear VertiPaq cache — clears the SE query cache only; columns stay resident.
Warm-up run: cold (on disk) → warm (resident).
Measured runs: warm + no-cache — the ideal optimization-test state.
Execute with trace capture — run the DAX query with server timing trace enabled.
Derive key metrics — Total Duration, FE/SE split, SE query count, peak memory, and result row count. See Understanding FE vs. SE Metrics for derivation from trace events.
Record all metrics, save the full trace events, and save the baseline result data for semantic equivalence checks.
After all runs: discard the warm-up. Start with 2 measured runs. If timings are close (for example, <20% spread), use the fastest valid measured duration as the baseline and record both runs. If the spread is wider, keep adding measured runs until the normal range is clear; discard only protocol-invalid runs (for example, cache not cleared), use the fastest valid duration, and record the full range.
Isolating measures: When a query has many measures and the trace is noisy, comment out all but one (or a small group), re-run, and compare. Repeat in groups to isolate which measures drive the majority of total duration.
Step 4: Analyze Baseline
Apply Section 2: Trace Diagnostics to interpret the metrics and events. Use the Decision Guide above to identify which Section 3 patterns to try first.
Phase 2: Optimization Iterations
Step 1: Select and Apply Optimizations
Using Section 3 (Tier 1), start from trace identifiers when available; otherwise use the DAX-only fallback patterns as hypotheses. Apply one or more of DAX001–DAX021.
CRITICAL: Modify only the measure definitions in the DEFINE block. Do NOT change the EVALUATE clause or SUMMARIZECOLUMNS grouping columns. Query structure must stay identical to preserve semantic equivalence.
Step 2: Execute and Compare
Clear the VertiPaq cache (returns the model to the warm + no-cache state — same condition as the baseline measured runs).
Execute the query with trace capture enabled.
During iteration: No warm-up is needed — columns are already resident from baseline. Clear the SE cache, execute 2 measured runs, and use the fastest valid duration if timings are consistent. If the spread is wide, run additional measured attempts until normal performance is clear. Reserve the baseline protocol (warm-up + measured runs) for final confirmation against the original baseline.
Evaluate:
Improvement = (BaselineDuration − OptimizedDuration) / BaselineDuration × 100 using fastest valid measured durations.
Semantic equivalence: Compare the CSV result from this run against the baseline CSV — same row count, same columns, same data values. If results differ, the change modified calculation semantics — revert it. Check this immediately after each iteration, not after multiple changes.
Step 3: Iterate and Escalate
Meaningful improvement + semantically equivalent → Success. "Meaningful" = exceeds the baseline's run-to-run noise band (e.g., baseline 1200/1280/1310 ms → 1200 ms is noise; 900 ms is real). Present to user; offer the optimized query as new baseline for further rounds (compound improvements are common).
Further rounds: Re-run Phase 1 Steps 3–4 on the new baseline; re-analyze the new structure against the Decision Guide, as it may expose patterns that didn't apply before (fusion, materialization, etc.).
Within the noise band → Try another Section 3 pattern, or combine patterns. Re-examine trace for other bottlenecks.
Results differ → Revert; the optimization changed semantics. Try another approach.
Tier 1 exhausted → Move to Phase 3 (Tier 2) with user approval. "Exhausted" = every signal-matching pattern tried (individually + combined), measures isolated for multi-measure queries, last 1–2 attempts at noise floor.
Phase 3: Query Structure Changes (Tier 2 — User Approval Required)
STOP — Do not modify the query structure without explicit user approval.
Consult Section 4: Tier 2 — Query Structure Patterns (QRY001–QRY004).
Before applying any change:
Explain the specific change (e.g., "Group by YearMonth instead of Date reduces result rows from 365K to 12K").
Explain what changes in the output and what the user gains in performance.
Wait for explicit approval.
If approved, modify query structure, run the full baseline cycle, present results.
Phase 4: Model and Data Layout Changes (Tier 3/4 — High Caution, User Approval Required)
STOP — Do not modify the model without explicit user approval.
Consult Section 5: Tier 3 — Model Patterns (MDL001–MDL009) and Section 6: Tier 4 — Direct Lake (DL001–DL002).
Before proceeding:
Present the specific diagnosis and proposed model change.
Explain why the model design is causing the performance bottleneck.
Warn that model changes can break downstream reports and visuals.
Suggest creating a copy of the semantic model to experiment on.
Identify if upstream changes are required (Lakehouse tables, Warehouse views, Power Query transformations) — these cannot be done through semantic model tooling alone.
If approved, coordinate with the user's CI/CD process.
After applying changes, re-run the full baseline optimization workflow to measure impact.
Error Handling
Connection failure — Verify dataset name, workspace name, or XMLA endpoint. For Desktop, ensure Power BI Desktop is running and note the local port. For Service, verify XMLA read/write is enabled on the capacity.
Query syntax error — Validate DAX syntax before executing.
Semantic equivalence failure — Optimization changed calculation semantics. Review filter context, aggregation granularity, and CALCULATE filter arguments. Revert and try differently.
No improvement found — Some queries are already well-optimized at the DAX level. Check whether the bottleneck is data layout (Phase 4) or query structure (Phase 3).
Trace events empty — Ensure server timing / trace capture is enabled before executing the query. Verify the trace is subscribed to the correct event types (
QueryEnd,VertiPaqSEQueryEnd,VertiPaqSEQueryCacheMatch).
Section 1: How the Engine Works
Query Processing Architecture
Every DAX query runs through two components: the Formula Engine (FE) and the Storage Engine (SE).
The FE handles all DAX — branching logic, context transitions, complex arithmetic, measure evaluation. It is single-threaded and the bottleneck in most poorly written queries.
The SE reads compressed columnar data from VertiPaq. It is multi-threaded and very fast, but supports only a limited set of operations: the four basic arithmetic operators, GROUP BY, LEFT OUTER JOINs, and basic aggregations (SUM, COUNT, MIN, MAX, DISTINCTCOUNT).
For DirectQuery models, the data source serves as the SE role: the FE generates and pushes down SQL, trading network/source latency for in-memory scan cost.
How they interact:
FE requests data via one or more SE scans; each result is a datacache (columns + aggregated values).
Complex queries need multiple datacaches (e.g., one builds a filter set, one aggregates the fact).
If the SE can't evaluate an expression natively → callback to FE row-by-row → that scan is effectively single-threaded.
The core principle of DAX optimization: push as much work as possible into the SE, minimize SE scans, and eliminate callbacks entirely.
xmSQL: The Storage Engine Query Language
xmSQL is the human-readable representation of SE scan activity in trace events — it shows which tables are scanned, which columns are aggregated, which filters apply, and how joins resolve. Syntax resembles SQL with key differences:
Implicit GROUP BY: Every column in the SELECT list is automatically a grouping column — no GROUP BY keyword.
Computed expressions: Row-level calculations use a WITH block with :=, referenced in aggregations via @:
WITH $Expr0 := ( 'Sales'[UnitPrice] * 'Sales'[OrderQuantity] )
SELECT Product[Category], SUM ( @$Expr0 )
FROM Sales
LEFT OUTER JOIN Product ON 'Sales'[ProductKey] = Product[ProductKey]Relationship joins are LEFT OUTER unless otherwise stated: the many-side table is FROM, the one-side is joined in. Other internal forms (INNER JOIN, REDUCED BY, reverse joins) can also appear depending on the operation.
Semi-join projections: Appear as DEFINE TABLE $Filter0 ... ININDEX in xmSQL — an initial dimension scan builds a key index injected into the fact WHERE clause.
Callbacks: Occur whenever the SE must compute an expression that falls outside of VertiPaq's native capabilities. Forms include CallbackDataID (arbitrary expressions, most common) and specialized variants exposing individual FE functions (rounding, log/abs math). See DAX002, DAX007, DAX008, DAX018 for callback elimination patterns.
Compression, Segments, and Parallelism
Compression determines scan speed. VertiPaq uses dictionary and run-length-style encodings to reduce scan work. For Direct Lake, source Delta/Parquet layout affects how quickly columns load into VertiPaq; V-Order improves RLE-friendly layout for read-heavy Power BI tables (see DL001).
Segments are fixed-size column chunks — the unit of both compression and parallel execution. The SE assigns one CPU thread per segment, so segment count determines how many cores a scan can utilize.
Parallelism: A 32M-row table in 2 segments uses 2 threads; in 32 segments it uses all 16 available threads — a 4–8× speedup with zero DAX changes.
Segment skew matters equally: if one segment has 15M rows and the rest have 1M, the scan bottlenecks on the oversized segment. Segments must be evenly sized for parallelism to be effective.
Diagnosing low parallelism: The SE Parallelism Factor (StorageEngineCpuTime ÷ StorageEngineDuration) shows thread utilization. Values near 1.0 mean single-threaded execution; values of 8–16 indicate strong multi-core use. When a trace shows few SE queries (1–4), high SE Duration, Parallelism Factor ≈ 1.0, and clean xmSQL — the bottleneck is likely too few segments or skewed segment sizes. DAX changes are unlikely to help; use data layout instead (see General Data Layout Best Practices and DL001–DL002).
SE Query Fusion
Fusion is the engine's ability to combine multiple SE scans into fewer scans. Two flavors:
Vertical fusion merges multiple measure aggregations that share the same filter context into a single SE query. Three measures on the same fact table under the same filter = one scan instead of three. Gain scales with fact table size.
Horizontal fusion merges SE queries that differ only in which value(s) of a column they filter. N separate fact scans collapse to one; the FE partitions the result.
Why fusion breaks. Fusion needs the competing SE requests to have compatible scan shapes — the same filter context for vertical fusion, compatible single-column filter differences for horizontal. Common triggers that break that:
FE chooses the branch — SWITCH/IF between measures, or per-measure filter predicates that materialize separate
VANDtuples → structurally different SE queries → see DAX017Table- or range-valued filter — time intelligence (DATESYTD, DATEADD, etc.) injects a per-measure date/range scan the SE can't fold in → see DAX019
Slicing column not in the groupby — horizontal fusion can only merge slices the result groups by; absent that, scans stay separate
Runtime-computed filter value — a predicate held in a variable is treated as dynamic and won't fuse
Calculation group items — each item applies its own filter modification → structurally different SE query
Trace diagnosis: Same fact table + same joins across multiple SE queries → missed vertical fusion. Near-identical queries differing only by WHERE values → blocked horizontal fusion. See Section 3 and Section 2 trace analysis.
Section 2: Reading and Diagnosing Traces
Understanding Formula Engine (FE) vs. Storage Engine (SE) Metrics
These are the critical metrics for DAX optimization, derived from Analysis Services trace events.
| Metric | How to Derive | Description | Target |
|---|---|---|---|
| Total Duration | QueryEnd.Duration | End-to-end query time (ms) | Lower is better |
| FE Duration | Total Duration − SE wall-clock time | Single-threaded FE processing time (ms) — the #1 bottleneck in most slow queries | Lower is better |
| SE Duration | Union of overlapping VertiPaqSEQueryEnd intervals | Multi-threaded SE query time (ms) | Higher % of total is better |
| SE Query Count | Count of VertiPaqSEQueryEnd events | Number of SE scans generated | Fewer is better |
| SE CPU Time | Sum of all VertiPaqSEQueryEnd.CpuTime | Total CPU across all SE threads | Higher ratio to SE Duration is better |
| SE Parallelism Factor | SE CPU Time ÷ SE Duration | Thread utilization across all scans | Higher is better (>1 = multi-threaded) |
| Cache Matches | Count of VertiPaqSEQueryCacheMatch events | Cache hits (SE queries answered from memory) | Only relevant on warm cache |
| Peak Memory (KB) | From execution metrics summary | Memory consumed during query execution | Lower is better — high values signal excessive materializations |
| SE Scan Row Count | volume from [Estimated size (volume, marshalling bytes): X, Y] in VertiPaqSEQueryEnd.TextData | Rows materialized per SE scan | Large volumes signal excessive materialization — the SE is handing too many rows to the FE |
| FE % | FE Duration ÷ Total Duration × 100 | Percentage of time in formula engine | Lower is better |
| SE % | SE Duration ÷ Total Duration × 100 | Percentage of time in storage engine | Higher is better |
Net wall-clock: SE Duration is the union of overlapping SE intervals — not the sum of individual durations. Three concurrent 100ms scans = ~100ms wall clock, not 300ms.
Parallelism — aggregate vs. per-scan: The aggregate parallelism factor is computed across all SE scans. Each individual scan has its own CpuTime / Duration. A healthy aggregate factor can mask a single unparallelized scan where CpuTime ≈ Duration.
FE processing gaps: FE Duration is the sum of all time intervals where no SE query was executing — gaps between SE events on the timeline.
Analyzing Trace Events
Trace events are captured from the Analysis Services engine during query execution. Each event includes: EventClass (event type), EventSubclass, TextData (xmSQL or DAX), Duration, CpuTime, StartTime, EndTime.
Key event types:
VertiPaqSEQueryBegin/VertiPaqSEQueryEnd— SE scan lifecycle.DurationandCpuTimeare on the End event.TextDatacontains the xmSQL query.VertiPaqSEQueryCacheMatch— SE query answered from cache (no scan). Count these separately.QueryBegin/QueryEnd— Overall DAX query lifecycle.Durationon QueryEnd = total wall-clock time.AggregateTableRewriteQuery— Fired when the engine rewrites a query to use an aggregation table.TextDatacontains the rewritten query. Presence indicates the engine found and used an agg table hit — absence on an agg-enabled model means the query fell through to the detail table.
Filtering trace output: Focus on the event types above. Ignore
VertiPaqScanInternalsubclass events — these duplicate the outerVertiPaqScanwith internal detail (e.g.,DC_KIND="DENSE") and identical timing. Also ignoreCommandBegin/CommandEnd(DAX execution wrapper, no diagnostic value) andErrorevents (only relevant when errors occur).
Per-scan derived metrics (from VertiPaqSEQueryEnd events):
Each VertiPaqSEQueryEnd event provides the raw data to derive per-scan diagnostics:
Rows scanned / Marshalling KB — parse
[Estimated size (volume, marshalling bytes): X, Y]at the end ofTextData. X = rows, Y = bytes. Identifies excessive materializations on a specific scan.Per-scan parallelism —
CpuTime / Durationfor that individual scan. A ratio near 1.0 means single-threaded even if the aggregatestorageEngineCpuFactorlooks healthy.Callbacks on slow scans — scan
TextDataforCallbackDataID/EncodeCallbackto confirm which specific SE query has the callback.
Building an FE gap waterfall:
FE processing occurs in the gaps between SE events. Use StartTime/EndTime offsets from QueryBegin.StartTime to build a timeline:
Gap between
QueryBeginand the first SEStartTime→ FE plan compilationGap between one SE
EndTimeand the next SEStartTime→ FE processing blockGap between the last SE
EndTimeandQueryEnd.EndTime→ final FE assemblyOverlapping SE events → parallel SE execution; sequential non-overlapping → FE feeding results between scans
A large gap (>100ms) signals expensive FE computation — examine the SE query before the gap
What to Look For
Scan for these signals in priority order when analyzing a slow query:
Callbacks —
CallbackDataIDorEncodeCallbackin SE TextData. Fix first (DAX002, DAX007, DAX008, DAX018).High FE % — FE doing too much work; usually paired with many short SE queries.
High SE query count / repeated fact scans — multiple SE queries hitting the same fact table with same joins but different WHERE clauses or aggregations → blocked fusion. See SE Query Fusion.
Large materializations — SE rows far exceed final result, or SE queries with no WHERE clause → FE filtering post-materialization instead of pushing to SE. See DAX009.
Low parallelism factor — near 1.0 on slow scans → data layout problem, not DAX. See Compression, Segments, and Parallelism.
High KB per SE event — wide intermediate tables; reduce columns or aggregate earlier.
Two-step dimension pre-scans — dimension-only SELECT followed by
where predicateon the fact. Restructure query to collapse into one scan.Large semi-join index tables —
DEFINE TABLE+ININDEXorWHERE ... INwith hundreds of compound tuples (e.g.,(GroupByCol, FilterKey)pairs). See DAX021.Missing aggregate table hit — Model has agg tables configured but no
AggregateTableRewriteQueryevent in the trace → query fell through to the detail table. Check agg table mappings and query grain.
Prioritization: Callbacks → Large FE processing → SE query count (DAX) → parallelism and data volume (data layout). Target the highest-duration SE scan first — ignore 0ms cache-hit scans.
DAX vs. Data Layout: Reading the Signal
Many SE queries + high FE time + individually short SE scans → DAX problem
Fusion is blocked, callbacks are present, or filters resolve iteratively. Fix the DAX — see Section 3 and Section 4. Example: 109 SE queries, 30% FE → after restructuring: 4 SE queries, 1% FE.
Few SE queries + low FE time + high SE duration + low parallelism → Data layout problem
The DAX is clean but SE scans are slow due to insufficient segments or poor compression. DAX changes will not help — see Section 5 / Section 6 (General Data Layout Best Practices, DL001–DL002).
Further Reading
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Microsoft
- Publisher
- Microsoft (Official)
- Source
- microsoft/skills-for-fabric · skills/semantic-model-authoring · commit f8fac11
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- microsoft/skills-for-fabric, microsoft/skills-for-fabric
AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download semantic-model-authoring.zip