SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Ingest and transform data

Streaming Data in Fabric: Eventstreams, Spark, and Real-Time Intelligence

20 min readDP-700 · Ingest and transform dataUpdated

Microsoft Fabric gives you three engines for ingesting and transforming streaming data: Eventstream for no-code capture, transformation, and routing; Spark structured streaming for code-first processing into lakehouse Delta tables; and KQL in a Real-Time Intelligence Eventhouse for high-volume time-series analytics, dashboards, and alerting. DP-700 tests whether you can pick the right engine for a scenario, decide how an Eventhouse should reach data (native tables, standard OneLake shortcuts, or shortcuts with Query acceleration), and apply windowing functions — tumbling, hopping, sliding, and session — to aggregate events over time. This lesson walks the full decision path: what each engine does best, how an Eventstream is built from sources, operators, reference-data joins, derived streams, and destinations, how structured streaming uses checkpoints, watermarks, and output modes, and how KQL buckets, transforms, and precomputes event data. It closes with an end-to-end IoT scenario that connects Azure Event Hubs to an Eventhouse and an Activator alert.

What you’ll learn
  • Choose between Eventstream, Spark structured streaming, and KQL for a given streaming workload, and rule out batch-only items
  • Decide between native tables, standard OneLake shortcuts, and Query acceleration in an Eventhouse, including sizing the hot caching period
  • Build an Eventstream from sources, operators, reference-data joins, derived streams, and destinations
  • Write Spark structured streaming jobs with a stable checkpoint, a watermark, and an output mode the Delta sink supports
  • Aggregate and transform streaming data in KQL with bin, make-series, update policies, and materialized views
  • Select and implement the correct windowing function — tumbling, hopping, sliding, or session — in each engine

How to choose a streaming engine in Fabric

Choose Eventstream when you need to capture, lightly transform, and route events without writing code; choose Spark structured streaming when you need complex, code-first transformations landing in lakehouse Delta tables; choose KQL in Real-Time Intelligence when the goal is interactive analytics, time-series exploration, dashboards, or alerting over high-volume event data. The three are complementary, not competing — a common pattern uses an Eventstream to ingest and route, an Eventhouse to store and query, and Spark only where custom logic demands it.

EngineBest forInterfaceTypical destinations
EventstreamNo-code capture, filtering, light aggregation, enrichment from reference tables, and routing of events; connecting external sources into FabricDrag-and-drop editor with operators (filter, manage fields, aggregate, group by, expand, union, join, SQL)Eventhouse, Lakehouse, derived stream, custom endpoint, Activator, Spark notebook (preview)
Spark structured streamingComplex transformations, custom Python or Scala logic (including packages from a Fabric environment), joins against large Delta tables, stateful deduplication, ML scoring, medallion processingCode-first (PySpark, Scala) in notebooks or Spark job definitionsDelta tables in a lakehouse
KQL / Real-Time IntelligenceHigh-volume telemetry and log analytics, time-series queries, near-real-time dashboards, anomaly detection, alertingKQL in querysets and dashboard tiles, update policies, materialized viewsEventhouse tables, Real-Time Dashboards, Activator

Exam scenarios usually hinge on two signals: the skill profile (no-code analysts point to Eventstream; data engineers writing Python point to Spark; teams fluent in KQL point to an Eventhouse) and the workload shape (routing points to Eventstream; heavy transformation points to Spark; interactive time-series analysis points to KQL).

Where custom Python fits

Python in a stream usually points to Spark, but be precise about why. KQL does have a python() plugin that runs inline Python in a sandbox inside the Eventhouse, which suits small per-query calculations. Libraries you publish to a Fabric environment (custom packages and their dependencies) load only into Spark sessions, so a requirement to run a packaged model or library against the stream is a Spark structured streaming job. Eventstream operators cannot run custom Python at all.

Non-streaming near-twins

Two Fabric items often appear as wrong answers in streaming scenarios. Dataflow Gen2 is refresh-based batch: it runs on a schedule and cannot subscribe to Azure Event Hubs or Azure IoT Hub as a continuous stream, so even a frequent refresh is not streaming ingestion. A Warehouse is not an Eventstream destination and is not a data source for Real-Time Dashboards; streaming data for dashboards belongs in an Eventhouse.

