Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power BI

Implementing Power BI Query Scale-Out with Read-Only Replicas to Distribute Enterprise Dataset Load Across Multiple Processing Nodes

When hundreds of users hammer a single Power BI dataset simultaneously, no amount of DAX optimization saves you — you need architectural separation of read and write operations. This deep-dive lesson teaches you to implement Query Scale-Out with read-only replicas from capacity sizing through monitoring, including the consistency window behaviors that most tutorials skip entirely.

🔥 Expert29 min readAug 21, 2026Updated Sep 23, 2026
Implementing Power BI Query Scale-Out with Read-Only Replicas to Distribute Enterprise Dataset Load Across Multiple Processing Nodes
On this page
  • Introduction
  • Prerequisites
  • Understanding What Query Scale-Out Actually Does at the Engine Level
  • The Vertipaq Engine and the Single-Writer Constraint
  • How Read-Only Replicas Are Created and Maintained
  • The Query Router and Connection Routing Logic
  • Capacity-Level Configuration: Setting the Stage
  • Enabling Scale-Out at the Capacity Level
  • Checking In-Memory Dataset Size via XMLA
  • Dataset-Level Configuration: Enabling and Tuning Scale-Out
  • Enabling Scale-Out in the Dataset Settings
  • Configuring Scale-Out via the Power BI REST API
  • Using PowerShell for Dataset Configuration at Scale
  • The Consistency Window: Designing Around Eventual Consistency
  • Quantifying the Consistency Window
  • The Manual Synchronization Pattern for Financial Reporting
  • Connection String Routing: Controlling Where Your Queries Land
  • Forcing a Read-Write (Primary) Connection
  • Forcing a Read-Only Replica Connection
  • Connection String Routing in Power BI Desktop
  • Integrating with paginated reports and SQL Server Reporting Services
  • Monitoring: You Can't Manage What You Can't See
  • Connecting Power BI to Azure Log Analytics
  • Tracking Synchronization Lag Over Time
  • Building a Replica Health Dashboard
  • Advanced Patterns and Integration Considerations
  • Scale-Out and Incremental Refresh: A Powerful Combination
  • Scale-Out and Deployment Pipelines
  • Scale-Out with External Tools via XMLA: Third-Party BI Considerations
  • Hands-On Exercise
  • Setup and Prerequisites for the Exercise
  • Step 1: Profile Your Dataset's Memory Footprint
  • Step 2: Enable Scale-Out via REST API
  • Step 3: Verify Replica Status
  • Step 4: Generate Load and Observe Distribution
  • Step 5: Test Manual Synchronization Control
  • Common Mistakes & Troubleshooting
  • Mistake 1: Enabling Scale-Out on Undersized Capacities
  • Mistake 2: Ignoring the Consistency Window in Report Design
  • Mistake 3: Write Operations Against Read-Only Replicas
  • Mistake 4: Not Accounting for Scale-Out in Refresh Windows
  • Mistake 5: Misconfigured Deployment Pipeline Post-Steps
  • Troubleshooting Replica Synchronization Failures
  • Summary & Next Steps
  • What to Explore Next
  • Implementing Power BI Query Scale-Out with Read-Only Replicas to Distribute Enterprise Dataset Load Across Multiple Processing Nodes

    Introduction

    Picture this: your enterprise Power BI environment is humming along on a Monday morning. Finance is running their end-of-month variance reports, the sales team is hammering the regional pipeline dashboards, and the executive team has scheduled their weekly KPI review — all at the same time, all hitting the same 40 GB semantic model that took three hours to refresh overnight. By 9:15 AM, you're getting Slack messages from department heads about sluggish dashboards, and your Power BI Premium capacity is pegged at 100% CPU. The refresh you need to kick off for the updated actuals data is sitting in a queue, waiting for resources to free up. You've been here before. You know the fix is not simply throwing more capacity at the problem.

    The real issue is architectural: you have a single processing node absorbing every read query, every DAX calculation, and every data refresh simultaneously. Power BI's Query Scale-Out feature — available on Premium Per Capacity (P-SKU), Premium Per User (PPU), and the newer Fabric F-SKU capacities — directly addresses this by introducing read-only replicas of your semantic model. Instead of one dataset engine handling everything, query scale-out creates multiple synchronized copies of your model across separate processing nodes, routing interactive report queries to those replicas while the primary dataset handles data refreshes and write operations. The result is a fundamental separation of concerns at the infrastructure level.

    By the end of this lesson, you will understand not just how to flip on the feature, but the underlying architecture that makes it work, the scenarios where it shines (and where it doesn't), and the operational discipline required to run it reliably at scale. You'll walk away with the knowledge to design, implement, and troubleshoot a query scale-out deployment for a production enterprise environment.

    What you'll learn:

    • The internal architecture of Power BI Query Scale-Out, including how replicas are synchronized and how the query router distributes load
    • How to configure scale-out at the capacity, workspace, and dataset level — including the less-documented settings that actually matter
    • How to manage the consistency window between the primary dataset and its read replicas, and how to design your reporting architecture around it
    • How to instrument and monitor replica health, query distribution, and resource utilization using Log Analytics and the XMLA endpoint
    • Advanced patterns including pinning specific report connections to specific replicas, and integrating scale-out with CI/CD pipelines and deployment pipelines

    Prerequisites

    Before working through this lesson, you should be comfortable with:

    • Power BI Premium or Fabric capacity administration — you need at minimum a Capacity Administrator role
    • Power BI semantic models and the XMLA endpoint — we'll interact with models via XMLA for monitoring and connection management
    • DAX and the Analysis Services engine — understanding Vertipaq and how DAX queries are processed will help you reason about what scale-out actually does
    • Tabular Editor or SSMS for XMLA connections — we'll use these tools to inspect model state
    • Azure Log Analytics — for monitoring query distribution and replica health
    • Basic familiarity with Power BI REST APIs — for programmatic configuration and automation

    If you're newer to Premium capacity administration, work through the foundational Premium capacity lessons in this learning path first.


    Understanding What Query Scale-Out Actually Does at the Engine Level

    Before you configure anything, you need a precise mental model of how this feature works. The marketing description — "distribute queries across replicas" — glosses over the details that determine whether your implementation succeeds or causes subtle data consistency bugs in production.

    The Vertipaq Engine and the Single-Writer Constraint

    Power BI semantic models run on the Analysis Services Vertipaq in-memory engine. Vertipaq is a columnar, in-memory store optimized for DAX query patterns. A critical characteristic of Vertipaq is that it operates on a single-writer, multiple-reader model. When a dataset refresh runs, Vertipaq must reconstruct column stores, recalculate hierarchies, and commit new data — operations that acquire exclusive write access to the in-memory structures. During this process, concurrent read queries either wait or are served from a snapshot of the previous model state, depending on how the refresh is configured.

    In a standard Premium dataset without scale-out, all of this happens on a single processing node. Read queries from interactive reports and background queries from scheduled reports compete directly with refresh operations for CPU and memory resources. When you have a 40 GB model with aggressive refresh schedules and hundreds of concurrent users, this contention is the root cause of your performance problems.

    How Read-Only Replicas Are Created and Maintained

    When you enable Query Scale-Out on a dataset, the Power BI service creates one or more read-only replicas of that dataset on separate nodes within your Premium capacity. These are not separate logical datasets — they are engine-level copies of the same semantic model, managed by the Power BI service automatically.

    The synchronization mechanism works as follows:

    1. After a successful dataset refresh completes on the primary node, the refreshed dataset image (the Vertipaq in-memory structures serialized to disk, essentially the .abf backup format) is propagated to each read-only replica node.
    2. Each replica loads the updated dataset image, replacing its previous in-memory state.
    3. During this propagation and load phase, the replica may briefly serve queries from its previous (slightly stale) state or hold queries in a short queue while the new image loads, depending on the timing.

    This is the architectural reality that creates the consistency window — the period between when data is refreshed on the primary and when all replicas reflect that updated state. For most enterprise reporting scenarios, this window is seconds to a few minutes. But for certain use cases — near-real-time operational dashboards, financial reporting where users cross-reference live transactional data — you must account for it explicitly.

    The Query Router and Connection Routing Logic

    The Power BI service includes a query router component that sits in front of your dataset and directs incoming connections. Here's how routing decisions are made:

    • Read/write connections (XMLA write operations, refresh-triggered connections, Report Builder connections using live refresh) are always routed to the primary dataset node.
    • Read-only connections (interactive report queries, Explore in Excel sessions, direct XMLA read connections) are routed to the available read-only replicas in round-robin or load-balanced fashion.
    • Connections that explicitly specify DataSourceType=ReadWrite in the XMLA connection string bypass replicas and hit the primary.

    This routing happens transparently for most Power BI report connections. The browser-based Power BI report viewer doesn't know or care which node it's hitting — it just sends DAX queries through the Power BI service, which routes them appropriately. The complexity emerges when you have direct XMLA connections from tools like Excel, third-party BI tools, or custom applications, and you need explicit control over which node they target.


    Capacity-Level Configuration: Setting the Stage

    Query Scale-Out is not enabled by default. Configuration happens in layers: capacity-level settings establish the maximum number of replicas available, and dataset-level settings control whether and how a specific dataset uses them. Let's walk through both.

    Enabling Scale-Out at the Capacity Level

    Sign in to the Power BI Admin Portal at app.powerbi.com/admin-portal. Navigate to Capacity settings and select your Premium capacity (P1, P2, F64, or larger — query scale-out requires at minimum P1 or F64). Under the Power BI workloads section, find the Datasets workload configuration.

    You'll see a setting labeled Max Memory for datasets and, critically, the Query Scale-Out setting. The Query Scale-Out toggle at the capacity level is a prerequisite — if it's off here, no dataset in this capacity can use replicas regardless of dataset-level settings.

    What the admin portal doesn't tell you — and what matters a great deal operationally — is that enabling Query Scale-Out at the capacity level reserves a portion of your capacity's total memory for replica hosting. On a P1 capacity (25 GB total dataset memory), enabling two replicas effectively divides your available memory three ways: primary dataset plus two replicas. A 40 GB model won't fit on a P1 with replicas enabled for that dataset, which means capacity sizing must account for the replica memory footprint before you enable this feature.

    The calculation you should do before enabling scale-out:

    Required capacity memory = (dataset_size_in_memory × (1 + replica_count)) + headroom_for_other_datasets
    

    For a dataset that loads to approximately 35 GB in memory with two replicas, you need at minimum 105 GB of dataset memory capacity — a P3 or F256. This is the most common reason enterprises turn on scale-out and immediately experience out-of-memory errors.

    Warning

    The in-memory size of a dataset is not the same as its .pbix file size or the size of the source data. A 5 GB .pbix file might expand to 20 GB in memory after Vertipaq loads and decompresses its column stores. Always profile your dataset's actual in-memory footprint using the System.Memory DMV via XMLA before calculating replica capacity requirements.

    Checking In-Memory Dataset Size via XMLA

    Connect to your dataset via SSMS or Tabular Editor using the XMLA endpoint (format: powerbi://api.powerbi.com/v1.0/myorg/YourWorkspaceName). Run the following DMV query:

    SELECT 
        DIMENSION_NAME,
        ATTRIBUTE_NAME,
        DICTIONARY_SIZE,
        USED_SIZE
    FROM $SYSTEM.DISCOVER_STORAGE_TABLE_COLUMNS
    ORDER BY USED_SIZE DESC
    

    For total model memory consumption:

    SELECT 
        DATABASE_NAME,
        [OBJECT_TYPE],
        SUM(USED_SIZE) as TotalUsedSize,
        SUM(DICTIONARY_SIZE) as TotalDictionarySize
    FROM $SYSTEM.DISCOVER_STORAGE_TABLE_COLUMNS
    WHERE DIMENSION_NAME IS NOT NULL
    GROUP BY DATABASE_NAME, [OBJECT_TYPE]
    

    This gives you the actual Vertipaq memory footprint broken down by column store data versus dictionaries. For capacity planning, use the TotalUsedSize figure. Convert from bytes to gigabytes (divide by 1,073,741,824) to compare against your capacity limits.


    Dataset-Level Configuration: Enabling and Tuning Scale-Out

    Once the capacity is correctly sized and scale-out is enabled at the capacity level, you configure individual datasets. Not every dataset needs scale-out — applying it indiscriminately wastes capacity memory. Target it at the datasets that are both large (memory-intensive to load) and heavily queried concurrently.

    Enabling Scale-Out in the Dataset Settings

    In the Power BI service, navigate to your workspace and find the semantic model (dataset) you want to configure. Select the Settings option from the dataset's context menu (the three-dot menu). In the settings pane, navigate to the Scale-out section.

    You'll see two key controls here:

    1. Enable Scale-Out toggle — turns on replica creation for this dataset
    2. Maximum number of read-only replicas — sets the upper bound on how many replicas the service will create (currently maximum of 3 replicas is supported, giving you a total of 4 copies: 1 primary + 3 replicas)

    The service doesn't always create the maximum number of replicas immediately. It scales replica count based on available capacity resources. In practice, on a properly sized capacity, you'll reach the maximum replica count within a few minutes of enabling the feature.

    Tip

    The Power BI service automatically manages replica lifecycle — creating them when resources are available and removing them when capacity is constrained. This elastic behavior is by design, but it means you cannot guarantee a specific number of active replicas at any given time. Design your monitoring to alert on replica count dropping below your expected minimum.

    Configuring Scale-Out via the Power BI REST API

    For production environments, you should manage scale-out configuration through the REST API or PowerShell rather than the UI. This enables configuration as code, integration with deployment pipelines, and auditability. Here's the REST API approach:

    PATCH https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/datasets/{datasetId}
    Content-Type: application/json
    Authorization: Bearer {access_token}
    
    {
      "queryScaleOutSettings": {
        "autoSyncReadOnlyReplicas": true,
        "maxReadOnlyReplicas": 3
      }
    }
    

    The autoSyncReadOnlyReplicas property is worth understanding carefully. When set to true (the default), the service automatically synchronizes replicas after each successful refresh. When set to false, replicas are not automatically updated after refreshes — you must trigger synchronization manually via a separate API call. This manual synchronization mode is actually useful in specific scenarios, which we'll cover shortly.

    Using PowerShell for Dataset Configuration at Scale

    If you're managing scale-out across dozens of datasets, PowerShell with the MicrosoftPowerBIMgmt module is more practical than manual REST calls:

    # Install the module if needed
    Install-Module -Name MicrosoftPowerBIMgmt -Scope CurrentUser
    
    # Connect to Power BI
    Connect-PowerBIServiceAccount
    
    # Function to enable scale-out on a dataset
    function Enable-DatasetScaleOut {
        param(
            [string]$WorkspaceId,
            [string]$DatasetId,
            [int]$MaxReplicas = 3,
            [bool]$AutoSync = $true
        )
    
        $body = @{
            queryScaleOutSettings = @{
                autoSyncReadOnlyReplicas = $AutoSync
                maxReadOnlyReplicas      = $MaxReplicas
            }
        } | ConvertTo-Json
    
        $url = "https://api.powerbi.com/v1.0/myorg/groups/$WorkspaceId/datasets/$DatasetId"
        
        Invoke-PowerBIRestMethod -Url $url -Method Patch -Body $body -ContentType "application/json"
        
        Write-Host "Scale-out configured for dataset $DatasetId with $MaxReplicas replicas"
    }
    
    # Apply to your target dataset
    Enable-DatasetScaleOut `
        -WorkspaceId "a7f2e3b1-4c9d-4e2a-8f1b-3c7e9d0a1b2c" `
        -DatasetId "b3e1a9c2-7d4f-4b8e-9c2a-5f3d7b0e1c4a" `
        -MaxReplicas 3 `
        -AutoSync $true
    

    The Consistency Window: Designing Around Eventual Consistency

    This is where most enterprise implementations either succeed or develop subtle, hard-to-diagnose bugs. Query scale-out introduces a form of eventual consistency into your reporting environment. After a refresh completes on the primary node, there is a propagation period before all replicas reflect the updated data. During this window, different users may see different data depending on which replica serves their query.

    Quantifying the Consistency Window

    In practice, the propagation window for most enterprise datasets on a properly resourced capacity ranges from 30 seconds to 5 minutes. The factors that influence this:

    • Dataset size: Larger datasets take longer to serialize, transfer, and load on replica nodes. A 5 GB dataset might synchronize in under a minute; a 35 GB dataset might take 4-5 minutes.
    • Capacity resource pressure: If the capacity is under load, replica synchronization competes for I/O and CPU resources.
    • Number of replicas: Three replicas take longer to synchronize than one, as each loads independently.
    • Network latency within the Azure region: Within a single Azure region, this is typically negligible, but cross-region Premium capacities can see higher synchronization latency.

    You can query the current synchronization state of your replicas via the REST API:

    GET https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/datasets/{datasetId}/queryScaleOut/syncStatus
    Authorization: Bearer {access_token}
    

    The response provides per-replica synchronization timestamps:

    {
      "commitVersion": "2024-01-15T06:47:23.000Z",
      "commitTimestamp": "2024-01-15T06:47:23.000Z",
      "minActiveReadVersion": "2024-01-15T06:45:12.000Z",
      "syncStartTime": "2024-01-15T06:47:25.000Z",
      "syncEndTime": "2024-01-15T06:47:51.000Z",
      "replicasSyncState": "Succeeded"
    }
    

    The commitTimestamp tells you when the primary completed its refresh. The syncEndTime tells you when the last replica finished loading. The difference is your actual consistency window for that refresh cycle.

    The Manual Synchronization Pattern for Financial Reporting

    Here's a sophisticated pattern used by enterprises with strict data consistency requirements — for example, month-end financial close reporting where all users must see the same data simultaneously, not a staggered view during the propagation window.

    Set autoSyncReadOnlyReplicas to false during the critical reporting window. After your primary refresh completes and your finance team has validated the data via a direct primary connection, you trigger synchronization explicitly:

    POST https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/datasets/{datasetId}/queryScaleOut/sync
    Authorization: Bearer {access_token}
    

    This initiates replica synchronization on demand. You can poll the sync status endpoint until replicasSyncState shows Succeeded, then send a notification to finance that all dashboards are showing validated, consistent data.

    The PowerShell implementation of this pattern:

    function Invoke-ControlledDatasetSync {
        param(
            [string]$WorkspaceId,
            [string]$DatasetId,
            [int]$TimeoutSeconds = 600
        )
    
        $syncUrl    = "https://api.powerbi.com/v1.0/myorg/groups/$WorkspaceId/datasets/$DatasetId/queryScaleOut/sync"
        $statusUrl  = "https://api.powerbi.com/v1.0/myorg/groups/$WorkspaceId/datasets/$DatasetId/queryScaleOut/syncStatus"
    
        # Trigger synchronization
        Write-Host "Initiating replica synchronization at $(Get-Date -Format 'HH:mm:ss')..."
        Invoke-PowerBIRestMethod -Url $syncUrl -Method Post
    
        # Poll until complete or timeout
        $elapsed   = 0
        $pollInterval = 15
    
        while ($elapsed -lt $TimeoutSeconds) {
            Start-Sleep -Seconds $pollInterval
            $elapsed += $pollInterval
    
            $status = Invoke-PowerBIRestMethod -Url $statusUrl -Method Get | ConvertFrom-Json
            Write-Host "[$elapsed s] Sync state: $($status.replicasSyncState)"
    
            if ($status.replicasSyncState -eq "Succeeded") {
                Write-Host "Synchronization completed at $(Get-Date -Format 'HH:mm:ss')"
                Write-Host "Consistency achieved as of: $($status.commitTimestamp)"
                return $true
            }
    
            if ($status.replicasSyncState -eq "Failed") {
                Write-Error "Replica synchronization failed. Check capacity health."
                return $false
            }
        }
    
        Write-Error "Synchronization timed out after $TimeoutSeconds seconds."
        return $false
    }
    

    Warning

    During the period when autoSyncReadOnlyReplicas is false and you haven't triggered a manual sync, your replicas continue serving queries from the previous refresh's data. This is intentional in the controlled-sync pattern but can be alarming if you haven't designed for it. Document this behavior explicitly in your runbooks.


    Connection String Routing: Controlling Where Your Queries Land

    For advanced scenarios, you need explicit control over which node a specific connection targets. Power BI's XMLA endpoint exposes this through connection string parameters.

    Forcing a Read-Write (Primary) Connection

    When you need a tool or application to always hit the primary dataset — for administrative work, model exploration with guaranteed latest data, or write operations — append DataSourceType=ReadWrite to the XMLA connection string:

    Data Source=powerbi://api.powerbi.com/v1.0/myorg/YourWorkspaceName;
    Initial Catalog=YourDatasetName;
    DataSourceType=ReadWrite
    

    In SSMS, this goes in the Server field when connecting. In Tabular Editor, it's configurable in the connection dialog.

    Forcing a Read-Only Replica Connection

    To explicitly target the replica pool (useful for testing that replicas are serving correctly, or for tools that you want to ensure don't impact primary node resources):

    Data Source=powerbi://api.powerbi.com/v1.0/myorg/YourWorkspaceName;
    Initial Catalog=YourDatasetName;
    DataSourceType=ReadOnly
    

    Connection String Routing in Power BI Desktop

    Power BI Desktop files that use a live connection to a Premium dataset always use the default routing — which means they'll be directed to read-only replicas when scale-out is active. This is the correct behavior for report development and sharing. However, if a report developer needs to validate against the primary's latest data during active development, they can temporarily modify the connection in Desktop's data source settings to include ReadWrite in the connection string — though this is a manual step and should be documented in your development workflow.

    Integrating with paginated reports and SQL Server Reporting Services

    Paginated reports (Power BI Report Builder) connect to datasets via the XMLA endpoint and can specify connection string properties. For paginated reports that run large, complex DAX queries — subscription-based delivery reports, large tabular exports — routing them to read-only replicas offloads significant CPU from the primary node. In Report Builder's connection settings, use the standard XMLA connection string without ReadWrite specified, and the query router will direct queries to replicas automatically.


    Monitoring: You Can't Manage What You Can't See

    A scale-out deployment without proper observability is a liability, not an asset. You need to know how many replicas are active, how queries are being distributed, and whether individual replicas are falling behind in synchronization.

    Connecting Power BI to Azure Log Analytics

    Power BI Premium supports native integration with Azure Log Analytics for query-level telemetry. Enable this in the Admin Portal under Azure connections > Log Analytics workspace. Once connected, the Power BI service streams detailed query logs — including which node served each query — to your Log Analytics workspace.

    Navigate to your Azure Portal, open your Log Analytics workspace, and query the PowerBIDatasetsWorkspace table. Here's a KQL query that gives you per-replica query distribution over a time window:

    PowerBIDatasetsWorkspace
    | where TimeGenerated > ago(24h)
    | where OperationName == "QueryEnd"
    | extend ReplicaId = tostring(parse_json(ExtendedProperties).ReplicaId)
    | extend DurationMs = tolong(parse_json(ExtendedProperties).QueryDuration)
    | summarize 
        QueryCount = count(),
        AvgDurationMs = avg(DurationMs),
        P95DurationMs = percentile(DurationMs, 95),
        P99DurationMs = percentile(DurationMs, 99)
        by ReplicaId, bin(TimeGenerated, 1h)
    | order by TimeGenerated desc, QueryCount desc
    

    This query tells you how many queries each replica is handling and whether query latency differs significantly between replicas. If one replica has consistently higher P95 latency, it may be under-resourced or experiencing synchronization issues.

    Tracking Synchronization Lag Over Time

    Build a monitoring query that tracks how long synchronization takes after each refresh:

    PowerBIDatasetsWorkspace
    | where TimeGenerated > ago(7d)
    | where OperationName in ("SyncStart", "SyncEnd")
    | extend DatasetId = tostring(parse_json(ExtendedProperties).DatasetId)
    | summarize 
        SyncStart = minif(TimeGenerated, OperationName == "SyncStart"),
        SyncEnd = maxif(TimeGenerated, OperationName == "SyncEnd")
        by DatasetId, bin(TimeGenerated, 1h)
    | extend SyncDurationSeconds = datetime_diff('second', SyncEnd, SyncStart)
    | where isnotnull(SyncDurationSeconds)
    | project TimeGenerated, DatasetId, SyncDurationSeconds
    | order by TimeGenerated desc
    

    Plot this as a time series in Azure Monitor or a Power BI report and set an alert when synchronization duration exceeds your SLA threshold (typically 2x the average sync duration for a given dataset).

    Building a Replica Health Dashboard

    Create a Power BI report that consolidates replica health using the REST API as a data source. Use Power BI's Web connector to call the sync status endpoint periodically, or use a Power Automate flow that polls the API on a schedule and writes results to a SharePoint list or Azure SQL table that your monitoring report reads.

    A practical monitoring dashboard should display:

    • Current replica count vs. configured maximum (alerts when below expected)
    • Last successful sync timestamp per replica
    • Sync duration trend over the past 7 days
    • Query volume per replica over the past 24 hours
    • P95 query latency trend by replica

    Advanced Patterns and Integration Considerations

    Scale-Out and Incremental Refresh: A Powerful Combination

    Incremental refresh reduces the volume of data processed during each refresh cycle by only updating partitions that contain new or changed data. Combined with scale-out, this creates an extremely efficient architecture for large datasets:

    • Incremental refresh keeps the primary's refresh duration short (minutes rather than hours), reducing the window during which the primary is occupied with write operations.
    • A shorter primary refresh means replicas synchronize more frequently and the consistency window narrows.
    • The hot and historical partition split in incremental refresh aligns well with scale-out: the frequently refreshed hot partition is small, so replica synchronization stays fast even on large historical datasets.

    Enable incremental refresh in Power BI Desktop using the RangeStart and RangeEnd parameters and configure it to store 36 months historically while refreshing only the last 3 days. With scale-out enabled, your replica sync times drop dramatically because only the changed partitions need to propagate — not the full dataset.

    Tip

    When using incremental refresh with scale-out, set your incremental refresh policy to only refresh complete days (not the current partial day) unless you're specifically building near-real-time dashboards. This prevents stale partial-day data from creating confusing discrepancies across replicas.

    Scale-Out and Deployment Pipelines

    If you're using Power BI Deployment Pipelines to manage development, test, and production environments, scale-out configuration must be part of your deployment process. Dataset settings — including scale-out — are workspace-level properties, not embedded in the .pbix file, so they don't automatically propagate through pipeline stages.

    Build a post-deployment script that configures scale-out settings after each pipeline deployment:

    function Set-PostDeploymentDatasetConfig {
        param(
            [string]$WorkspaceId,
            [hashtable]$DatasetScaleOutConfig
        )
    
        foreach ($dataset in $DatasetScaleOutConfig.Keys) {
            $config = $DatasetScaleOutConfig[$dataset]
            
            # Find the dataset ID by name in the target workspace
            $datasets = Invoke-PowerBIRestMethod `
                -Url "https://api.powerbi.com/v1.0/myorg/groups/$WorkspaceId/datasets" `
                -Method Get | ConvertFrom-Json
            
            $targetDataset = $datasets.value | Where-Object { $_.name -eq $dataset }
            
            if ($null -eq $targetDataset) {
                Write-Warning "Dataset '$dataset' not found in workspace $WorkspaceId"
                continue
            }
    
            $body = @{
                queryScaleOutSettings = @{
                    autoSyncReadOnlyReplicas = $config.AutoSync
                    maxReadOnlyReplicas      = $config.MaxReplicas
                }
            } | ConvertTo-Json
    
            Invoke-PowerBIRestMethod `
                -Url "https://api.powerbi.com/v1.0/myorg/groups/$WorkspaceId/datasets/$($targetDataset.id)" `
                -Method Patch `
                -Body $body `
                -ContentType "application/json"
    
            Write-Host "Configured scale-out for '$dataset': $($config.MaxReplicas) replicas, AutoSync=$($config.AutoSync)"
        }
    }
    
    # Configuration map for your production workspace
    $productionScaleOutConfig = @{
        "Enterprise Finance Model"  = @{ MaxReplicas = 3; AutoSync = $false }
        "Sales Analytics Platform"  = @{ MaxReplicas = 2; AutoSync = $true  }
        "Supply Chain Operations"   = @{ MaxReplicas = 2; AutoSync = $true  }
    }
    
    Set-PostDeploymentDatasetConfig `
        -WorkspaceId "prod-workspace-guid-here" `
        -DatasetScaleOutConfig $productionScaleOutConfig
    

    Run this script as part of your CI/CD pipeline immediately after a deployment pipeline promotion to production.

    Scale-Out with External Tools via XMLA: Third-Party BI Considerations

    Many enterprises use third-party tools — Tableau, Qlik Sense, custom applications with MDX/DAX queries — that connect to Power BI Premium datasets via the XMLA endpoint. These tools generally don't understand the Power BI-specific connection routing parameters. The default behavior is that XMLA connections without explicit DataSourceType specification are treated as read-only and routed to replicas.

    This is usually what you want, but verify it explicitly. Some connection libraries may send session properties that inadvertently signal a read-write intent, causing the query router to direct them to the primary node. If your third-party tool connections are consistently hitting the primary even with scale-out enabled, check the XMLA session properties being sent and consult the tool's documentation for connection string customization.


    Hands-On Exercise

    This exercise walks you through a complete scale-out implementation for a realistic enterprise scenario: the Northwind Enterprise Analytics semantic model, a 12 GB dataset serving finance, operations, and executive teams across a P2 Premium capacity.

    Setup and Prerequisites for the Exercise

    You'll need:

    • A Power BI Premium P1 or larger capacity (or F64+ Fabric capacity) with admin access
    • A workspace in that capacity with a substantial semantic model (or one you can create by importing a large dataset)
    • Azure Log Analytics workspace connected to your Power BI tenant
    • PowerShell with MicrosoftPowerBIMgmt module installed
    • SSMS or Tabular Editor for XMLA connections

    Step 1: Profile Your Dataset's Memory Footprint

    Connect to your dataset via the XMLA endpoint in SSMS. Use the connection string format powerbi://api.powerbi.com/v1.0/myorg/<YourWorkspaceName> and authenticate with your organizational account.

    Run the following to get the total in-memory size:

    SELECT 
        SUM(USED_SIZE) / 1073741824.0 as TotalMemoryGB,
        SUM(DICTIONARY_SIZE) / 1073741824.0 as DictionaryGB,
        COUNT(DISTINCT DIMENSION_NAME) as TableCount
    FROM $SYSTEM.DISCOVER_STORAGE_TABLE_COLUMNS
    

    Record the TotalMemoryGB value. For the exercise, assume this returns 11.4 GB. With three replicas, you need 11.4 × 4 = 45.6 GB of dataset memory capacity available. A P2 provides 50 GB — workable, but tight if other datasets share the capacity.

    Step 2: Enable Scale-Out via REST API

    Generate a Power BI service principal or use your user account's Bearer token. Using PowerShell:

    Connect-PowerBIServiceAccount
    
    # Get your workspace and dataset IDs
    $workspace = Get-PowerBIWorkspace -Name "Northwind Analytics Production"
    $dataset = Get-PowerBIDataset -WorkspaceId $workspace.Id | 
        Where-Object { $_.Name -eq "Northwind Enterprise Analytics" }
    
    Write-Host "Workspace ID: $($workspace.Id)"
    Write-Host "Dataset ID: $($dataset.Id)"
    
    # Enable scale-out with auto-sync
    $body = @{
        queryScaleOutSettings = @{
            autoSyncReadOnlyReplicas = $true
            maxReadOnlyReplicas      = 2
        }
    } | ConvertTo-Json
    
    Invoke-PowerBIRestMethod `
        -Url "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($dataset.Id)" `
        -Method Patch `
        -Body $body `
        -ContentType "application/json"
    
    Write-Host "Scale-out enabled. Waiting for replicas to provision..."
    

    Step 3: Verify Replica Status

    Wait 3-5 minutes after enabling scale-out, then check the sync status:

    $statusUrl = "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($dataset.Id)/queryScaleOut/syncStatus"
    
    $status = Invoke-PowerBIRestMethod -Url $statusUrl -Method Get | ConvertFrom-Json
    $status | ConvertTo-Json -Depth 5
    

    Verify that replicasSyncState shows Succeeded and the syncEndTime is recent (within the last few minutes).

    Step 4: Generate Load and Observe Distribution

    Open several browser tabs, log in as different users (or use incognito sessions with different accounts), and access your reports simultaneously. In parallel, watch the Log Analytics workspace:

    PowerBIDatasetsWorkspace
    | where TimeGenerated > ago(30m)
    | where OperationName == "QueryEnd"
    | extend ReplicaId = tostring(parse_json(ExtendedProperties).ReplicaId)
    | summarize QueryCount = count() by ReplicaId
    

    You should see queries distributed across your primary and replica nodes. If all queries show the same ReplicaId, the router isn't distributing load — check that the reports aren't using explicit ReadWrite connections.

    Step 5: Test Manual Synchronization Control

    Disable auto-sync and practice the controlled synchronization pattern:

    # Disable auto-sync
    $body = @{
        queryScaleOutSettings = @{
            autoSyncReadOnlyReplicas = $false
            maxReadOnlyReplicas      = 2
        }
    } | ConvertTo-Json
    
    Invoke-PowerBIRestMethod `
        -Url "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($dataset.Id)" `
        -Method Patch -Body $body -ContentType "application/json"
    
    # Trigger a refresh (or wait for a scheduled one to complete)
    # Then manually initiate sync
    $syncUrl = "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($dataset.Id)/queryScaleOut/sync"
    Invoke-PowerBIRestMethod -Url $syncUrl -Method Post
    
    # Poll until done
    do {
        Start-Sleep -Seconds 15
        $status = Invoke-PowerBIRestMethod -Url $statusUrl -Method Get | ConvertFrom-Json
        Write-Host "State: $($status.replicasSyncState) at $(Get-Date -Format 'HH:mm:ss')"
    } while ($status.replicasSyncState -notin @("Succeeded", "Failed"))
    

    Record the total sync duration. This is your baseline for SLA planning.


    Common Mistakes & Troubleshooting

    Mistake 1: Enabling Scale-Out on Undersized Capacities

    Symptom: After enabling scale-out, reports become slower or datasets are evicted from memory more frequently. The capacity utilization metrics show memory pressure spikes after refresh operations.

    Root cause: The replica copies require the same memory as the primary. Enabling 2 replicas on a dataset that already uses 70% of your capacity's memory causes total dataset memory to exceed capacity, triggering evictions.

    Fix: Profile all datasets' in-memory sizes, calculate total replica memory requirements, and right-size the capacity before enabling scale-out. Use DISCOVER_STORAGE_TABLE_COLUMNS DMV queries across all datasets on the capacity to get a complete picture.

    Mistake 2: Ignoring the Consistency Window in Report Design

    Symptom: Users report seeing different data in the same report at the same time, or data that appears "stale" compared to the source system shortly after a refresh.

    Root cause: Read-only replicas haven't finished synchronizing when users begin querying. Different users hit different replicas at different stages of synchronization.

    Fix: For reports where consistency is critical, either use the manual sync pattern (controlled synchronization after validation) or implement a visual indicator in reports showing the data's Last Refreshed timestamp. You can surface this via a calculated measure:

    Last Refreshed Timestamp = 
    "Data as of " & FORMAT(MAX('RefreshLog'[RefreshCompletedAt]), "YYYY-MM-DD HH:MM AM/PM")
    

    Include this in a report header card so users know exactly which data version they're viewing.

    Mistake 3: Write Operations Against Read-Only Replicas

    Symptom: XMLA write operations fail with permissions errors. Deployment scripts that push model changes fail intermittently.

    Root cause: Deployment scripts or XMLA tools using the default connection string (without DataSourceType=ReadWrite) are routed to read-only replicas, which reject write operations.

    Fix: Always specify DataSourceType=ReadWrite in the XMLA connection strings used by deployment and administrative scripts. Create a documented standard for your team: use ReadOnly for read operations in monitoring tools, ReadWrite for administrative and deployment connections.

    Mistake 4: Not Accounting for Scale-Out in Refresh Windows

    Symptom: Even with scale-out enabled, reports are still slow during the refresh window. Primary node CPU utilization drops (refresh is happening there), but users are experiencing slow query responses.

    Root cause: During replica synchronization, replicas are loading the new dataset image and are temporarily less available for query serving. If the sync window is long relative to your refresh frequency, there's a period where fewer replicas are fully operational.

    Fix: Combine scale-out with incremental refresh to shorten both the primary refresh duration and the replica sync window. For a 12 GB model, incremental refresh on the hot partition (last 3 days) might reduce the refreshed data volume to 500 MB, dropping sync time from 3 minutes to 30 seconds.

    Mistake 5: Misconfigured Deployment Pipeline Post-Steps

    Symptom: Scale-out works perfectly in production but isn't configured in test environments, causing test performance to differ significantly from production behavior. Bugs related to replica consistency go undetected until they reach production.

    Root cause: Deployment pipelines don't carry scale-out settings between stages.

    Fix: Include scale-out configuration in your deployment runbook as an explicit post-deployment step, applied to every pipeline stage that should mirror production behavior. Even in test environments, enable at least one replica so replica-specific behaviors can be tested.

    Troubleshooting Replica Synchronization Failures

    If replicasSyncState consistently returns Failed, investigate in this order:

    1. Check capacity health in the Admin Portal. Look for sustained memory pressure or CPU throttling in the 30 minutes following the refresh.
    2. Check the dataset refresh status — sync failures often follow partial refresh failures. If the primary's refresh didn't complete cleanly, there may be no valid image to propagate.
    3. Review Log Analytics for SyncFailed events with extended properties that include error codes.
    4. Temporarily reduce maxReadOnlyReplicas to 1 to rule out resource exhaustion from trying to sync multiple replicas simultaneously.
    5. Try disabling and re-enabling scale-out on the dataset — this forces the service to reconstruct the replica from the current primary state.

    Summary & Next Steps

    Query Scale-Out is one of the most impactful architectural tools in the Power BI Premium toolkit, but it demands more than a feature toggle. The implementation requires careful capacity sizing based on real in-memory footprints, deliberate decisions about synchronization strategy (auto vs. controlled), clear communication with report consumers about the consistency model, and robust monitoring instrumentation.

    The key ideas to carry forward:

    • Scale-out creates physical replica copies of datasets on separate nodes. Memory requirements multiply accordingly.
    • The query router handles connection distribution transparently for most Power BI clients, but XMLA connections require explicit DataSourceType parameters for fine-grained control.
    • The consistency window is not a bug — it's an inherent property of a distributed read model. Design around it explicitly rather than hoping users won't notice.
    • Manual synchronization gives you precise control over when replica data is updated, enabling strict consistency guarantees for critical financial and operational reporting.
    • Monitoring via Log Analytics with KQL queries gives you per-replica query distribution and latency visibility. Without it, you're flying blind.
    • Scale-out amplifies the value of incremental refresh by shortening sync windows and reducing capacity pressure during refresh operations.

    What to Explore Next

    From here, the natural progression deepens your enterprise Power BI architecture skills:

    1. Composite Models at Enterprise Scale — Understanding how DirectQuery partitions interact with scale-out replicas, and the routing implications for hybrid models.
    2. Power BI Embedded with Scale-Out — Embedding reports in applications while maintaining replica routing and multi-tenant isolation.
    3. Fabric Capacity Architecture — How F-SKU capacities differ from P-SKU in their scale-out implementation, and what the Fabric unified compute model means for dataset replicas in the long term.
    4. Automated Capacity Scaling — Combining scale-out with Azure Autoscale policies for Premium capacities to elastically adjust capacity size and replica count based on load patterns.
    5. Advanced XMLA Automation — Using the Tabular Object Model (TOM) programmatically for deep model inspection, partition management, and monitoring in scale-out environments.

    The organizations that get the most from Query Scale-Out are those that integrate it into a broader capacity governance practice — with documented sizing standards, automated deployment configuration, active monitoring dashboards, and regular capacity reviews. It's not a set-and-forget feature; it's a component of a living, managed enterprise data platform.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Enterprise Power BI

    Previous

    Power BI Field Parameters & Calculation Groups: Dynamic Measures and Dimension Switching for Enterprise Reports

    Next

    Power BI Personal Gateway vs. On-Premises Data Gateway: Choosing the Right Refresh Architecture

    Related Insights

    Power BIExpert

    Implementing Power BI External Data Sharing to Securely Distribute Live Semantic Models to External Partners and Business-to-Business Consumers Without Exporting Data

    29 min
    Power BIExpert

    DAX Many-to-Many Bridge Patterns with Bidirectional Filtering: Building Accurate Allocation, Attribution, and Shared Dimension Measures Across Complex Schemas

    28 min
    Power BIExpert

    Mastering Power BI Query Folding: Diagnosing Fold Boundaries, Optimizing Power Query Steps, and Reducing Data Source Load for Scalable Model Refreshes

    27 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding What Query Scale-Out Actually Does at the Engine Level
    • The Vertipaq Engine and the Single-Writer Constraint
    • How Read-Only Replicas Are Created and Maintained
    • The Query Router and Connection Routing Logic
    • Capacity-Level Configuration: Setting the Stage
    • Enabling Scale-Out at the Capacity Level
    • Checking In-Memory Dataset Size via XMLA
    • Dataset-Level Configuration: Enabling and Tuning Scale-Out
    • Enabling Scale-Out in the Dataset Settings
    • Configuring Scale-Out via the Power BI REST API
    • Using PowerShell for Dataset Configuration at Scale
    • The Consistency Window: Designing Around Eventual Consistency
    • Quantifying the Consistency Window
    • The Manual Synchronization Pattern for Financial Reporting
    • Connection String Routing: Controlling Where Your Queries Land
    • Forcing a Read-Write (Primary) Connection
    • Forcing a Read-Only Replica Connection
    • Connection String Routing in Power BI Desktop
    • Integrating with paginated reports and SQL Server Reporting Services
    • Monitoring: You Can't Manage What You Can't See
    • Connecting Power BI to Azure Log Analytics
    • Tracking Synchronization Lag Over Time
    • Building a Replica Health Dashboard
    • Advanced Patterns and Integration Considerations
    • Scale-Out and Incremental Refresh: A Powerful Combination
    • Scale-Out and Deployment Pipelines
    • Scale-Out with External Tools via XMLA: Third-Party BI Considerations
    • Hands-On Exercise
    • Setup and Prerequisites for the Exercise
    • Step 1: Profile Your Dataset's Memory Footprint
    • Step 2: Enable Scale-Out via REST API
    • Step 3: Verify Replica Status
    • Step 4: Generate Load and Observe Distribution
    • Step 5: Test Manual Synchronization Control
    • Common Mistakes & Troubleshooting
    • Mistake 1: Enabling Scale-Out on Undersized Capacities
    • Mistake 2: Ignoring the Consistency Window in Report Design
    • Mistake 3: Write Operations Against Read-Only Replicas
    • Mistake 4: Not Accounting for Scale-Out in Refresh Windows
    • Mistake 5: Misconfigured Deployment Pipeline Post-Steps
    • Troubleshooting Replica Synchronization Failures
    • Summary & Next Steps
    • What to Explore Next