Domain 3 · 30–35%

Monitor and optimize an analytics solution

The operations third of the exam: knowing where to look when something breaks, reading the error message correctly, and applying the right lever — not just the biggest one — when something is slow.

Three objectives:

3.1 Monitor Fabric items

Know your monitoring surfaces

Half of this objective is simply picking the right tool. Four surfaces, four different questions:

SurfaceAnswersScope
Monitoring hub (Monitor)"Did it run, how long did it take, why did it fail?"All runs across items you can access — pipelines, notebooks, Spark, dataflows, refreshes
Capacity Metrics app"What is burning CUs? Are we throttled?"Capacity-wide consumption, overloads, carryforward
Workspace monitoring"Give me queryable logs and metrics I can build alerts on"Per-workspace diagnostic logs landed into an Eventhouse, queried with KQL
Purview audit log"Who accessed, shared or exported this?"Tenant-wide security/compliance events

Workspace monitoring

When enabled on a workspace, Fabric provisions a monitoring Eventhouse and streams diagnostic logs and metrics into it. Because the data lands in a KQL database, you can query it with KQL, build real-time dashboards on it, and attach Activator rules — which is how you get genuinely custom alerting rather than the built-in per-item notifications.

Monitoring data ingestion

WhatWhereSignals that matter
Pipeline Copy activityMonitoring hub → run → activity outputrowsRead, rowsCopied, rowsSkipped, throughput, duration, used DIUs, parallel copies
Copy jobThe Copy job item's run historyRows moved, incremental watermark position
Dataflow Gen2Refresh history on the itemPer-query duration and row counts, staging vs destination phases
MirroringThe mirrored database's replication viewReplication lag, rows replicated, per-table status, stopped/errored tables
EventstreamEventstream runtime view / data insightsIncoming vs outgoing events, backlog, errors per destination

Caveat — read rows read against rows written

A Copy activity that succeeds can still be wrong. If rowsReadrowsCopied, rows were filtered, skipped by fault tolerance, or dropped by type incompatibility. When a question says "the pipeline succeeded but the target has fewer rows than expected", check rowsSkipped and the fault-tolerance log — do not chase permissions.

Monitoring data transformation

SurfaceShows
Monitoring hub → Spark runDuration, status, submitter, attached Lakehouse, executor allocation
Spark UI — JobsJobs and their stages; where the wall-clock time went
Spark UI — StagesTask duration distribution (max vs median exposes skew), shuffle read/write, spill
Spark UI — SQL/DataFrameThe physical plan: join strategies, scans, exchanges, AQE rewrites
Spark UI — ExecutorsPer-executor memory, disk, GC time, failed tasks
Spark UI — StorageCached DataFrames and how much actually fit in memory
Query insights (Warehouse)Historical T-SQL query performance — see Warehouse optimization

Reading the Stages tab for skew

In a healthy stage, task durations cluster tightly. If the max task duration is far above the median and one task processes far more input, you have data skew — a few keys dominate a partition. That is a different fix from "the whole job is slow", so the exam uses this signal to separate skew answers from cluster-size answers.

Monitoring semantic model refresh

Caveat — refresh a Direct Lake model?

Direct Lake does not import data, so "schedule a refresh to load the new rows" is the wrong answer for it — new Delta commits are picked up by reframing. If a Direct Lake report suddenly gets slower with no error, suspect DirectQuery fallback caused by exceeding a capacity guardrail, or an unoptimized (small-file-heavy) Delta table underneath.

Configuring alerts

Alert typeSet up whereFires on
Activator ruleActivator item, or "Set alert" on a real-time dashboard / Power BI visual / EventstreamA data condition — value crosses a threshold, changes, or stays in a state
Job failure notificationPipeline settings → notification activity, or item settingsRun failure or completion — email or Teams
Refresh failure notificationSemantic model / dataflow settingsRefresh failure, to owner + contacts
Capacity notificationAdmin portal → capacity settingsCapacity approaching or exceeding its limit
KQL-based alertActivator on a KQL queryset / workspace monitoring EventhouseAny condition you can express in KQL

Activator in one line

Activator (previously Data Activator / Reflex) is Fabric's no-code detect-and-act engine: it watches a stream, a KQL query or a report visual, evaluates rules on objects, and triggers an action — email, Teams message, or run a Fabric item such as a pipeline or notebook. When a question asks for "automatically start the pipeline when X happens in the data", Activator is the mechanism.

3.2 Identify and resolve errors