Process data by using Eventstreams

An Eventstream ingests events from a source, optionally transforms them in a no-code editor, and routes the results to one or more destinations. It is the front door for streaming data in Fabric.

Sources

Eventstreams connect to Azure Event Hubs, Azure IoT Hub, Kafka-compatible endpoints, change data capture feeds from operational databases, Fabric workspace and job events, sample data for testing, and a custom endpoint your own applications can publish to.

Transformations

The event processor supplies drag-and-drop operators:

  • Filter drops events that fail a condition.
  • Manage fields renames, casts, adds, or removes columns.
  • Aggregate and Group by compute metrics over time; Group by is where you configure tumbling, hopping, sliding, session, and snapshot windows.
  • Expand flattens an array field: it produces one output row per array element, repeating the event's other fields on each row.
  • Union combines streams that share a schema into one stream; it does not match rows on a key.
  • Join matches events on a key — either two streams within a time window, or a stream against reference data (see below).
  • A SQL operator lets you express the transformation as a SQL-style query instead of chaining visual operators.

Enriching a stream with reference data

The Join operator can enrich each event from a Lakehouse Delta table added as a reference data source — for example, adding each device's site and owner from a device registry to its readings. The reference source can be refreshed periodically on an interval you set, so changes to the table reach the enrichment without restarting the stream; with no refresh, the stream keeps using the data it loaded. Pick the join type by what should happen to unmatched events: an inner join drops events with no matching reference row, while a left outer join keeps them with empty enrichment columns. A Union is not a substitute — it appends rows, it does not look anything up.

Destinations

DestinationWhat it does
EventhouseLands events in a KQL table for real-time analytics. Two ingestion modes: direct ingestion (the Eventhouse pulls the raw stream; no Eventstream operators can sit in front of it) and event processing before ingestion (the Eventstream transforms events with its operators, then pushes them to the table). To filter or cast inside the Eventstream, the destination must use event processing before ingestion.
LakehouseWrites Delta tables for downstream batch and Spark work. Writes are batched, so rows appear in the table in commits rather than one by one.
Derived streamPublishes the transformed events as a new named stream (see below).
Custom endpointLets applications outside Fabric consume the stream in real time over the Event Hubs, AMQP, or Kafka protocols.
ActivatorEvaluates conditions on the events and fires actions such as Teams messages or emails.
Spark Notebook (preview)Hands the stream to a notebook for code-first processing.

One Eventstream can route the same source to several destinations at once (fan-out), so you never re-ingest a source to feed a second consumer. There is no Warehouse, Dataflow Gen2, or Power BI semantic model destination.

Derived streams and the Real-Time hub

A derived stream is the named output of a transformation chain. It is published to the Real-Time hub, where other teams can discover it and subscribe to it or route it onward, so one raw feed can fan out into several shaped feeds. Connecting an operator straight to a destination does the same transformation but publishes no stream — nobody else can find or reuse that shaped feed.

Eventstream transformation is deliberately lightweight. When a scenario needs custom code, packaged Python, stateful deduplication, or joins against large historical tables, route the stream to a lakehouse or Eventhouse and do the heavy work in Spark or KQL.

Native tables vs OneLake shortcuts in Real-Time Intelligence

Use a native table when you ingest events into the Eventhouse itself and need the fastest query performance; use a OneLake shortcut when the data already lives elsewhere in OneLake (for example, Delta tables a Spark job maintains in a lakehouse) and you want to query it from KQL without copying it.

A native table stores data in the Eventhouse's own optimized columnar format with hot caching and indexing. Everything KQL is built for — sub-second aggregations over billions of rows, time-series operators, update policies, materialized views — performs best against native tables, and data ingested from an Eventstream is queryable within seconds. If you also enable OneLake availability on the table, the Eventhouse writes a Delta copy into OneLake so Spark, SQL, and Power BI can read the same data without export jobs; that setting exposes native data outward, which is the reverse direction from a shortcut.

A OneLake shortcut points the KQL database at external Delta or Parquet data in a lakehouse, warehouse, or another OneLake location. Nothing is duplicated and there is no ingestion pipeline to maintain — the shortcut always reflects the current state of the source. The trade-off is performance: standard shortcut queries read the external files at query time, without the Eventhouse's native indexing and caching, so they run noticeably slower than native-table queries.

