RTI Library: architektura
Cíle kapitoly
- Popsat mental model Fabric RTI: Eventhouse, KQL DB, Eventstream, konzumace
- Dodržet naming conventions napříč šesti demy knihovny
- Použít pattern Bronze → Silver (update policy) → materialized view v KQL
- Postavit Custom Endpoint publisher a vyhnout se jeho anti-patternům
- Odhadnout cost a zvládnout start/stop lifecycle workspace
Úvod
Tento dokument popisuje sdílené architektonické rozhodnutí, naming conventions, KQL patterns a anti-patterns používané ve všech 6 runbookech (A-F). Než pojedeš dělat individual runbook, projdi si tohle.
Mental model — jak vypadá Fabric RTI stack
┌──────────────────────────────────────────────────────────────────────┐ │ FABRIC WORKSPACE │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ EVENTHOUSE │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ │ │ KQL Database (je vlastně Azure Data Explorer DB) │ │ │ │ │ │ ├── tables (raw + clean per demo) │ │ │ │ │ │ ├── update policies (raw → clean transform) │ │ │ │ │ │ ├── materialized views (pre-aggregated state) │ │ │ │ │ │ └── functions (reusable KQL snippets) │ │ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ ingest │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ EVENTSTREAM(S) │ │ │ │ Source ────► Transformations ────► Destination │ │ │ │ (input) (filter, enrich) (KQL DB / Lakehouse / etc) │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ events │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ INPUT SOURCES │ │ │ │ • Sample Data (MS native, no setup) → demo A │ │ │ │ • Custom Endpoint (HTTP push) → demos B-E │ │ │ │ • Azure Event Hub / IoT Hub / Kafka / CDC → not used │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ CONSUMPTION │ │ │ │ • Real-Time Dashboard (KQL native, sub-second refresh) │ │ │ │ • Power BI (DirectQuery na KQL DB) │ │ │ │ • Activator (Reflex) → Teams/email/HTTP webhook │ │ │ │ • Eventstream output back → Lakehouse (for batch reuse) │ │ │ └────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────┘
Kdy co použít
| Use case | Volba |
|---|---|
| Demo / proof-of-concept | Sample Data → Eventstream preview, žádné destination |
| Real-time poll z public HTTP endpointu | Notebook poller → Custom Endpoint → Eventstream → KQL DB |
| Real-time push z device/IoT | Device pošle přímo na Custom Endpoint URL → Eventstream → KQL DB |
| Sub-sekundový dashboard | Real-Time Dashboard nad KQL DB |
| Komplexní reporty s historií | PBI DirectQuery na KQL DB + composite model s Direct Lake na Lakehouse gold |
| Alerting / automation | Activator (Reflex) na KQL stream |
Kdy NEpoužít Fabric RTI
- Batch ETL — Lakehouse + Spark / Warehouse + T-SQL je levnější a expresivnější
- Sub-millisekundové latency (high-frequency trading) — Fabric RTI cílí stovky ms, ne mikrosekundy
- Strukturované transakce (CRUD na entity) — to je SQL DB / Cosmos use case
Naming conventions
Workspace
DP600_Demo_RTI # main workspace pro celou library
Eventhouse / KQL DB
eh_rti_demo # single Eventhouse pro celou library └── kql_rti_demo # single KQL DB
Eventstreams
es_a_bicycles # demo A es_b_chmi # demo B es_c_ndic # demo C es_d_alpr # demo D es_e_pid # demo E
KQL tables — table prefix per demo
a_bicycles_raw # demo A (pokud chceš sink) b_chmi_raw b_chmi_clean # demo B c_ndic_raw c_ndic_clean d_alpr_raw d_alpr_clean e_pid_raw e_pid_clean
Materialized views
mv_b_chmi_last_per_station # latest reading per station mv_c_ndic_avg_5min_per_loop # 5-min avg counts per traffic loop mv_d_alpr_count_per_camera_5min # 5-min count per camera mv_e_pid_active_vehicles # currently active vehicles
Functions
fn_kraj_from_idreg(idReg:int) # lookup helper fn_speed_class(kmh:int) # bucketize
Notebooks
nb_b_chmi_publisher # publisher pro demo B nb_c_ndic_publisher nb_d_alpr_simulator nb_e_pid_adapter
Real-Time Dashboards
rtd_b_chmi_live rtd_d_alpr_live
Power BI semantic models / reports
sm_rti_combined # composite model nad celou KQL DB rpt_b_chmi_realtime rpt_d_alpr_realtime
Bronze → Silver → MV pattern v KQL
Stejný medallion princip jako v Days 1-2, ale realizovaný KQL nativnimi nástroji.
Bronze = raw table
- Schema reflektuje to, co přijde z Eventstreamu
- Žádné transformace, žádný join
- Retention typicky 7-30 dní (krátká, raw je velký)
.create-merge tablepattern (idempotentní)
Silver = clean table + update policy
- Schema reflektuje business model (typed columns, parsed timestamps, joined dimensions)
- Update policy = stored proc, která se spustí při každém ingestu do Bronze a transformovaná data inkrementálně přidá do Silver
- Retention typicky 90-365 dní
MV = materialized view nad Silver
- Pre-aggregated, denormalized state (např.
last position per vehicle,5-min avg per sensor) - Updatuje se inkrementálně, query ji vrátí v ms
- Použij pro dashboardy a alert rules
Anti-patterns (don't do this)
externaldata() ani external tables. Lookupy proto nejdřív materializujte (.set-or-replace dim_pollutant <| external_table('dim_pollutant_ext')). Pokud .alter table … policy update odmítne join kvůli streaming ingestion na zdrojové tabulce, vypněte ji: .alter table b_chmi_raw policy streamingingestion disable. Přesné podmínky ověřte v dokumentaci update policies.- ❌ Update policy z Silver do Gold (vícenásobný kaskádování → exploze cost) — radši MV
- ❌ Jenom 1 tabulka pro raw + clean → ztratíš ability zpětně reprocesovat
- ❌ MV bez
lookbackparametru → re-aggreguje VŠE při každém commit, devastující na cost
Custom Endpoint pattern (B/C/D/E)
V Microsoft Fabric (jaro 2026) neexistuje native HTTP poller v Eventstreamu. Pro real-time ingestion z external HTTP endpointů používáme tento pattern:
┌─────────────────┐ ┌──────────────────┐
│ External API │ │ Fabric Notebook │
│ (HTTP endpoint) │ ←── poll ──── │ (Pure Python) │
└─────────────────┘ └──────────────────┘
│
│ POST JSON events
▼
┌──────────────────┐
│ Eventstream │
│ Custom Endpoint │ ← UNIQUE URL + KEY
│ (HTTP push input)│
└──────────────────┘
│
▼
┌──────────────────┐
│ KQL DB tables │
└──────────────────┘Custom Endpoint setup
- V Eventstreamu:
Add source → Custom App(pojmenování: Custom Endpoint nebo Custom App podle UI verze) - Eventstream vyhradí unique ingestion URL + access key (zobrazí se v "Sample data" panel)
- Z Notebooku posíláš HTTP POST na ten URL
- Eventstream events okamžitě propíše do destination (KQL DB)
Notebook publisher boilerplate
import requests import json import time from datetime import datetime, timezone EVENTSTREAM_URL = "https://<your-region>.eventstream.fabric.microsoft.com/..." EVENTSTREAM_KEY = mssparkutils.credentials.getSecret("kv-rti", "es-key-b") # uložené v KeyVaultu def publish_event(event: dict): """Send single event to Eventstream Custom Endpoint.""" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {EVENTSTREAM_KEY}", } r = requests.post(EVENTSTREAM_URL, headers=headers, data=json.dumps(event), timeout=10) r.raise_for_status() def publish_batch(events: list[dict]): """Send batch (newline-delimited JSON).""" headers = { "Content-Type": "application/x-ndjson", "Authorization": f"Bearer {EVENTSTREAM_KEY}", } body = "\n".join(json.dumps(e) for e in events) r = requests.post(EVENTSTREAM_URL, headers=headers, data=body, timeout=30) r.raise_for_status()
Cost & latency notes
-
Eventstream charge je CU per event, ne per second. Pro F2 capacity je rozumný strop ~10k events/min sustained.
-
End-to-end latency Notebook → KQL = 2-10 sekund typicky.
- Notebook poller na F2 má cold start 1-2 min — pro continuous run použij scheduled run, ne ad-hoc.
Anti-patterns
- ❌ Notebook s
while True: time.sleep(5)— sežere capacity i když nic nejede - ❌ Sending events 1-by-1 v loop bez batch — 10× horší throughput
- ❌ Hardcoded Eventstream key v Notebook source — bezpečnostní problem
- ❌ Žádný retry → první 5xx error a poller umře
KQL update policy template
// Step 1: Bronze table (Eventstream destination) .create-merge table b_chmi_raw ( EventEnqueuedUtcTime: datetime, idRegistration: int, startTime: datetime, idValueType: string, value: real ) // Step 2: Silver table (typed, joined, business model) .create-merge table b_chmi_clean ( ingest_ts: datetime, measurement_ts: datetime, idRegistration: int, pollutant_code: string, pollutant_name: string, value: real, unit: string, eu_limit: real, over_limit: bool ) // Step 3: Update policy function (transforms raw -> clean) .create-or-alter function fn_b_chmi_transform() { b_chmi_raw | extend ingest_ts = EventEnqueuedUtcTime | extend measurement_ts = todatetime(startTime) // dim_pollutant = bezna KQL tabulka (lookup nahrany .set-or-replace, viz runbook B); // update policy nesmi cist externaldata ani external tables | join kind=leftouter dim_pollutant on $left.idValueType == $right.pollutant_code | extend over_limit = (value > eu_limit) | project ingest_ts, measurement_ts, idRegistration, pollutant_code, pollutant_name, value, unit, eu_limit, over_limit } // Step 4: Wire policy .alter table b_chmi_clean policy update @'[{"IsEnabled":true,"Source":"b_chmi_raw","Query":"fn_b_chmi_transform()","IsTransactional":true,"PropagateIngestionProperties":true}]' // Step 5: Materialized view nad Silver .create-or-alter materialized-view with (backfill=true) mv_b_chmi_last_per_station on table b_chmi_clean { b_chmi_clean | summarize arg_max(measurement_ts, *) by idRegistration, pollutant_code }
Time-series KQL primer (pro Day 4 demo)
Top 5 funkcí, které musíš ukázat:
// 1. summarize bin() — equivalent SQL GROUP BY date_trunc b_chmi_clean | where measurement_ts > ago(24h) | summarize avg(value) by bin(measurement_ts, 1h), pollutant_code // 2. make-series — vyrovnaná time series s gaps b_chmi_clean | where pollutant_code == "PM10" | make-series avg(value) on measurement_ts step 1h by idRegistration // 3. arg_max / arg_min — latest / earliest per group b_chmi_clean | summarize arg_max(measurement_ts, *) by idRegistration // 4. percentiles b_chmi_clean | summarize percentiles(value, 50, 95, 99) by pollutant_code // 5. series_decompose / series_detect_anomalies b_chmi_clean | make-series val=avg(value) on measurement_ts step 1h | extend (anomalies, score, _) = series_decompose_anomalies(val, 1.5)
KQL ↔ DAX/SQL analogie v KQL cheat sheetu (runbook 22).
Cost guidance
| Aktivita | Capacity Units (F2 baseline) |
|---|---|
| Eventstream idle | 0 CU/s |
| Eventstream ingesting 1k events/min | ~1-2 CU/s |
| KQL DB query (interactive) | spike 1-5 CU/s, < 1s |
| Real-Time Dashboard auto-refresh (10s) | sustained 0.5-1 CU/s per dashboard |
| Notebook publisher running | spark cluster cost (cold start 1-2 min) |
| Activator alert evaluation | < 0.5 CU/s |
F2 (2 CU) je dostačující pro 1 demo running současně. Pro 2+ paralelně přepni na F4+ trial.
Workspace lifecycle (start/stop pattern)
Pokud F2 kapacita běží v start/stop módu:
- Před demo: start capacity (~30s), warmup query do KQL DB
- Eventstream + KQL se zaktivuje automaticky
- Po demu: stop capacity → veškerá data v KQL DB zůstanou (storage je perzistentní), ale ingestion zastaví
- Při restartu: Eventstream auto-resume od kde přestal (až do retention limitu)
Troubleshooting top 5
- „Events nepříchází do KQL" → check Eventstream "Data preview" tab. Pokud tam jsou events ale KQL prázdný, je problém v destination mapping nebo schema mismatch.
- „Update policy nezískává nic" → spusť update policy function ručně:
fn_b_chmi_transform() | take 10. Pokud funkce vrátí data ale policy nic, problém vIsEnablednebo schema mismatch. - „Notebook poller selhává po 5 minutách" → spark session timeout. Použij Pure Python notebook (ne PySpark), který má vlastní lifecycle.
- „Real-Time Dashboard refresh laguje" → na F2 je sustained < 1k events/min. Při větším provozu přepni na F4+.
- „Custom Endpoint URL vrací 401" → zkontroluj že posíláš
Authorization: Bearer <key>, ne?key=...query param.
References
- KQL language: https://learn.microsoft.com/kusto/query/
- Eventstream sources: https://learn.microsoft.com/fabric/real-time-intelligence/event-streams/add-source-custom-app
- Update policies: https://learn.microsoft.com/kusto/management/update-policy
- Materialized views: https://learn.microsoft.com/kusto/management/materialized-views/materialized-view-overview
- Real-Time Dashboard: https://learn.microsoft.com/fabric/real-time-intelligence/dashboard-real-time-create
- Activator (Reflex): https://learn.microsoft.com/fabric/data-activator/data-activator-introduction
Setup KQL DB — pořadí skriptů a smoke test
Kompletní obsah souboru — rozbalte a zkopírujte tlačítkem Kopírovat.
// Master setup — poradi skriptu (KQL setup je v kazdem runbooku A-E) // Master setup script — vytvoří všechny tabulky + update policies + MVs // pro celou RTI Library v jednom průchodu. // // Idempotentní (`.create-merge`, `.create-or-alter`). // // Použití: // 1. V Fabric workspace otevři KQL DB `kql_rti_demo` // 2. New query → paste tento script → Run // 3. Per demo (A-E): spust KQL setup skript z prislusneho runbooku // // POZN: KQL `.execute database` syntax pro chained scripts není jednoduchý. // V praxi: zkopiruj KQL setup z runbooku 16-20 postupne. // Tento file je jen index s pořadím: // ────────────────────────────────────────────────────────────────── // Order of execution // ────────────────────────────────────────────────────────────────── // 1. runbook 16 (A — Bicycles) — 1 table // 2. runbook 17 (B — CHMU polling) — 2 tables + 1 MV + 2 functions // 3. runbook 18 (C — NDIC) — 2 tables + 2 MVs + 2 functions // 4. runbook 19 (D — Camera ALPR) — 2 tables + 2 MVs + 1 function // 5. runbook 20 (E — PID GTFS-RT) — 2 tables + 2 MVs + 1 function // (F nemá vlastní KQL — bolt-on Activator nad B/D) // ────────────────────────────────────────────────────────────────── // Verification queries (run after all 5 setups) // ────────────────────────────────────────────────────────────────── // Tables exist? .show tables // Update policies wired? .show table b_chmi_clean policy update .show table c_ndic_clean policy update .show table d_alpr_clean policy update .show table e_pid_clean policy update // MVs exist? .show materialized-views // Functions exist? .show functions // ────────────────────────────────────────────────────────────────── // Smoke test (run after publishers běží alespoň 5 min) // ────────────────────────────────────────────────────────────────── // A // a_bicycles_raw | count // B // b_chmi_raw | count // b_chmi_clean | count // mv_b_chmi_last_per_station | count // C // c_ndic_raw | count // c_ndic_clean | count // D // d_alpr_raw | count // d_alpr_clean | count // mv_d_alpr_speeders | count // E // e_pid_raw | count // e_pid_clean | count // mv_e_pid_active_vehicles | count