flowchart TD F["Run failed"] --> H["Monitoring hub
find the failed run and activity"] H --> C{"Read the error class"} C -->|"Auth / credentials"| A["Refresh connection
check service principal, expired secret"] C -->|"Network / gateway"| N["Gateway offline or unreachable
firewall, private endpoint"] C -->|"Schema / type"| S["Mapping mismatch
fix mapping or cast explicitly"] C -->|"Timeout"| T["Raise timeout, add parallelism
reduce batch size"] C -->|"Capacity / throttling"| K["Capacity Metrics app
scale, reschedule, reduce concurrency"] C -->|"Permission"| P["Workspace role, item permission
source-side IAM"]
Triage flow: always start in the monitoring hub, classify the error, then act on the class rather than guessing.

Pipeline errors

Symptom / errorCauseFix
Invalid data source credentialsExpired secret, rotated password, deleted service principalUpdate the connection; prefer workspace identity or managed identity over stored secrets
Copy activity failed — type mismatchSource and sink column types incompatibleFix the explicit column mapping, cast in the source query, or enable fault tolerance to skip bad rows
Activity timed outLong-running source query or oversized batchRaise the activity timeout, filter the source, increase parallel copies
Gateway unreachable / offlineOn-premises data gateway service stopped or blockedRestart the gateway, verify outbound connectivity and cluster health
Capacity throttled / rejectedCU exhaustion on the capacityCapacity Metrics app → scale up, move the workspace, or reschedule heavy jobs
Sink table locked / concurrent writeTwo writers targeting the same Delta tableSerialize the writes; Delta's optimistic concurrency will fail one of them
Expression evaluation failedNull or wrong-typed value in a dynamic expressionGuard with coalesce()/if(); check the upstream activity's actual output shape

Design pipelines to fail well

Use activity dependency conditions (Success / Failure / Completion / Skipped) to build error paths, set retry count and interval on transient-prone activities, and add a Fail activity with a clear message so downstream monitoring shows the business reason rather than a generic error.

Dataflow Gen2 errors

SymptomCauseFix
Out of memory / resource exceededHeavy transformation evaluated in the mashup engineEnable staging so the work is pushed down; split the dataflow; reduce the data pulled
Evaluation timeoutToo many steps, or query folding brokenRestore query folding — move filters earlier, avoid steps that break folding
Data source error on refreshCredentials expired or source unavailableRe-authenticate the connection; check gateway for on-prem sources
Destination write failedTarget missing, no permission, or schema conflictVerify the destination exists, check the update method (append vs replace) and column mapping
Duplicate column namesCollision after a merge/expandRename before combining

Caveat — query folding is the real lever

When a Dataflow Gen2 folds, the transformation is translated into a query the source executes; when folding breaks, everything is pulled into the mashup engine and processed locally — which is where memory and timeout failures come from. Filters and column removal placed early usually fold; custom functions, some type changes and index columns usually break it. "Dataflow is slow / runs out of memory" → staging plus restoring folding, not a bigger capacity.

Notebook and Spark errors

ErrorCauseFix
OutOfMemoryError (executor)Partition too large, skew, or a broadcast that was too bigRepartition, fix skew, lower the broadcast threshold, larger node size
OutOfMemoryError (driver)collect() / toPandas() pulling a huge result to the driverAggregate first, or write to a table instead of collecting
AnalysisException: Table or view not foundNo default Lakehouse attached, wrong name, or endpoint metadata lagAttach the Lakehouse, use a fully qualified name or ABFSS path
ConcurrentAppendException / ConcurrentModificationTwo jobs writing the same Delta tableSerialize writers, or partition so they touch disjoint files
FileNotFoundException on readFiles removed by VACUUM while a long query or stale snapshot referenced themRe-read the table; do not vacuum below the retention window
Session expired / timed outIdle session reclaimedRe-run; adjust session timeout; use high concurrency mode
ModuleNotFoundErrorLibrary not installed in the environmentAdd it to the Environment (persistent) or %pip install (session-only)
Py4JJavaErrorWrapper around a JVM-side failureRead the nested stack trace and the Spark UI — the real cause is inside

%pip install is not persistent

%pip install lasts only for the current session. For a library every run needs — and for reproducibility across notebooks and pipeline-triggered runs — add it to an Environment and attach that environment to the notebook or workspace.

Eventhouse errors

SymptomCauseFix
Ingestion failuresSchema or format mismatch between events and the tableCheck the ingestion mapping; inspect .show ingestion failures
Data arrives late (minutes)Default batching ingestion policy aggregating small batchesTune the batching policy or enable streaming ingestion for low latency
Query timeout / memory errorUnbounded scan, expensive join, missing time filterFilter on the time column first, reduce the range, pre-aggregate with a materialized view
Old data missingRetention policy soft-deleted itExtend retention — data past retention is gone, not merely uncached
Historical queries slowData outside the hot cache windowExtend the caching policy for the period you query

Caveat — caching policy vs retention policy

Retention decides how long data exists. Caching (hot cache) decides how much of it is held on fast local SSD. Data past its cache window is still queryable, just slower; data past its retention window is deleted. "Queries on last year's data are slow" → extend caching. "Last year's data is gone" → retention was too short. Hot cache should always be ≤ retention.

Eventstream errors

SymptomCauseFix
Source connection failureBad connection string, expired SAS, wrong consumer groupRe-create the connection; give the Eventstream its own consumer group
Deserialization errorsEvents don't match the declared format/schemaCorrect the data format (JSON/Avro/CSV) and the schema
Backlog / input lag growingIngress exceeds processing throughputSimplify the processing graph, increase source partitions, reduce destination fan-out
Destination write failureTarget unavailable, permission denied, or schema conflictCheck destination status and permissions; validate the schema mapping
Events missing downstreamA filter operator dropping more than intendedInspect the processor preview at each operator

Consumer groups

Two readers sharing one Event Hubs consumer group compete for the same partitions and each sees only part of the data. Every independent consumer — each Eventstream, each Spark job — should have its own consumer group. "Some events are missing and another job started recently" is this.

T-SQL errors

ErrorCauseFix
Invalid object nameWrong schema, wrong database, or endpoint metadata not yet syncedQualify as database.schema.object; refresh the SQL endpoint metadata
Cannot INSERT/UPDATE — read-onlyWriting through a Lakehouse SQL analytics endpointWrite with Spark, or move the table to a Warehouse
Permission denied on object/columnMissing GRANT, or an explicit DENY from CLSGrant the needed permission; remember DENY always wins
Conversion failedImplicit conversion of incompatible typesExplicit CAST/TRY_CAST; fix upstream typing
Unsupported feature / syntaxFabric Warehouse T-SQL is a subset — identity columns, some constraints, MERGE nuances, triggersUse a supported pattern (CTAS, explicit key generation)
Query rejected / long queueCapacity throttlingCapacity Metrics app; reduce concurrency or scale

Caveat — constraints are not enforced

Fabric Warehouse supports PRIMARY KEY / UNIQUE / FOREIGN KEY only as NOT ENFORCED metadata. They inform the optimizer but do not stop you inserting duplicates or orphans. If a question asks how to guarantee uniqueness, the answer is to enforce it in the load logic (MERGE on the key, dedupe before insert), not to add a constraint.

OneLake shortcut errors

SymptomCauseFix
Access denied through the shortcutSource-side credentials expired, or the caller lacks source permission — both layers applyUpdate the connection; grant access at the source (S3 IAM, ADLS RBAC/ACL)
Target not found / broken shortcutSource path moved, renamed or deletedRe-create the shortcut against the current path
Shortcut appears but has no tablesTarget isn't a valid Delta folder, or was created under Files/ instead of Tables/Point at a proper Delta table folder under Tables/
Queries are slowCross-service/cross-cloud latency, or chained shortcutsUse query acceleration (RTI), mirror the data, or copy it locally
Stale or unexpected schemaSource schema changed underneathRefresh metadata; re-create the shortcut
Blocked from external toolsOneLake external-access setting disabledRe-enable at tenant/workspace level (see Domain 1)

3.3 Optimize performance

Optimizing a Lakehouse table

flowchart TD S([Lakehouse table is slow]) --> Q1{Symptom} Q1 -->|"Thousands of tiny files"| O["OPTIMIZE
compact"] Q1 -->|"Filtered queries scan everything"| Z["OPTIMIZE ZORDER BY
on the filter columns"] Q1 -->|"Storage cost from old versions"| V["VACUUM
respect 7-day retention"] Q1 -->|"Reads slow across the board"| VO["V-Order enabled
plus OPTIMIZE"] Q1 -->|"Huge table, always filtered by date"| P["Partition by a
low-cardinality column"]
Match the maintenance operation to the symptom — they are not interchangeable.

Optimizing a pipeline

LeverHowWhen it helps
Copy lessIncremental instead of full; project only needed columns; filter at the sourceAlways the biggest win
Parallel copyRaise degree of copy parallelism; partition the source readLarge single-table copies
DIUsIncrease Data Integration Units on the Copy activityThroughput-bound copies
Staged copyEnable stagingCross-region moves, or sinks that need bulk load
ForEach parallelismTurn off isSequential, tune the batch countMany independent tables — but watch capacity
Flatten dependenciesRun independent branches concurrently instead of chainingLong pipelines with false sequencing
Right activityPush a set-based transform into a stored procedure or notebook instead of row-wise pipeline logicPipelines used as a transformation engine

Parallelism is not free

Raising ForEach batch counts and copy parallelism consumes more CUs at once and can push the capacity into throttling, making everything slower. If the capacity is already near its limit, the fix is scheduling and scope reduction — not more concurrency.

Optimizing a data warehouse

TechniqueDetail
StatisticsFabric auto-creates and maintains them, but you can CREATE STATISTICS / UPDATE STATISTICS manually when plans look wrong after a big load
Correct data typesAvoid oversized varchar(max)-style columns — they inflate memory grants and slow everything
CTASPreferred for large transforms; minimally logged compared with row-by-row INSERT
Avoid SELECT *Columnar storage means unread columns cost nothing — until you ask for them
Result set cachingRepeated identical queries served from cache
Materialized viewsPre-compute expensive aggregations and keep them current automatically
Star schemaThe engine is optimized for it; deeply normalized snowflakes join poorly at scale
NOT ENFORCED keysDeclaring PK/FK (not enforced) still gives the optimizer useful cardinality information
T-SQL · query insights
-- Historical query performance (queryinsights schema)
SELECT TOP 20 *
FROM   queryinsights.long_running_queries
ORDER  BY median_total_elapsed_time_ms DESC;

SELECT TOP 20 *
FROM   queryinsights.frequently_run_queries
ORDER  BY number_of_runs DESC;

SELECT command, submit_time, total_elapsed_time_ms, status
FROM   queryinsights.exec_requests_history
ORDER  BY total_elapsed_time_ms DESC;

-- Currently executing requests (DMV)
SELECT * FROM sys.dm_exec_requests WHERE status <> 'Completed';

Know the three query insights views

exec_requests_history = every request. long_running_queries = the slow ones, aggregated by query shape. frequently_run_queries = the ones worth optimizing because they run constantly. Pair them: the best optimization target is usually frequently run and moderately slow, not the single slowest ad-hoc query.

Optimizing Eventstreams and Eventhouses

TargetLeverEffect
EventstreamSource partitioning / partition keyMore parallel processing; pick a key with even distribution
Simplify the processor graphFewer operators and destinations reduce per-event cost
Trim the payloadDrop unused fields early with "manage fields"
EventhouseCaching (hot) policyKeeps the queried window on fast storage — the main query-latency lever
Retention policyControls storage cost and how far back data exists
Materialized viewsMaintain aggregates incrementally so dashboards don't re-scan raw data
Update policiesTransform/route at ingestion time into a curated table (ETL at ingest)
Partitioning & batching policiesTune extent shape and ingestion batching for throughput vs latency
KQL · policies and materialized views
// Hot cache: keep 30 days on fast storage
.alter table SensorReadings policy caching hot = 30d

// Retention: keep data for one year
.alter table SensorReadings policy retention softdelete = 365d

// Pre-aggregate hourly so dashboards never scan raw events
.create materialized-view HourlySensorAgg on table SensorReadings
{
    SensorReadings
    | summarize AvgTemp = avg(Temperature), MaxTemp = max(Temperature)
        by bin(Timestamp, 1h), DeviceId
}

// Inspect physical layout
.show table SensorReadings extents
| summarize Extents = count(), Rows = sum(RowCount), Size = sum(OriginalSize)

Optimizing Spark performance

flowchart TD S([Spark job is slow]) --> UI["Diagnose in the Spark UI"] UI -->|"Max task time much greater than median"| SK["Data skew
salt the key · AQE skew join · repartition"] UI -->|"Large shuffle read/write"| SH["Reduce shuffle
broadcast the small side · filter earlier"] UI -->|"Spill to disk"| SP["Memory pressure
more partitions · larger nodes · avoid huge broadcast"] UI -->|"Long scan, few rows used"| SC["Prune
predicate pushdown · partitions · Z-Order"] UI -->|"Thousands of tiny input files"| SF["OPTIMIZE the table
optimize write on ingest"] UI -->|"Minutes before the first task"| CS["Cold start
use a starter pool"]
Every Spark answer starts from a symptom in the Spark UI — the exam gives you that symptom in the stem.
TechniqueHowUse when
Broadcast joinbroadcast(small_df) or autoBroadcastJoinThresholdSmall dimension joined to a large fact — avoids a shuffle entirely
Adaptive Query ExecutionOn by default in FabricRuntime re-planning: coalesces shuffle partitions, converts to broadcast, splits skewed joins
SaltingAdd a random suffix to the hot key, join, then aggregate awaySevere skew AQE can't resolve
Predicate pushdown / column pruningFilter and select earlyAlways — least data read is the cheapest optimization
coalesce vs repartitioncoalesce(n) reduces partitions without a full shuffle; repartition(n) shuffles for even distributioncoalesce before write; repartition to fix imbalance
cache / persistdf.cache()A DataFrame reused several times — not for single-pass work
Native execution engineEnable in Spark workspace settingsBroad speed-up; unsupported operators fall back silently
Avoid Python UDFsUse built-in functions, or pandas/Arrow UDFs if unavoidableRow-at-a-time Python UDFs break Catalyst optimization and serialize every row
High concurrency modeShare sessions across notebooksMany users/small jobs paying repeated start-up cost
PySpark · common optimizations
from pyspark.sql.functions import broadcast, col

# Broadcast the small side to avoid a shuffle join
result = fact_df.join(broadcast(dim_df), "customer_id")

# Filter before joining — pushdown and less shuffle
recent = fact_df.filter(col("order_date") >= "2026-01-01")

# Reduce output files without a full shuffle
result.coalesce(8).write.format("delta").mode("overwrite").saveAsTable("gold_orders")

# Cache only when the DataFrame is genuinely reused
base = recent.filter(col("region") == "EU").cache()
total, distinct_customers = base.count(), base.select("customer_id").distinct().count()

Caveat — the three Spark traps

1. coalesce() only reduces partitions and avoids a full shuffle; it cannot rebalance skew. Use repartition() when distribution matters.
2. Broadcasting a table that is too large moves the OOM from the executors to the driver. Broadcast the genuinely small side only.
3. AQE is already on — answers proposing to manually tune spark.sql.shuffle.partitions or hand-fix skew are usually distractors, because AQE coalesces partitions and splits skewed joins at runtime.

Optimizing query performance

PrincipleApplies to
Read less — filter early, select only needed columnsEvery engine
Enable pruning — partition elimination, Z-Order, KQL time filtersLakehouse, Warehouse, Eventhouse
Pre-compute — materialized views, aggregate tables, gold layerWarehouse, Eventhouse, Lakehouse
Keep statistics freshWarehouse
Right-size files — OPTIMIZE, avoid over-partitioningLakehouse, Direct Lake models
Join order and strategy — small side broadcast, star schema shapeSpark, Warehouse
Avoid row-by-row logic — set-based over cursors/UDFsWarehouse, Spark

Scenario quick reference

ScenarioDeciding constraintAnswer
Find why last night's pipeline failedRun historyMonitoring hub
Find which item is exhausting the capacityCU consumptionCapacity Metrics app
Prove who exported a datasetSecurity forensicsPurview audit log
Custom alerting on queryable Fabric logsKQL over diagnosticsWorkspace monitoring + Activator
Start a pipeline when a metric crosses a thresholdDetect and actActivator rule
Copy succeeded but rows are missingSilent filteringCompare rowsRead / rowsCopied / rowsSkipped
Dataflow Gen2 out of memoryLocal evaluationEnable staging + restore query folding
Notebook fails on collect()Driver memoryAggregate first / write to a table
Library missing on every scheduled runPersistenceAdd to the Environment, not %pip install
FileNotFoundException after maintenanceRetentionVACUUM removed files still referenced
Cannot INSERT via T-SQLRead-only endpointUse a Warehouse (or write with Spark)
Duplicates despite a primary keyNot enforcedDedupe in the load — constraints are metadata only
KQL queries on old data are slowCache windowExtend the caching policy
Old KQL data has disappearedRetention windowRetention policy too short
Eventstream backlog growingThroughputSimplify processing, add partitions
Some events never arrive at one consumerShared readerGive each consumer its own consumer group
One Spark task runs 20× longer than the restSkewSalt the key / rely on AQE skew join
Slow join, one table is tinyShuffle avoidanceBroadcast join
Thousands of small output filesWrite shapecoalesce() before write + OPTIMIZE
Which Warehouse queries to tune firstImpactqueryinsights.frequently_run_queries
Notebooks take minutes to startCold startStarter pool