The decision rule: data born in the stream and queried constantly belongs in native tables; data owned and maintained by another engine that KQL only needs to reference — dimension tables, historical archives, lakehouse gold tables — belongs behind a shortcut.

Query acceleration vs standard OneLake shortcuts

Enable Query acceleration on a OneLake shortcut when you need shortcut convenience — no data duplication, no ingestion pipeline — but query performance close to a native table; keep a standard shortcut when queries are occasional and the cost of acceleration is not justified.

With a standard shortcut, every query reads the underlying Delta or Parquet files directly at query time. That is fine for infrequent lookups or joins against small reference data, but it is slow for the interactive, high-frequency query patterns Real-Time Dashboards depend on.

Query acceleration changes the physics: the Eventhouse indexes and caches the shortcut data in the background, so queries against the accelerated shortcut behave much like queries against native tables — while the source remains the single copy of the data, still owned and updated by the producing engine.

The hot caching period is the cost lever

The query acceleration policy sets a hot period: how many days of the source data are indexed and cached. Acceleration consumes capacity in proportion to the data it keeps hot, so size the period to the range your queries actually read — if dashboards read the last 30 days of a five-year table, a 30-day hot period gives them the speed without paying to cache years nobody queries. Accelerate the shortcuts that are queried frequently; leave rarely used lookups standard.

Limits to know

  • One copy, one owner. Neither shortcut option copies the data, so there is no second copy to reconcile with the source; ingesting into a native table does create one.
  • Accelerated shortcuts behave like external tables. You cannot build a materialized view over one. If you need precomputed aggregates, ingest the data into a native table (for example, by adding an Eventhouse destination to the Eventstream) and build the view there.
  • Update policies: an update policy whose source is an accelerated delta shortcut is supported only in preview, and it runs asynchronously and non-transactionally, without processing deletes made in the Delta table. Even then it transforms each batch independently, so it cannot maintain running aggregates — that is a materialized view's job.
  • Freshness is not seconds. New data reaches an accelerated shortcut only after the source write lands (a Lakehouse destination batches its Delta commits) and the background indexing catches up. Acceleration is for fast interactive queries over data owned elsewhere, not for alerting that must fire within seconds — alerting data belongs in native Eventhouse tables.
RequirementChoose
Hot streaming data, sub-second queries, seconds-level freshness, alertsNative table
Data owned elsewhere, queried often by dashboards, must stay a single copyShortcut with Query acceleration (hot period sized to the queried range)
Data owned elsewhere, queried occasionally, minimize costStandard shortcut
Native Eventhouse data needed by Spark or Power BI in Delta formatOneLake availability on the native table

Process data by using Spark structured streaming

Spark structured streaming treats a live data source as an unbounded table: you define a query with readStream and writeStream, and Spark runs it incrementally as micro-batches, applying your transformations to new data as it arrives. In Fabric you run it from a notebook or Spark job definition, typically reading from Azure Event Hubs, a Kafka endpoint, or a Delta table an Eventstream is feeding, and writing Delta tables in a lakehouse.

from pyspark.sql.functions import window, col

df = (spark.readStream
      .format("delta")
      .load("Tables/raw_events"))

agg = (df.withWatermark("eventTime", "10 minutes")
         .groupBy(window(col("eventTime"), "5 minutes"), "deviceId")
         .count())

(agg.writeStream
    .format("delta")
    .outputMode("append")
    .option("checkpointLocation", "Files/checkpoints/device_counts")
    .toTable("device_counts"))

Checkpointing

The checkpointLocation is where Spark persists the query's progress: the source offsets already processed, the commit log of completed micro-batches, and the state store (running window aggregates, deduplication keys). It is not the Delta transaction log, which belongs to the target table. After a failure or restart, Spark resumes from the checkpoint, and together with the Delta sink this gives exactly-once output.

The checkpoint must be a stable path, unique to that query, reused on every run. If the path changes per run (built from a timestamp, say) or the folder is deleted, Spark sees no history and reprocesses the source from the beginning — an append query then writes duplicate copies of rows already in the table. A streaming write without a checkpoint location is the classic wrong answer.

