Domain 3 — Monitor and optimize an analytics solution¶
Microsoft weights this skill area at 30–35% of the exam, the same as the other two. It is the diagnosis domain: something is slow, something failed, and you have to say what to change. Many questions give you a symptom and ask you to identify the cause, the surface that would show it, or the corrective action — and others simply ask you to choose the right monitoring or optimisation mechanism for a requirement.
These notes keep monitoring first but then put capacity before the per-item error paths, because capacity is the cause that masquerades as every other symptom. A slow notebook, a queued pipeline and a rejected submission can all be one overloaded capacity, and a candidate who does not know the throttling stages will spend the question debugging the item. Optimisation comes last and is organised by what is being optimised — files, then queries, then aggregations — rather than by the guide's per-product list, because the same few mechanisms recur across products.
What this domain actually asks¶
Separate the symptom from the cause. Microsoft says of one Spark error that it is "always a symptom, not the root cause", and that instruction generalises. A failure count, a queue, a slow query: each is a signal pointing somewhere else. The option that fixes the signal is nearly always wrong.
Check the capacity before the item. Throttling is applied at the capacity level and its behaviour is stage-based, so the same job can run fine, run slowly, or be rejected outright depending on nothing but recent consumption elsewhere.
Know which tool answers which layer. Job execution is the Monitor hub. Query execution is query insights. Executor failure is the Spark interface. Reaching for the wrong surface is how a question gets lost before the options are read.
flowchart TD
A["Slow or failed?"] -->|Rejected| B["Capacity"]
A -->|Slow| C["Which engine?"]
C -->|Spark| D["Exit code"]
C -->|SQL| E["Statistics"]
Where to look¶
Two surfaces, two layers.
| Monitor hub | Query insights | |
|---|---|---|
| Layer | Job execution across items | SQL query execution |
| Scope | Items you have permission to view | Warehouse or SQL analytics endpoint |
| Answers | Did it run, where did it fail | Which query was slow, and why |
The Monitor hub, opened from Monitor in the navigation pane, is where runs across items are tracked. Any Fabric user can open it, but sees only activities for items they have permission to view — so a colleague who cannot find a failed run has an item-permission problem rather than a monitoring one. In its classic view you sort and filter runs by status, item type, start time, submitter or location.
Currency note. Microsoft now calls the classic view the legacy Monitoring hub, and a redesigned Monitor hub with pages for job runs, alerts, capacity, applications and agents is in preview. This does not change the exam answer. "Monitoring hub" in a question means this place; pick it for job execution whichever name the options use.
Query insights holds 30 days of historic query data for a Warehouse or SQL analytics endpoint.
Two boundaries matter. It covers queries run in a user's context only — system queries are not
stored — and complete query text is visible to the Admin, Member and Contributor roles, so a Viewer
sees the insight without the text. Its aggregations group by query shape, which is why two
queries differing only in a literal value are reported as one. For the health of the warehouse
itself rather than of a query, the sql_pool_insights view gives pool-level metrics and pressure
indicators.
Watching the things that run¶
A Dataflow Gen2 refresh runs on demand or on a schedule, and the schedule allows at most 48 runs a day — a floor of half an hour, much coarser than the one-minute floor of the job scheduler. Do not confuse that with the separate cap on how many refreshes a dataflow may actually perform: 300 in a rolling 24 hours, or 150 for a Dataflow Gen2 without continuous integration (CI) and continuous deployment (CD) support, counting on-demand and publish-triggered runs as well as scheduled ones. Past it an error may appear in the refresh history and refreshes resume once usage drops, and a sudden burst can be throttled and rejected even below the cap. The one a cadence question turns on is the schedule ceiling. It is also stricter about who: the user who triggers a refresh needs Member or higher on the workspace plus access to every connection the dataflow uses, so a Contributor who can edit the dataflow may still be unable to refresh it.
Two things start a refresh without anyone scheduling or clicking one: a successful publish, and a pipeline containing a dataflow activity. That explains most unexplained runs in a refresh history.
One rule explains most unexplained slowness. If even one query in a dataflow is configured to use a gateway, every query in that dataflow uses that gateway for data movement. A single on-premises source therefore drags the whole dataflow through it, and nothing in the other queries shows why.
A refresh can be cancelled mid-run, and Microsoft frames cancellation as a capacity remedy — stop a refresh during peak time, or when a capacity is nearing its limits. Which is the next section but one.
Semantic model refresh¶
Refreshes have their own admin surface. The refresh summary page shows a capacity's refresh history, exports it, and exists to investigate refresh errors and work out a sensible refresh schedule for the Power BI items on that capacity. It has two tabs and they differ in width.
| Tab | Shows |
|---|---|
| Schedule | Refreshes for one capacity, chosen from a dropdown |
| History | Refreshes across every capacity you administer |
Both are capacity-scoped admin surfaces, which is what separates them from the per-item refresh history an item's owner sees.
Configuring alerts¶
Five routes, and they watch different things. Choosing the wrong one is a common way to answer a monitoring question incorrectly while naming a real feature.
| Route | Watches | Set up |
|---|---|---|
| Outlook or Teams activity | One activity's outcome | As an activity in the pipeline |
| Pipeline-level alert | The run outcome | On the pipeline |
| Failure notifications | Scheduled run failures | Under the pipeline's Schedule settings |
| Activator rule | The data itself | As a rule on an object |
| Workspace-level alert | Every job run in a workspace | One rule over workspace monitoring |
The activity-level route is the one that surprises people: the alert is an activity, an Outlook or Teams step placed after whatever you want to know about, set to trigger on its failure. Give each monitored activity its own: one connected to several activities fires only when all of them fail. Failure notifications email named users or groups when a scheduled run fails — so a manually triggered failure sends nothing, which is a good distractor.
An Activator rule is different in kind. It watches the data rather than the job, evaluating
conditions continuously on objects. Rules can be simple comparisons, or stateful expressions such as
BECOMES, DECREASES, INCREASES and EXIT RANGE, or the absence of data as a heartbeat.
"Tell me when the load fails" is a notification. "Tell me when nothing has arrived by nine" is a
rule, and none of the first three routes can answer it.
Workspace monitoring is what makes the fifth route possible. It sends logs and metrics from supported items to an Eventhouse in a monitoring item; collection is off until you turn it on and does not backfill, and anyone with Contributor or above can query it with KQL. With it on, a single alert rule over a KQL queryset of recent failures watches every pipeline and job run in the workspace, instead of one alert per pipeline. Real-Time hub adds one more way in: you can set an alert on Fabric workspace item events by choosing the events, the condition and the action Activator takes. The preview Monitor hub gathers failure emails and Activator-based job alerts on one page.
Self-check — where to look
1. A colleague says a pipeline run is missing from the Monitor hub. What do you check? 2. A Viewer can see that a query was slow but not what it was. Is that a fault? 3. A dataflow refresh is slow and none of its queries look expensive. What would you check first? Answers: their permission on the item — the hub shows only items you may view. No; complete query text is available to Admin, Member and Contributor. Whether any one query uses a gateway, because that forces every query in the dataflow through it.Capacity is the cause that looks like everything else¶
Throttling happens when operations consume more capacity unit seconds than the capacity licence allows. Fabric applies it at the capacity level, so one capacity can be degraded while others run normally — which is why moving a workspace to another capacity is a legitimate remedy.
Two features exist so that ordinary spikes do not cause it.
- Bursting lets an operation temporarily use more compute than the capacity licence provides, so a large job finishes fast on a small capacity.
- Smoothing averages that consumption over a longer window, so the burst does not immediately trigger throttling. Interactive operations are smoothed over five to 64 minutes; background operations over 24 hours.
The 24-hour window is why every scheduled job can start at once without spiking — and also why a capacity can be throttled today by work that finished yesterday.
The accounting underneath is finer than either window suggests. Fabric divides time into timepoints of 30 seconds, which puts 2,880 of them in the next 24 hours, and smoothing spreads an operation's consumed capacity units across those future timepoints rather than charging them where the work happened. That is the mechanism to picture when you are trying to predict whether a particular load will tip a capacity over.
Currency note. Fabric does not support bursting and smoothing when a capacity admin has enabled Autoscale Billing for Spark. In that mode Spark usage is pay-as-you-go and neither concept applies. If a scenario mentions autoscale billing, the mechanism this section describes is switched off.
The stages¶
Throttling is progressive, and the order is the part worth memorising.
| Usage borrowed | Stage | What happens |
|---|---|---|
| Up to 10 minutes | Overage protection | Nothing; the job runs |
| 10 to 60 minutes | Interactive delay | User-requested jobs delayed 20 seconds |
| 60 minutes to 24 hours | Interactive rejection | User jobs rejected; background jobs still run |
| Over 24 hours | Background rejection | Everything rejected |
Interactive work degrades first and background work survives longest, by design, so that data refreshes keep running while a capacity recovers. Most people assume the opposite.
That table is the default experience, not a universal rule, and the same page lists workloads that behave differently. An eventstream is not rejected — an operation that is meant to run for years gains nothing from being refused, so Fabric starves it instead, reducing the capacity units keeping the stream open until the capacity recovers. Real-Time Intelligence skips the 20-second delays, because a product whose name promises real time cannot answer by pausing, and its throttling begins at the rejection phase. That does not make it immune in the meantime: an Eventhouse's own throttling log records an Interactive Delay state in the same window, in which responses might be slower because autoscale is disabled. Learn the two shapes rather than a list: a stream is starved, and real-time work skips the 20-second delay.
Currency note. Microsoft states on that page that it might change or update the throttling policy. This does not change the exam answer. The order of the stages is what a question turns on; treat the minute figures as current rather than permanent.
When Spark jobs queue¶
A queued or rejected Spark job has three possible causes, with three different remedies.
| Cause | What it means |
|---|---|
| Maximum compute limits | The capacity licence, or the autoscale ceiling |
| Capacity saturation | Active jobs in your workspace |
| Capacity contention | Other workspaces on the same capacity |
Contention is the one people miss, because it is invisible from the workspace-level view — Fabric
provides a capacity-level view precisely for it. And a submission that hits the compute limit comes
back as HTTP 429 Too many requests, which is the most recognisable symptom in the domain: that
response has already told you the problem is capacity and not code.
Self-check — capacity
1. Interactive queries are being rejected but the overnight pipelines still run. Which stage? 2. A notebook submission returns `HTTP 429`. Where is the problem? 3. A capacity admin enabled autoscale billing for Spark. What stops applying? Answers: interactive rejection — between 60 minutes and 24 hours of borrowed capacity. In the capacity, not the notebook: it has hit a maximum compute limit. Bursting and smoothing both stop applying; Spark usage becomes pay-as-you-go.Reading a Spark failure¶
A MaxExecutorFailures error means Spark aborted the application because too many executor processes
crashed. Microsoft is explicit that this is always a symptom, not the root cause — so an option
that raises the failure threshold is answering the wrong question.
The diagnosis is the executor exit code, read from the Executors tab of the Spark interface.
| Exit code | Meaning | Most likely cause |
|---|---|---|
137 |
Killed by the operating system | Container exceeded its memory limit |
143 |
Terminated | Timeout, preemption, node decommission |
134 |
Aborted | Java virtual machine crash |
1 |
General error | User code exception or misconfiguration |
137 is the one to recognise. It most likely means the data an executor processed exceeded its heap and overhead
together. Microsoft names six common triggers, and each has a different fix — so the exit code
narrows the question rather than answering it.
| Trigger | What it means |
|---|---|
| Data skew | One partition holds far more rows than the others |
| Large partitions | Every partition is too big for the executor |
| Excessive caching | Cached data is competing with working memory |
| Broadcast join against a large table | The "small" side is not small |
| Python user-defined functions | Serialisation overhead per row |
| Insufficient disk for shuffle spill | Nowhere to put what will not fit in memory |
Skew and the oversized broadcast are the two an exam scenario is most likely to describe, because both look like a working design until the data grows.
One failure has no exit code and no bad data behind it. An
INCONSISTENT_BEHAVIOR_CROSS_VERSION error means the same code and the same data now behave
differently after a runtime version change — different results, new failures, or degraded
performance. Fabric runtimes pin their components, so moving between them moves the Apache Spark
version underneath the notebook. This is the domain 1 workspace Spark setting surfacing here: an
administrator changed the pool or the runtime, and the notebook is where it showed up.
Self-check — Spark failures
1. A job fails with `MaxExecutorFailures`. What is your next step? 2. Executors are exiting with `137`. What class of fix are you looking for? 3. A notebook that ran last month now returns different numbers, unchanged. What do you suspect? Answers: read the exit codes on the Executors tab — the error itself is a symptom. Memory: reduce what an executor holds, whether by fixing skew, shrinking partitions, caching less, or avoiding a large broadcast join. A runtime version change under the workspace.Errors, product by product¶
Each product reports its failures in its own place, and the exam's error objective names them one by one. Read the symptom against this table before you read the options.
| Product | Symptom | What it means |
|---|---|---|
| Pipeline | Failed on purpose with your own error code | A Fail activity fired |
| Pipeline | ForEach says the value is a String, not an array | The array arrived from Execute Pipeline as a string |
| Dataflow Gen2 | The schedule stopped running and the owner got an email | Consecutive failures paused it |
| Dataflow Gen2 | Refreshes error, then work again later | The rolling refresh cap was reached |
| Notebook | Session timed out after inactivity | The 20-minute default shut it down |
| Eventstream | Data stops reaching a destination | Read the Runtime logs |
| OneLake shortcut | One user sees nothing through it | They lack access to the target |
| T-SQL | A rollback runs on success, or never | Transaction statements in the wrong block |
Pipelines. A Fail activity fails a pipeline deliberately, with a custom error message and code that appear in the run history and logs, and it usually sits behind an If Condition that tests for the problem. That is how a pipeline reports "the file arrived empty" as a failure rather than a success. A different failure catches people who pass arrays between pipelines: an Execute Pipeline activity hands its array parameter to the child as a string, so a ForEach over it fails until the value is wrapped in the create array function.
Dataflows Gen2. A scheduled refresh that keeps failing does not keep failing forever: Fabric pauses the refresh schedule and emails the dataflow's owner. A dataflow whose schedule went quiet after a run of failures is paused, not broken in some new way, so fix the failure and turn the schedule back on. A refresh history that shows errors and then recovers without anyone touching it points the other way, at the rolling cap of 300 refreshes, or 150 for a non-CI/CD dataflow.
Notebooks. A session shuts down after a period of inactivity, 20 minutes by default. Rerun the notebook to start a new session, or raise the timeout at the notebook or workspace level.
Eventstreams have two monitoring views. Data insights shows metrics for the stream, each source and each destination; Runtime logs hold the engine's warnings, errors and information entries, and they are where a destination's failures are explained.
OneLake shortcuts within one tenant use passthrough authentication by default: each reader's own identity goes to the target, so a reader sees only the target data they could read directly. A shortcut that works for its creator and returns nothing for a colleague is a permission on the target, not a broken shortcut. Cross-tenant and external shortcuts are delegated instead.
T-SQL. With TRY/CATCH, begin and commit the transaction inside the TRY block and roll it back inside the CATCH block, which only runs when something in TRY failed.
BEGIN TRY
BEGIN TRANSACTION;
-- statements
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
The Warehouse also applies snapshot isolation to every transaction and ignores a T-SQL change of
isolation level, so write-write update conflicts are possible. Conflicts are checked per table, not
per row, so two jobs updating different rows of one table can still collide. What reduces them is
avoiding concurrent UPDATE, DELETE and MERGE on the same table, and retrying a failed
transaction after a delay. And some commands are not supported at
all, including triggers, materialized views and manually created multi-column statistics; Microsoft
warns that these can cause problems even when they appear to succeed.
Self-check — errors by product
1. A child pipeline's ForEach fails, saying its input is a String. What do you change? 2. A colleague gets no rows through a shortcut that works for you. Where is the problem? 3. In a TRY/CATCH block, where does `ROLLBACK TRANSACTION` go? Answers: wrap the parameter in the create array function, because Execute Pipeline passed the array as a string. In their access to the shortcut's target, since same-tenant shortcuts pass the reader's identity through. Inside the CATCH block.Optimising files¶
Delta table files fragment over time. Fragmentation increases file-operation overhead, reduces compression efficiency and can limit reader parallelism — which is the answer to "the table got slower and nobody changed it".
Four operations, and they are not alternatives to one another.
| Operation | What it does | Reach for it when |
|---|---|---|
OPTIMIZE |
Rewrites small files into right-sized ones | Reads have slowed as the table grew |
ZORDER BY |
Colocates similar values in the same files | Queries filter on two or more columns |
| V-Order | Reorganises the Parquet file's internals | Reads are heavy and writes can pay 15 percent |
VACUUM |
Deletes unreferenced files past retention | Storage cost; little speed by itself |
flowchart TD
A["What hurts?"] -->|Slow reads| B["Which cause?"]
A -->|Storage cost| C["VACUUM"]
B -->|Many files| D["OPTIMIZE"]
B -->|Filters| E["ZORDER BY"]
OPTIMIZE is the primary compaction operation: it groups small files into bins targeting an ideal file size and rewrites them. In Spark SQL it is one statement; through the Delta table interface it is a call.
from delta.tables import *
deltaTable = DeltaTable.forName(spark, "dbo.table_name")
deltaTable.optimize().executeCompaction()
ZORDER BY is a clause on the same command. It rewrites active files so rows with similar values are colocated, which improves file skipping for selective filters. Use it when queries frequently filter on two or more columns together and those predicates are selective enough to matter.
OPTIMIZE is idempotent, but an oversized minFileSize increases write amplification: with the
threshold at 1 GB, a 900 MB file can be rewritten after a small extra write, again and again. Tuning
it upward to get bigger files can make the maintenance job the cost.
Better still is not to fragment the table in the first place. For streaming or microbatch writes,
Microsoft recommends optimize write with auto compaction; for a table already full of small
files, one OPTIMIZE and then auto compaction from there on. Set both as table properties rather
than in a session, because table properties persist for every session and every writer.
ALTER TABLE sales_silver SET TBLPROPERTIES (
'delta.autoOptimize.optimizeWrite' = 'true',
'delta.autoOptimize.autoCompact' = 'true'
)
V-Order¶
V-Order is a write-time optimization of the Parquet file's internals — row-group distribution, encoding, compression — improving read efficiency across Fabric engines. Its typical cost is around 15 percent longer writes. It is a trade, not a free win: read-heavy dashboarding benefits, a write-heavy ingestion pipeline pays for nothing.
Files stay open-source Parquet compliant, V-Order works at file level, and compaction, vacuum and time travel all work alongside it. It is compatible with Z-Order, which is the point most often missed — the two solve different problems.
| V-Order | Z-Order | |
|---|---|---|
| Rearranges | The file's internals | Which rows share a file |
| Applied | At write time | By OPTIMIZE |
| Helps | Read efficiency generally | File skipping on selective filters |
V-Order is disabled by default for all newly created workspaces, to favour write-heavy data engineering, so on a new workspace it is off until someone turns it on.
It can be switched on at three scopes: for a session with spark.sql.parquet.vorder.default, for a
table with the delta.parquet.vorder.enabled table property, or for a single write with the
parquet.vorder.enabled writer option.
df.write.mode("overwrite") \
.option("parquet.vorder.enabled","true") \
.saveAsTable("myschema.mytable")
VACUUM¶
VACUUM permanently removes data files that are both no longer referenced by the Delta log and older than the retention threshold. Both conditions, not either — which is why a vacuum can appear to free nothing.
Two things about it are examinable. It does not improve query performance by itself the way
compaction or file-layout optimization does; its main purpose is storage cleanup. That is why it
belongs in a maintenance routine alongside OPTIMIZE rather than in place of it. And because it deletes permanently
from OneLake, it removes the ability to time travel to the versions whose files it cleaned, so
retention is a design decision rather than a housekeeping setting.
It is a Spark command, and must not be run in the SQL analytics endpoint or the warehouse SQL editor — those experiences do not support Spark Delta maintenance commands. It runs in notebooks, Spark job definitions, or the Lakehouse Maintenance interface and pipeline-based maintenance workflows. The Maintenance interface is the answer whenever a stem says the team writes no code.
Self-check — file optimisation
1. Queries against a streaming sink have slowed steadily. Which operation, and why? 2. A team runs VACUUM weekly and query times have not improved. What did they expect wrongly? 3. Where can you not run VACUUM? Answers: OPTIMIZE now, then optimize write and auto compaction as table properties so future writes stay large. That VACUUM would speed up reads; by itself it is storage cleanup. In the SQL analytics endpoint or the warehouse SQL editor.Optimising queries¶
In a Warehouse, the usual answer to a slow query is statistics. Statistics are objects describing the data that let the query optimizer estimate how much work each candidate plan would take, so it can choose the cheapest. When they are stale, the optimizer costs plans against data that no longer exists and picks badly — which is the mechanism behind "the query got slow and nothing changed".
Fabric offers two paths, and an option claiming statistics must be created by hand is wrong.
| Path | Who maintains them |
|---|---|
| Automatic statistics | The engine, at query time |
| User-defined statistics | You, with data definition language |
Manual statistics are managed with CREATE STATISTICS, UPDATE STATISTICS and DROP STATISTICS,
and inspected with DBCC SHOW_STATISTICS. They are worth the effort on columns heavily used in the
query workload — specifically in GROUP BY, ORDER BY, filters and joins — and worth refreshing
after data changes that significantly alter row count or distribution.
On the Spark side, the native execution engine makes Spark processing faster and cheaper, and it
now supports a user-defined function (UDF) written in Python or Scala, and complex types, so keeping
a UDF no longer means giving up the speed. That support needs Runtime 1.3 or 2.0. Switch it on for a session with %%configure and "spark.native.enabled": "true", and off for one
query with SET spark.native.enabled=FALSE. Its gain is largest when a query avoids unsupported
features that trigger fallback.
Pipelines go faster through parallelism: a ForEach can run copy activities side by side, one copy activity reads and writes with several threads, and intelligent throughput optimization balances CPU, memory and network for the run. A Dataflow Gen2 is tuned with Fast Copy, query folding and staging, using the staging lakehouse and warehouse that come with it.
Self-check — query optimisation
1. A warehouse query that was fast last quarter is now slow, and the query is unchanged. First suspicion? 2. On which columns is manual statistics maintenance worth the effort? Answers: stale statistics, so the optimizer is costing plans against a data distribution that has moved. Those used in `GROUP BY`, `ORDER BY`, filters and joins.Optimising aggregations¶
In an Eventhouse, a repeated expensive aggregation is answered by a materialized view — an
aggregation query over a source table or over another materialized view, representing a single
summarize statement, created with the .create materialized-view command in Kusto Query Language.
Choose the creation mode deliberately; they trade in opposite directions.
| Creation mode | Available | Contains |
|---|---|---|
| Empty | Immediately | Only records ingested after creation |
| Over existing records | After a build that can take a long while | Everything in the source table |
So an empty view suits a stream you are about to start aggregating, and a backfilled one suits a table you already have and cannot afford to query repeatedly. Precomputing is the Eventhouse counterpart to compaction on the lakehouse side: rather than rearranging the data so the query is cheaper, you run the query once and keep the answer.
An eventhouse also suspends when nobody uses it, to save cost, and the first query after that waits a few seconds while it reactivates. Capacity Planner mode, which Microsoft previously called Always-on, keeps it always active, and a weekly minimum capacity schedule can sit on top of it. An eventstream is scaled by its event throughput setting instead: Low below 10 MB/s, Medium from 10 to 100, High above 100.
Currency note. Older questions, including Microsoft's own practice assessment, say Always-on. This does not change the exam answer. It is the same setting under its new name; pick it whichever name the options use.
🎯 Test Your Knowledge¶
Covered the domain? Test yourself with the DataCertLab DP-700 Practice Tests, then use the traps below as your last check.
💡 Tip: Open the link in a new tab to keep these notes open for reference.
Learn → Practice → Review → Improve
Traps worth carrying into the exam¶
Trap. V-Order is disabled by default in new workspaces. The portable rule: answer the current default, not the one a feature's reputation suggests.
Trap. VACUUM is not the fix for a slow table on its own. The portable rule: match the operation to the symptom — compaction for slow reads, vacuum for storage cost.
Trap.
MaxExecutorFailuresis a symptom. The portable rule: when an error reports that something failed too many times, the question is why it failed once.Trap. Interactive work is throttled before background work. The portable rule: the refresh keeps running while the user-facing query is rejected, which is the opposite of what the complaint will tell you.
🎯 DP-700 Practice Tests on Udemy · ← DP-700 exam overview · All certifications