Every analytics team eventually hits the same wall: the data that matters lives inside an operational database, and getting it into a reporting-friendly form usually means someone has to build and babysit a pipeline. Fabric Mirroring exists to remove that step. Rather than scheduling extracts or writing transformation jobs, a team points Mirroring at a supported source system and the platform takes over the job of keeping a copy current inside OneLake, translating the incoming data into Parquet-backed Delta tables along the way and freeing engineers from owning that plumbing themselves [S1].
That matters most for shops already standardized on Azure SQL Database: rather than standing up a parallel replication project, they can lean on Mirroring to keep a continuously refreshed copy of that estate available in OneLake, at a fraction of the cost and effort of a custom-built solution, and without touching the source system's design [S2]. Data Factory exposes the same capability under its own umbrella too, describing it as a way to maintain an up-to-date operational copy inside OneLake so that reports and analysis run against the copy rather than hammering the live transactional system [S3].
It sounds almost too simple: aim Mirroring at a source, and moments later the same data shows up as an ordinary Delta table, open to SQL, Spark, Power BI, or KQL. What trips teams up is not the setup — it's assuming Mirroring behaves like other replication tools they've used before. It doesn't preserve history, it isn't a backup mechanism, and it inherits none of the source's access controls automatically. The rest of this article walks through what Mirroring is built to do well, the practitioner gotchas that show up once it hits production, and just as importantly, where its edges are.
Three ways to mirror, one destination#
"Mirroring" is really an umbrella term covering three different mechanisms, and picking the right one depends on what a team actually needs to move [S1]:
- Database mirroring is the literal copy: table rows and their contents are physically replicated from source to OneLake. This is the default behavior most people picture when they hear "Mirroring."
- Metadata mirroring never touches the underlying rows at all. It replicates only the catalog layer — table names, schema, structure — and relies on a OneLake shortcut to reach across to where the source data already sits [S1].
- Open mirroring flips the model around: instead of Fabric pulling from a known connector, any developer can push change data into a mirrored item themselves, using a public API and a designated landing-zone URL. This is how sources with no built-in Fabric connector get mirrored [S1].
As of June 2026, the roster of database mirroring sources spans Azure Cosmos DB, Azure SQL Database, Azure SQL Managed Instance, Azure Database for PostgreSQL, Azure Database for MySQL (preview), SQL Server, Snowflake, Oracle, SAP, Google BigQuery (preview), Fabric SQL database (which wires itself up automatically), and mirrored databases populated through the open mirroring API. Azure Databricks and Dremio (the latter still in preview) aren't reached through the database-mirroring path at all — both go through metadata mirroring instead [S1]. Beyond Fabric's own docs, independent write-ups describe the same shape from the outside: one comparison piece frames Databricks' Unity Catalog mirroring as creating a OneLake shortcut to the source rather than copying it, which avoids duplicating storage [S6], and another positions Mirroring generally as the effective successor to Azure Synapse Link, covering Azure SQL, PostgreSQL, Cosmos DB, and Snowflake, while flagging that it still had some coverage gaps relative to Synapse Link at the time that piece was written [S7].
Fabric SQL database is the one entry on that list that configures its own mirroring automatically — every other source needs an explicit setup step [S1].
A Fabric product-blog post on multi-cloud architecture patterns places Mirroring specifically as the change-data-capture path for bringing Snowflake and BigQuery data into Fabric for near-real-time AI and BI scenarios — distinct from the scheduled Copy jobs used for broader, less time-sensitive cross-platform batch movement [S9].
Current state only — there is no history layer#
The single most important mental model to adopt before building on Mirroring: what lands in OneLake reflects the source right now, not a log of everything that happened to get there. Mirroring keeps overwriting the copy to match the live source; it was never designed to capture a stream of discrete change events, and it doesn't function as an append-only ledger of past states either [S4].
"A continuously updated current-state replica" [S4] — not a history you can rewind through.
If mirroring is paused and later resumed, Fabric doesn't pick up where it left off — it wipes the slate and reseeds entirely from whatever the source looks like at that moment, so any changes that happened in between are simply unrecoverable [S4].
The upshot: reach for Mirroring when the goal is a fresh, queryable snapshot of operational data. Reach for something else when an architecture needs to replay what changed and when, and never treat Mirroring as a safety net — if bad data or an accidental delete hits the source, that damage flows straight into the mirror too, meaning the mirrored copy offers no recovery point once the original is gone [S4].
When a project genuinely needs both angles — a live queryable view and a durable history of every change — the practical answer is to run Mirroring alongside a separate, minimal pipeline built specifically to append change records over time, since Mirroring alone was never meant to cover both jobs at once [S4].
What it costs to run#
Two design choices keep Mirroring's price tag low enough that most teams barely notice it:
- The replication itself doesn't burn capacity. The background work Fabric does to keep the mirror current runs outside the normal capacity-unit metering — it's only the downstream queries against that data (through SQL, Power BI, or Spark) that draw on standard capacity [S1].
- Storage comes with real headroom before charges kick in. Fabric grants one terabyte of mirrored-data storage free for every capacity unit a customer owns, so an F64 capacity carries 64 TB of no-cost storage dedicated to replicas; only usage beyond that ceiling, or storage tied to a paused capacity, actually bills [S1].
Inference: because neither the replication engine nor the storage allowance costs anything extra until a team blows past the included tier, Mirroring tends to be close to free for organizations that already own Fabric capacity for other analytics work — the marginal cost of adding a mirrored source is often just the query load it generates downstream.
How it works / best practices#
Point Power BI at mirrored data through Direct Lake, not Import#
Rule: build semantic models against mirrored tables using Direct Lake storage mode instead of scheduling an Import refresh.
Why: since the mirrored output already sits in OneLake as Delta-formatted files, Direct Lake can query that data where it lives, so reports track the live replica without a separate copy step inside the model [S1].
# Semantic model configuration
Storage mode: Direct Lake
Source: <mirrored-database-item>.OneLake
# No scheduled refresh needed — the model reads the Delta files
# directly, so report freshness tracks replication lag, not a
# refresh cadence you have to manage.
Federate mirrored tables with warehouse and lakehouse data in one query#
Rule: join across a mirrored table, a warehouse, and a lakehouse SQL endpoint in a single T-SQL statement rather than staging a copy somewhere first.
Why: because every one of those item types ultimately exposes its data through OneLake, three-part naming lets a single query reach across all of them without any intermediate ETL step [S1].
-- One statement, three item types, zero data movement:
-- a mirrored operational table joined to a warehouse fact table.
SELECT o.OrderId, o.Status, f.RevenueAmount
FROM MirroredSalesDb.dbo.Orders AS o
JOIN SalesWarehouse.dbo.FactRevenue AS f
ON o.OrderId = f.OrderId
WHERE o.Status = 'Shipped';
Share live data across tenants with metadata mirroring, not exports#
Rule: when an external organization needs an up-to-date view of a dataset, wire up metadata mirroring paired with OneLake's external sharing feature instead of handing over periodic file exports.
Why: metadata mirroring can stand up a read-only shortcut inside the partner tenant, so both sides query the identical live data with nothing duplicated and no extra pipeline to maintain [S1].
Read the Delta files directly with Spark when every second counts#
Rule: for workloads that need the absolute freshest data, have Spark read the underlying Delta table rather than routing through the SQL analytics endpoint.
Why: the SQL analytics endpoint layers its own sync step on top of the base replication, adding roughly half a minute to a minute of extra lag; skipping straight to the Delta files avoids that added delay entirely [S4].
# Bypass the SQL analytics endpoint's ~30-60s extra sync delay
# by reading the Delta table directly.
df = spark.read.format("delta").load(
"abfss://<workspace>@onelake.dfs.fabric.microsoft.com/"
"<mirrored-db>.MirroredDatabase/Files/Orders"
)
Own mirrored items with a service principal, never a person#
Rule: provision every mirrored database under a service principal or shared service account from day one — don't let it get created under someone's individual sign-in.
Why: Fabric ties a mirrored database permanently to whichever identity created it, and that link can't be reassigned later; if the original creator's account is deactivated when they leave, the only fix is deleting the item and standing up a replacement from scratch [S4].
# Bad: created under a personal account
Created by: alice@contoso.com # leaves the company -> item is orphaned
# Good: created under a service principal
Created by: svc-mirroring-prod@contoso.com # survives staff turnover
Turn on Workspace Monitoring before an incident forces the question#
Rule: enable Workspace Monitoring on any mirrored database heading into production.
Why: once switched on, it writes structured replication telemetry into an Eventhouse KQL table called MirroredDatabaseTableExecution — capturing how much data moved, how long each batch took, when it happened, and any errors along the way — but nobody gets that visibility unless they turn it on, and doing so brings normal Eventhouse charges with it [S4].
// Check replication health per table once monitoring is on
MirroredDatabaseTableExecution
| where Timestamp > ago(1h)
| summarize RowsReplicated = sum(RowCount), AvgLatencyMs = avg(BatchLatencyMs)
by TableName
| order by AvgLatencyMs desc
Land mirrored tables read-only and curate in a separate transform layer#
Rule: treat raw mirrored tables as an untouched landing zone — apply joins, filtering, and column selection in a distinct transformation layer, and expose only the curated result to BI consumers.
Why: one practitioner walkthrough of Mirroring recommends this layering specifically because it keeps the raw replica trustworthy as a source of truth while giving report authors a stable, business-friendly surface to build against, rather than letting every consumer reach into raw replicated tables directly [S5].
# Layering pattern (one practitioner's convention)
MirroredSalesDb (raw, read-only landing zone — do not modify)
-> silver_transform (notebook or Dataflow Gen2: joins, filters, renames)
-> gold_curated (BI-facing tables; the only layer analysts query)
Reconcile mirrored tables against the source instead of trusting the feed blindly#
Rule: periodically run a row-count comparison and an EXCEPT-based key comparison between a mirrored table and its source, rather than assuming the change feed captured everything.
Why: the same practitioner guide recommends this because CDC-style replication can silently miss edge cases (see the schema-evolution and permission-failure gotchas below), and a reconciliation query is a cheap way to catch drift before it reaches a dashboard [S5].
-- Row-count check
SELECT COUNT(*) AS mirrored_rows FROM MirroredSalesDb.dbo.Orders;
-- (compare against a row count pulled from the source system)
-- Key comparison: rows in source but missing from the mirror
SELECT src.OrderId
FROM SourceOrders AS src
EXCEPT
SELECT OrderId FROM MirroredSalesDb.dbo.Orders;
Scope access by role, not by convenience#
Rule: restrict who can configure mirroring, who can read raw mirrored tables, and who can read curated tables into three separate tiers.
Why: one practitioner's least-privilege convention limits mirroring configuration to workspace admins, limits raw mirrored-table access to the data engineering team, and gives business analysts reader access only to the curated tables downstream of the transform layer — keeping the blast radius of a bad config change or a leaked credential small [S5].
The layering, reconciliation, and access-scoping practices above are a single practitioner's documented convention rather than a Microsoft-published prescription — treat them as a solid starting pattern to adapt, not a platform requirement.
What goes wrong#
Access controls do not travel with the data. Row-level rules, column-level restrictions, and masking that exist on the source database have no equivalent inside the mirrored copy — every one of those protections has to be recreated from the ground up in Fabric before mirrored tables reach end users [S4].
A handful of other traps show up repeatedly once teams push Mirroring into production:
- PostgreSQL quietly leaves columns out. Rather than refusing to replicate a table with a column type it doesn't understand — geometric, network-address, range, JSON/JSONB, XML, INTERVAL types among them — the PostgreSQL connector just omits those columns and keeps going, so a query against the mirror can return rows that look complete but are actually missing fields, with nothing flagging the gap [S4].
- An underspecified Oracle NUMBER column stops an entire table cold. If an Oracle
NUMBERfield has no explicit precision and scale defined, replication for that whole table fails with anInvalidDecimalPrecisionerror; the actual fix isn't to touch the source schema but to update the Oracle Client for Microsoft Tools (the ODP driver) on the On-Premises Data Gateway [S4]. - Delete tracking trips up some Python readers. Mirroring marks deleted rows using Delta's deletion-vector mechanism instead of rewriting files, and not every reader handles that gracefully: the Polars library's
delta-rsbackend throws aDeltaProtocolErrorwhen it encounters one, while both Apache Spark and DuckDB (version 1.2 onward) read them without issue [S4]. Check reader compatibility before building a Python-based consumer against a mirrored table. - Locking down public internet access breaks most connectors. Turning on the tenant-wide Block Public Internet Access setting knocks most native database connectors offline — they simply stop replicating. Only a short list keeps working under that restriction: Open Mirroring, Cosmos DB, Azure SQL Managed Instance, and SQL Server 2025 [S4].
- Promoting through deployment pipelines doesn't start replication for you. A mirrored database item that arrives in a new workspace via a Fabric deployment pipeline shows up configured but completely empty — someone has to manually kick off replication, which then runs a full initial load from scratch [S4]. Build that trigger step into the release runbook rather than discovering it during a go-live.
- A failed first snapshot doesn't resume — it restarts. One practitioner walkthrough reports that if the initial snapshot fails partway through because of a permission error, the partial data already written to OneLake is left in place and the whole snapshot has to run again from scratch; the recommended defense is validating connectivity and service-account permissions before ever kicking off that first snapshot [S5].
- Renamed columns aren't recognized as renames. According to the same walkthrough, schema-change handling varies by change type: newly added source columns propagate automatically with NULLs backfilled into historical rows, and dropped columns are retained in the mirrored table rather than deleted outright — but a column rename isn't detected as a rename at all. It's instead treated as dropping the old column and adding a new one, which means any downstream query or model referencing the old column name breaks silently and needs manual coordination [S5].
- Native row filtering isn't a connector option. That same source reports that Mirroring has no built-in way to apply a WHERE-clause filter at the connector level — if a team only wants a subset of rows mirrored, the filtering has to happen afterward, in a downstream transformation notebook or Dataflow Gen2, not at the point of replication [S5].
- Wide string columns can carry a hidden performance cost. One practitioner note observes that a SQL Server
NVARCHAR(MAX)column maps to a SparkSTRINGtype once mirrored — which works for most workloads, but is worth watching on very wide string columns where it can affect query performance downstream [S5].
The last four points above come from a single tier-6 practitioner tutorial rather than Microsoft documentation or a higher-tier source — treat them as one team's field-reported experience worth validating against your own mirrored tables, not a guaranteed platform behavior.
Internals#
Architecture & design#
Mirroring occupies the space between an external database and OneLake, acting as a managed layer that neither the source system nor the consuming Fabric engines have to think about directly. For database mirroring and open mirroring, the flow runs through an intermediate landing zone: a replicator process checks that landing zone frequently and immediately folds each freshly-arrived batch of incremental changes into the destination Delta table [S1]. One practitioner tutorial describes this in terms of two distinct phases: an initial snapshot that captures a consistent baseline of the source database into Delta tables, followed by continuous application of the source's change log — inserts, updates, and deletes — in strict transaction order [S5]. Metadata mirroring bypasses that entire mechanism — it never stages or moves a single row, instead wiring a OneLake shortcut straight to where the source data already lives, which means its responsiveness depends on how quickly the source and the shortcut layer answer, not on any merge cycle [S1].
Because database and open mirroring both ultimately produce a standard Delta table sitting in OneLake, nothing downstream needs a special code path to reach it — Power BI through Direct Lake, Spark, the SQL analytics endpoint, warehouses, and Eventhouse/KQL all read the identical physical files [S1]. That shared-storage design is also the reason a T-SQL query can join a mirrored table to a warehouse table without any data being copied between them first [S1]. It's also why row-level change data from a mirrored database, with Delta Change Data Feed and Extended Capabilities turned on, can be streamed directly into an Eventstream and discovered through the Real-Time Hub — one production-stability review describes exactly this path as a way to bridge mirrored operational data into Fabric's real-time intelligence tooling [S8].
How it works internally#
Under the hood, the replication engine runs a tight cycle of checking the landing zone and immediately folding whatever it finds into the target table; under favorable conditions — steady change volume, healthy network — that whole loop can carry a change from the source into a queryable OneLake table in as little as fifteen seconds [S1]. That number describes a best case, not a service guarantee.
Rather than polling at a fixed interval no matter what, the engine adjusts its own pace: when the source goes quiet, it backs off and checks less often, easing the load it places on the source database, then speeds back up automatically once it detects change volume picking up again [S1]. Inference: one side effect of that design is that a source that goes bursty right after a quiet stretch may see a brief lag spike while the engine's polling frequency is still ramping back up, followed by it catching up quickly once sustained activity is detected.
The SQL analytics endpoint doesn't hand queries straight through to the raw Delta files underneath — it keeps its own metadata bookkeeping in between, and that extra layer tacks on roughly 30 to 60 seconds beyond whatever the base replication lag already is [S4]. Querying the Delta layer directly through Spark skips that endpoint-specific overhead entirely [S4].
Row deletions are tracked using Delta Lake's deletion-vector approach rather than by physically rewriting the affected Parquet files [S4]. Since that's a property of the Delta protocol itself, whether a given tool can correctly read a mirrored table's deletes comes down to that tool's own Delta support, not anything configurable on the Fabric side — Spark and DuckDB 1.2+ both handle deletion vectors correctly, while Polars' delta-rs reader currently does not [S4].
Schema changes at the source propagate into the mirror unevenly depending on the type of change, per one practitioner's field observations: an added column shows up automatically with NULL backfilled for every historical row already replicated; a dropped source column is retained rather than removed from the mirrored table, so old data doesn't silently vanish; but a rename is invisible as a rename to the replication engine — it's processed as a drop of the old column paired with an add of a new one, which means any downstream consumer keyed to the old column name has to be manually updated [S5].
Mirroring also keeps its own Delta tables tidy by running vacuum automatically, clearing out files the Delta transaction log no longer references. New databases (created since mid-June 2025) default to a one-day retention window before vacuum, while databases from before that point default to seven days; either figure can be changed through the Fabric portal or the public REST API [S1]. That retention setting effectively caps how far back a time-travel query against a mirrored table can reach — tightening it saves storage, loosening it preserves a longer queryable history.
Performance characteristics#
Coming soon — this depth isn't in the knowledge base yet. It needs an L4/L5 source such as a Microsoft engineering blog, conference talk, or internals deep-dive documenting the mirroring replication engine's throughput ceilings, scaling behavior across large numbers of concurrently mirrored tables, or benchmark data beyond the single "as little as 15 seconds" latency figure currently available. Tracked in content/queue.md.
Worked example: mirroring Azure SQL Database into a Power BI dashboard#
A team runs its order-management system on Azure SQL Database and wants a Power BI dashboard that reflects order status close to real time, without maintaining an Import-mode refresh schedule.
1. Stand up the mirrored database item. From the Fabric portal, create a new Mirrored Database, choose Azure SQL Database as the source type, and supply the connection details. Inference: those credentials should be issued to a service principal rather than a person's own account, so the item doesn't become orphaned the day that person's access changes [S4].
2. Review the schema before turning on replication. Certain source column types get silently excluded depending on which connector is in use [S4] — this matters especially for PostgreSQL and Oracle sources, so it's worth checking the table definitions for unsupported types before flipping mirroring on rather than after. Since one practitioner walkthrough reports that a failed first snapshot restarts from scratch rather than resuming, it's also worth confirming the service account's permissions against the source ahead of time rather than discovering a permission gap mid-snapshot [S5].
3. Watch the first sync land. The initial load reads the whole table and can take anywhere from minutes to hours depending on size. Turning on Workspace Monitoring ahead of time means the MirroredDatabaseTableExecution table in Eventhouse captures that initial load and every subsequent replication batch [S4].
MirroredDatabaseTableExecution
| where TableName == "Orders"
| order by Timestamp desc
| take 20
4. Wire the semantic model to Direct Lake. Set the Power BI semantic model's storage mode to Direct Lake against the mirrored item. Expect the dashboard to reflect source changes within roughly fifteen seconds under good conditions [S1], with another 30 to 60 seconds added on top if the model queries through the SQL analytics endpoint instead of reading Delta directly [S4].
5. Recreate access controls at the Fabric layer. None of the source database's row-level security, column masking, or similar protections carry over automatically [S4], so workspace permissions and, where needed, model-level row-level security need to be built fresh before the report goes to end users.
6. Add a reconciliation check. Before calling the dashboard trustworthy, run a row-count and key-comparison check between the mirrored Orders table and the source, following the pattern one practitioner guide recommends for catching any rows the change feed might have missed [S5].
7. Plan for deployment pipelines. If this item later moves from dev to test to production through a Fabric deployment pipeline, add an explicit "start replication" step to the release checklist — the promoted item lands empty, and nothing kicks off automatically [S4].
Steps 1 and 5 describe a recommended process built by combining several claims above, not a verbatim platform walkthrough — the actual portal screens may look different depending on the current release.