Every telemetry project starts the same way. Someone sees a demo, a needle sweeps across a gauge in real time, and the room agrees this is the future. Six months later the pipeline is running, the storage bill is climbing, and nobody has looked at the dashboard since the launch. This guide walks the real path from a plant floor to Microsoft Fabric, hop by hop, with the latency and cost decisions marked at each stage, and it is honest about where Fabric is the wrong layer to be doing the work.
A note on names and quotas
Fabric's Real-Time Intelligence surface, the eventhouse and eventstream naming, the SKU tiers and the ingestion quotas have all changed repeatedly since the platform launched. Everything here is verified as of 1 August 2026. Treat feature names and limits as perishable, and recheck the official docs every quarter before you commit an architecture to them.
The full path, gateway to lakehouse
Here is the shape of a working plant pipeline. Sensors sit on a PLC or DCS. A gateway on the plant network speaks their protocol, usually OPC UA for the process side or MQTT for the lighter field devices, and publishes upward. Fabric receives that stream in an eventstream, lands it in an eventhouse (the KQL database), and from there the data forks: hot data stays queryable in KQL for seconds-to-minutes questions, and a copy lands in OneLake as Delta so the lakehouse can do the slow, wide, historical work.
The single most useful thing you can do before building any of this is to draw the path and write the latency budget on every arrow. It forces you to admit what "real time" means for your use case, and it stops you promising millisecond behaviour that the architecture physically cannot deliver.
Read the budget end to end. From a tag changing on the plant to a number appearing in a KQL dashboard, you are realistically looking at three to eight seconds, dominated by the gateway publish interval and the ingest hop, not the query. That is fine for operational awareness and trend detection. It is nowhere near fast enough for control or protection, which is the boundary we come back to at the end.
The fork after the eventhouse is the part people skip and regret. Hot data lives in the KQL engine's cache and columnstore, where it is expensive per gigabyte but answers in milliseconds. Cold data belongs in OneLake as Delta, where it is cheap and wide but slow. If you send everything hot forever, your bill grows without bound. If you send everything cold, your live dashboard is querying a lakehouse and feels like treacle. The retention model below is how you split it deliberately. For the cold side specifically, see my note on OneLake for operational and sensor data, and if you are still deciding how much history to keep at all, sizing a process historian covers the volume maths.
Hot, warm, cold: retention by tag criticality
Not every tag deserves the same treatment. A bearing temperature on a critical pump is worth keeping instantly queryable for a week and recoverable for years. An ambient humidity reading in a warehouse is worth almost nothing after a day. The mistake is applying one retention policy to the whole database because it is easier. The fix is to tier by criticality and pay only where it matters.
KQL databases give you two levers per table: a hot cache window (data held on fast local SSD for instant queries) and a total retention window (how long the row survives at all, in cheaper columnstore). OneLake gives you the third tier, cold Delta, where cost per gigabyte drops by roughly an order of magnitude. Set all three by tag class.
| Tag criticality | Hot (KQL cache) | Warm (KQL retention) | Cold (OneLake) | Relative storage cost |
|---|---|---|---|---|
| Safety / protection-adjacent | 7 days | 90 days | 7 years | High |
| Critical rotating equipment | 3 days | 30 days | 3 years | Medium-high |
| Balance-of-plant | 24 hours | 14 days | 1 year | Medium |
| Non-critical / ambient | 6 hours | 7 days | 90 days | Low |
The number that drives your bill is the hot cache column, because that is the SSD tier. Warm retention in KQL columnstore is cheap by comparison, and cold OneLake is cheaper still. So the discipline is simple: keep the hot window as short as the fastest thing you actually query, push everything else to warm, and let OneLake hold the long tail. A safety-adjacent tag earns seven days hot because you may need to interrogate a recent event minute by minute. An ambient tag earns six hours because nobody has ever asked "what was the warehouse humidity forty minutes ago" and needed an answer in milliseconds.
The insight most teams learn too late
Your storage cost is not driven by how much data you ingest. It is driven by how much of it you insist on keeping hot. Two plants with identical sensor counts can have a fivefold difference in Fabric spend purely from a lazy retention policy. Tier by criticality on day one, before the tables fill up and the migration becomes a project of its own.
A dashboard nobody watches is decoration
Here is the reality that sinks more telemetry projects than any technical problem. You build a beautiful real-time dashboard, you present it, everyone nods, and then nobody is rostered to sit and watch it. A dashboard with no assigned pair of eyes is decoration. It looks like monitoring. It is a screensaver.
The fix is not a better dashboard. It is to remove the human from the detection loop entirely and let the system act. In Fabric that mechanism is Activator. You point an Activator rule at the output of a KQL query or the eventstream directly, define a condition, and attach an action. The action you want is almost never an email. It is a work order.
An email lands in an inbox that already has four hundred unread messages. It has no owner, no due date, and no accountability. A work order raised in your CMMS has an assignee, a priority, an SLA clock, and a closure that someone has to sign off. That is the difference between a system that changes what happens on the plant and one that generates noise. So the Activator action should be an HTTP call to your CMMS work-order API, or a Power Automate flow that does the same, carrying the asset tag, the detected condition, and the evidence.
- Condition: the trend query below returns a sustained rise, held for N minutes so a single spike cannot trigger it.
- Action: POST to the CMMS, creating a work order against the asset with a "condition alert" type and the slope value in the description.
- Deduplication: the rule checks for an open work order on that asset first, so a slow ten-hour rise raises one job, not sixty.
That last point matters as much as the first. An Activator rule that raises a work order every evaluation cycle recreates the exact alarm-flood problem we are trying to avoid, just in your maintenance backlog instead of an inbox. One condition, one open job, updated rather than duplicated. If you are wiring this into an existing historian and CMMS estate, the integration patterns in SCADA and historian integration apply directly here.
Detecting a trend, not a threshold
Now the technical heart of it. The naive way to watch a bearing temperature is a threshold: alarm if it exceeds 85 degrees. This is how most systems ship, and it is why most systems get muted by month three. A fixed threshold fires on every transient, every hot afternoon, every startup ramp, every sensor glitch. Operators get a hundred alarms a shift, ninety-eight of which are nothing, so they learn to ignore all hundred. When the real one comes, it is buried in the flood and dismissed with the rest. The threshold did not fail to fire. It fired so often it trained everyone to stop looking.
What you actually care about is not the absolute value, it is the trend. A bearing that climbs two degrees an hour and holds that climb is failing, even at 60 degrees, long before it ever touches 85. A bearing sitting steady at 82 because the plant is running hot today is fine. Threshold logic gets both of these exactly backwards. Trend logic gets both right.
KQL has native series functions for this. The pattern is: build a time series with make-series, fill gaps, fit a line to it, and act on the slope and fit quality rather than the raw reading.
Telemetry
| where Timestamp between (ago(6h) .. now())
| where TagId == "PUMP-07.MDE.BEARING.TEMP"
| make-series Temp = avg(Value) default=real(null)
on Timestamp step 2m
| extend Temp = series_fill_linear(Temp)
| extend (RSquare, Slope) = series_fit_line(Temp)
// Slope is degrees per 2-minute step; 30 steps make an hour
| extend DegPerHour = Slope * 30
| where DegPerHour > 1.5 and RSquare > 0.70
| project TagId, DegPerHour = round(DegPerHour, 2), RSquare = round(RSquare, 2)
Two conditions have to hold together. DegPerHour > 1.5 says the temperature is genuinely climbing, not just noisy. RSquare > 0.70 says the rise is a real trend that the line fits well, not a jagged mess that happens to average upward. Requiring both is what keeps this quiet. A hot afternoon produces a high reading but a near-zero slope, so it stays silent. A flapping sensor produces slope but terrible fit, so it stays silent. Only a steady, well-fitted climb passes, which is exactly the signature of a bearing on its way out.
This is the query you hand to Activator. It returns nothing on a normal day and one row when a machine starts to degrade, hours before a threshold would have caught it and without the ninety-eight false alarms that would have gotten the whole system switched off. Tune the two numbers per asset class; a slow-turning gearbox and a high-speed pump have very different "normal" slopes.
The caution that comes with trend logic
Trend detection is not free of false positives, it just has different ones. A genuine process change, a new production regime, a recalibrated sensor, can all present as a trend. Always carry the evidence into the work order (the slope, the fit, a link to the chart) so the technician can dismiss a false alarm in seconds rather than walking to the asset blind. And never let a trend rule be the only thing standing between you and a failure that has a hard consequence. That is the next section.
Where Fabric stops and protection begins
This is the line I draw in every one of these projects, usually more than once, because the demo makes it look like Fabric can do everything. It cannot, and pretending otherwise is how people get hurt or how equipment gets wrecked.
Fabric is the analytics layer. It sees your telemetry a few seconds after the fact, it is superb at spotting slow degradation across weeks of history, and it is the right place to turn "this bearing is trending badly" into a planned maintenance job before anything breaks. That is real value and it is genuinely hard to do well.
Fabric is not a protection system. A machine-protection platform (think a vibration monitoring rack wired directly to the machine, or a safety instrumented system on the PLC) operates in milliseconds, is deterministic, is safety-rated, and will trip a machine to a safe state without asking a cloud service for permission. It does not depend on a gateway publish interval, an ingest queue, or an internet link. It cannot, because a bearing seizing at 3000 rpm does not wait three to eight seconds for a KQL query.
So the division of labour is clean. The protection system prevents catastrophic failure in real time. Fabric prevents the failure from ever getting that far, by catching the trend days earlier and raising a work order while the fix is still cheap. They are complementary, and they run on completely different hardware for completely different reasons. If anyone proposes routing a trip signal through Fabric, or presents a Fabric dashboard as the reason a protection rack can be decommissioned, stop the conversation. That is the mistake that turns a good analytics project into an incident report.
Putting it together
A telemetry project on Fabric works when four decisions are made deliberately. You budget the latency on every hop and stop promising speeds the architecture cannot deliver. You tier retention by tag criticality so you pay for hot storage only where it earns its keep. You wire detection to a work order with an owner, not a dashboard nobody watches or an email nobody reads. And you detect trends, not thresholds, so the system stays quiet enough that people still trust it in month three. Get those four right and Fabric earns its place as the analytics layer over your plant. Keep it in that lane, above the protection systems and never in their path, and it will pay for itself in downtime avoided.
Independence and currency
I have no commercial relationship with Microsoft and receive nothing for recommending Fabric or anything else. The views here are my own, from building these pipelines on real plants. Because Fabric's licensing, SKU names, feature surface and Real-Time Intelligence quotas change quickly, treat every specific in this article as verified only as of 1 August 2026, and confirm against the current official docs before you commit.
Official references (canonical docs, current at time of writing):
Written by Muhammad Abbas
CMMS / CAFM Manager & Enterprise Integration Specialist · 22+ years across ERP, EAM, CAFM and enterprise integration.
Work with me