Watermarks and late data

withWatermark("eventTime", "10 minutes") tells Spark how late, in event time, data may arrive. Data arriving within the delay threshold is guaranteed to be aggregated into its window; data later than the threshold may be dropped (it is not guaranteed to be). Once the watermark passes a window's end, Spark can finalize that window and evict its state, so the state store stops growing. Set the threshold to the lateness you actually see — too short drops valid events, too long delays results and holds more state. A longer trigger interval or a processing-time window does not handle late events; event-time windows plus a watermark do.

Output modes and the Delta sink

ModeWrites each triggerValid for
Append (default)Only new rows that will never changeQueries with no aggregation; aggregations only with a watermark (windows are emitted once the watermark passes their end)
CompleteThe entire result tableAggregations only
UpdateOnly rows changed since the last triggerAggregations and plain queries — but not the Delta table sink

Three consequences follow:

  • A query with no aggregation — filter, select, write — uses append mode, the simplest configuration.
  • In append mode, the watermark delay sets result latency: a window appears in the table only after the watermark passes its end, so a 1-hour watermark on 15-minute windows holds each result back by more than an hour. Append mode on an aggregation without a watermark is rejected.
  • The Delta streaming sink supports only append and complete. To keep a large table current with changing aggregates (per-key totals that keep changing, for example) without rewriting it each trigger, use foreachBatch and run a Delta MERGE that upserts each micro-batch.

Stateful operations

dropDuplicates on a stream keeps the keys it has seen as state in the checkpoint; pair it with a watermark so that state is bounded. cache() is not supported on streaming DataFrames. Choose structured streaming when the scenario says custom transformation logic, Python or Scala packages, joins with large Delta tables, stateful deduplication, or medallion-style refinement of a stream — things Eventstream operators cannot express.

Process data by using KQL

KQL processes streaming data after it lands in an Eventhouse: you aggregate, reshape, and analyze events with query operators, and you can transform data at ingestion time with update policies or maintain pre-computed aggregates with materialized views. You write and save queries in a KQL queryset; a Real-Time Dashboard is a separate item whose tiles each hold their own KQL query against a data source such as an Eventhouse database (you can pin a queryset query to a dashboard as a tile). Activator alerts can also be driven from KQL queries.

Time bucketing: bin() vs make-series

The workhorse pattern is summarize with bin(), which rounds each timestamp down to the start of its interval for grouping — effectively a tumbling window over stored data:

Telemetry
| where Timestamp > ago(1h)
| summarize avgTemp = avg(Temperature), events = count()
    by DeviceId, bin(Timestamp, 5m)

summarize ... by bin() returns only bins that contain data: an hour with no errors simply has no row, and sorting or aligning bins with bin_at() does not bring it back. make-series builds a regular series per group and fills empty bins with a default value (such as 0). Give it an explicit from ... to range; without one, the series spans only the first to last bin that has data, so empty leading or trailing bins are still missing.

Telemetry
| make-series avgTemp = avg(Temperature) default = 0
    on Timestamp from ago(1d) to now() step 15m by DeviceId
| extend anomalies = series_decompose_anomalies(avgTemp)

The series_* functions operate on the arrays make-series produces, not on summarize rows. series_decompose_anomalies() decomposes each series into seasonality, trend, and residual before flagging outliers, so a regular daily peak is not reported as an anomaly — unlike a fixed threshold such as "three times the average".

Update policies

An update policy runs a KQL function automatically whenever a batch is ingested into a source table, writing the transformed rows to a target table — useful for parsing raw JSON into typed columns as it arrives. Key rules:

  • The policy is defined on the target table and names its source. To split one raw table into two typed tables, put one policy on each target, both reading the same source.
  • IsTransactional defaults to false: if the policy fails, ingestion into the source still succeeds and the target misses that batch. Set it to true so the source and target ingestion succeed or fail together.
  • To transform and discard raw rows, set the source table's retention soft-delete period to 0 seconds; this requires a transactional policy, so data is never lost between the tables.
  • Each batch is transformed independently — an update policy cannot maintain aggregates across batches.

Materialized views

A materialized view keeps an aggregation continuously up to date so dashboards read a pre-aggregated result instead of scanning raw events. Its query must aggregate (a summarize). The classic latest-record-per-key view uses arg_max(): summarize arg_max(Timestamp, *) by DeviceId. Querying the view by name combines the materialized part with records not yet materialized, so results are fully current. materialized_view("ViewName") reads only the materialized part — cheaper, but possibly slightly stale; materialized_view("ViewName", 10m) reads only the materialized part when it is at most 10 minutes old and otherwise falls back to the full view. The view's lookback property (for arg_max, arg_min, and take_any views) limits how far back deduplication looks; it does not control how stale query results may be.

Near-twins that do not precompute

FeatureWhat it doesReduces scan cost?
Materialized viewMaintains an aggregation incrementally as data is ingestedYes — queries read the precomputed result
Update policyTransforms each ingested batch into a target tableYes for parsing/reshaping, done once at ingestion; not for aggregates
Stored functionSaves query text under a name for reuseNo — it runs the full query every call
Query results cacheReturns a cached result for an identical query within a maximum ageOnly for repeated identical queries; it is not refreshed as data arrives

Reach for KQL when the scenario emphasizes interactive exploration of high-volume telemetry, time-series operators, near-real-time dashboards, or log analytics.

Windowing functions: tumbling, hopping, sliding, session

Windowing functions divide an unbounded stream into finite time buckets so you can aggregate it; the four you must distinguish are tumbling, hopping, sliding, and session windows. Scenarios describe a timing requirement and ask for the window.

WindowSizeOverlapAn event belongs toChoose when
TumblingFixedNone — windows are contiguous and back-to-backExactly one windowPeriodic, non-overlapping totals: events per 5 minutes, hourly revenue
HoppingFixedYes — windows advance by a hop smaller than the window sizePossibly several windowsOverlapping schedule: the last 10 minutes, recomputed every 2 minutes
SlidingFixedYes — a result is produced only when an event enters or leaves the windowPossibly several windowsThreshold conditions over any interval: alert if more than 3 errors occur in any 60-second span
SessionVariableNoneExactly one windowBursts of activity separated by idle gaps: group a user's clicks until 5 minutes of inactivity ends the session

Boundaries and emission

A tumbling window is simply a hopping window whose hop equals its size. For a hopping window, the size is the lookback and the hop is how often a result is produced: "the last 60 minutes, every 15 minutes" is size 60, hop 15. Tumbling and hopping windows are aligned to the clock, so a burst can straddle a boundary: five errors between 09:15:24 and 09:15:38 split two and three across the 30-second tumbling windows starting 09:15:00 and 09:15:30, so neither window reaches a threshold of four. A sliding window evaluates whenever an event enters or leaves, so it catches the condition in any 30-second span. A session window has no fixed size at all — it opens with an event and closes after a configured inactivity timeout, and it should be grouped by the entity (user, device) whose activity defines the session.

Implementing windows in each engine

  • Eventstream: the Group by operator offers tumbling, hopping, sliding, and session windows, plus snapshot windows (events sharing the same timestamp).
  • Spark: window(col, windowDuration, slideDuration, startTime). With only a duration it is tumbling; a slide shorter than the duration creates overlapping windows (Spark calls them sliding windows; they behave like hopping windows), so each event is counted in duration ÷ slide windows — window("eventTime", "10 minutes", "5 minutes") counts every event twice. startTime only offsets the window boundaries. session_window(col, gapDuration) builds inactivity-gap sessions; group by the user key too (groupBy(session_window("ts", "20 minutes"), "userId")) or one session spans all users' events. Pair both with withWatermark.
  • KQL: bin() gives tumbling behaviour over stored data. row_window_session(Timestamp, MaxDistanceFromFirst, MaxDistanceBetweenNeighbors[, Restart]) runs over serialized (time-ordered) rows and returns, for each row, the start time of its session; a new session starts when the gap to the previous row exceeds the limit, the session reaches its maximum length, or the restart condition is true (for example, when the user changes). The session_count plugin only counts active sessions over time; it does not assign rows to sessions.

Worked scenario: IoT telemetry from Event Hubs to an alert

Here is how the pieces compose for a typical requirement: thousands of factory sensors publish temperature readings; operations needs a live dashboard and an alert whenever a device's five-minute average exceeds a threshold, and data science wants the raw history in the lakehouse.

  1. Ingest: the sensors publish to Azure Event Hubs (or Azure IoT Hub). An Eventstream uses it as a source — no code, just a connection.
  2. Shape and route: a Manage fields operator casts the payload into typed columns. The Eventstream routes the full raw stream to a Lakehouse destination as Delta tables for data science, and the shaped stream to an Eventhouse destination set to event processing before ingestion — one source fanning out to two destinations.
  3. Analyze: in the Eventhouse, the readings land in a native table (hot, constantly queried streaming data — not a shortcut). A Real-Time Dashboard tile runs summarize avg(Temperature) by DeviceId, bin(Timestamp, 5m) against it — tumbling-window behaviour.
  4. Alert: Activator watches the stream or the KQL query and fires a Teams message or email when the five-minute average crosses the threshold. Because the data is native, the alert sees readings within seconds.
  5. Reference data: the device master table is maintained in the lakehouse by another team, so the KQL database reaches it through a OneLake shortcut; if dashboard joins against it prove slow, enable Query acceleration rather than building an ingestion copy.

Notice what was not needed: Spark. It enters only if the requirement adds custom logic — say, scoring readings with a Python package from a Fabric environment — at which point a structured streaming job with readStream from the lakehouse Delta table, a stable checkpoint location, and append output mode does that one job.

Tip. Expect engine-selection scenarios where no-code or drag-and-drop points to Eventstream, custom Python packages, stateful deduplication or large Delta joins point to Spark structured streaming, and interactive time-series analytics by KQL-literate teams points to an Eventhouse — with Dataflow Gen2 and Warehouse as batch distractors. Eventstream items test operators (Expand, Join against Lakehouse reference data with refresh and inner vs left outer, Union), destinations (custom endpoint protocols, Eventhouse direct ingestion vs event processing before ingestion), and derived streams in the Real-Time hub. Storage items pit native tables, standard shortcuts, accelerated shortcuts (hot period sizing, external-table limits, freshness) and OneLake availability against each other. Spark items probe checkpoint contents and stable paths, watermark guarantees, and output modes against the Delta sink; KQL items probe make-series vs bin gaps, series_decompose_anomalies, update policy placement and IsTransactional, and materialized views vs stored functions and caching. Windowing items ask you to name or configure the window in each engine.

Key takeaways
  • Match the engine to the workload: Eventstream for no-code capture, enrichment, and routing; Spark structured streaming for code-first logic, environment Python packages, and stateful work into Delta tables; KQL in an Eventhouse for interactive time-series analytics, dashboards, and alerting. Dataflow Gen2 is batch and a Warehouse is neither an Eventstream destination nor a Real-Time Dashboard source.
  • Eventstream operators: Filter, Manage fields, Aggregate, Group by (windows), Expand (one row per array element), Union, Join (stream-to-stream or against Lakehouse reference data with periodic refresh, inner or left outer), and SQL. Filtering before an Eventhouse needs the event-processing-before-ingestion mode.
  • Derived streams are published to the Real-Time hub for other teams to subscribe to; a custom endpoint destination serves external apps over Event Hubs, AMQP, or Kafka.
  • Native tables give the fastest, freshest KQL queries; OneLake shortcuts query external Delta data in place; Query acceleration indexes and caches a shortcut for near-native speed, costed by its hot caching period, with external-table limits (no materialized views) and a freshness delay unsuitable for seconds-level alerts.
  • Spark structured streaming needs a stable, per-query checkpointLocation (offsets, commit log, state); withWatermark guarantees data within the threshold is counted and bounds state; append is the default and needs a watermark for aggregations, and the Delta sink takes only append or complete — upsert changing aggregates with foreachBatch and MERGE.
  • In KQL, bin() omits empty buckets while make-series fills them (with an explicit from/to range) for series_* functions such as series_decompose_anomalies; update policies live on the target table and need IsTransactional for transform-and-discard; materialized views (arg_max for latest-per-key) precompute aggregates, unlike stored functions or the results cache.
  • Window choice is scenario-driven: tumbling = fixed and non-overlapping, hopping = fixed and overlapping on a clock-aligned schedule (size = lookback, hop = frequency), sliding = any span of the given length, session = variable length ended by an inactivity gap and grouped by the entity.

Frequently asked questions

When should I use Eventstream instead of Spark structured streaming in Fabric?

Use Eventstream when the job is capturing events and applying light, declarative transformations — filtering, field mapping, windowed aggregation, enrichment from a Lakehouse reference table, routing — through a no-code editor. Use Spark structured streaming when you need custom code: complex business logic, Python packages published to a Fabric environment, ML scoring, joins against large Delta tables, stateful deduplication, or multi-step medallion refinement. A common design uses both: the Eventstream ingests and routes the raw feed, and Spark picks it up from a lakehouse for the heavy processing.

What is the difference between a native table and a OneLake shortcut in an Eventhouse?

A native table holds data ingested into the Eventhouse itself, stored in its optimized, indexed, hot-cached format — the fastest option for KQL queries and queryable within seconds of arrival. A OneLake shortcut is a pointer to Delta or Parquet data that lives elsewhere in OneLake, letting KQL query it without copying it or building an ingestion pipeline, but standard shortcut queries read the external files at query time and run slower. Ingest hot streaming data into native tables; use shortcuts for data another engine owns and maintains.

What does Query acceleration for OneLake shortcuts actually do, and what does it cost?

Query acceleration makes the Eventhouse index and cache a shortcut's data in the background, so queries perform close to native-table queries while the source remains the only copy. Its policy sets a hot caching period — how many days are kept accelerated — and capacity cost grows with that period, so size it to the range queries read. Accelerated shortcuts behave like external tables (no materialized views over them), and new data appears only after the source write and indexing, so seconds-level alerting belongs on native tables.

Why does a Spark structured streaming query need a stable checkpoint location?

The checkpoint stores the source offsets already processed, the commit log, and intermediate state such as window aggregates and deduplication keys. On restart Spark resumes from it, which with the Delta sink gives exactly-once output. If the path changes between runs or the folder is deleted, Spark has no history and reprocesses the source from the start, so an append query writes duplicate rows. Use one fixed path per query and reuse it on every run.

Which Spark output mode should a streaming write to a Delta table use?

Append is the default and fits queries with no aggregation and windowed aggregations with a watermark; each window is written once, after the watermark passes its end, so the watermark delay sets how late results appear. Complete rewrites the whole result each trigger and is only for aggregations. The Delta streaming sink does not support update mode, so to keep a large table current with changing aggregates, use foreachBatch with a Delta MERGE that upserts each micro-batch.

What is the difference between an update policy and a materialized view in KQL?

An update policy is defined on a target table and runs a function over each batch ingested into its source table, writing the transformed rows to the target — ideal for parsing raw JSON into typed columns at ingestion. Each batch is handled independently, so it cannot maintain aggregates. A materialized view keeps an aggregation, such as arg_max of the timestamp per device or hourly averages, up to date incrementally so queries read the precomputed result. A stored function saves only query text and reduces no cost.

Why does summarize by bin() leave gaps in a time chart?

summarize by bin() returns a row only for bins that contain data, so an hour with no events is simply absent and a line chart joins across the gap; sorting or bin_at() alignment does not change that. make-series fills every bin in the range with a default value such as 0, but only across an explicit from/to range — without one it spans just the first to last bin with data. make-series also produces the arrays that series_* functions like series_decompose_anomalies need.

How do I pick between tumbling, hopping, sliding, and session windows?

Read the requirement's timing language. Fixed, non-overlapping buckets where every event counts once — a total per 5 minutes — is tumbling. A fixed lookback recomputed on a faster schedule — the last 10 minutes, every 2 minutes — is hopping, with size 10 and hop 2. A condition that must hold over any interval of a given length — more than 5 failures in any 60 seconds — is sliding, because clock-aligned windows can split a burst. Bursts of activity ending after an idle gap — a user session ending after 20 minutes of inactivity — is session, grouped by user.

Source

This lesson covers the "Ingest and transform data" domain of the official DP-700 exam guide. Vendors revise their guides — check the source for the current version.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?