DP-700 · MÓDULO 10 🔥

Trabajar con datos en tiempo real en un Eventhouse de Fabric

El motor Kusto por dentro: KQL databases, ingesta, optimización de queries, materialized views, stored functions, update policies y todas las policies que controlan el comportamiento. 🌸

Intermedio Avanzado
🎯

1. Objetivos y encaje en el DP-700

¿Qué te enseña este módulo?

Este es el módulo del motor Kusto por dentro. Vamos a profundizar en:

  • La estructura del Eventhouse.
  • Cómo escribir KQL efectivo y optimizado.
  • Features avanzadas: materialized views, stored functions, update policies.
  • Todas las policies que controlan el comportamiento de las KQL databases.

Los objetivos oficiales:

  1. Crear un Eventhouse en Microsoft Fabric.
  2. Escribir queries KQL efectivas.
  3. Entender cómo usar materialized views y stored functions en una KQL database.

Peso en el examen DP-700

DominioCómo aparece
Implement and manage (30-35%)RLS/CLS/OLS en KQL, permisos, policies
Ingest and transform (30-35%)Queries KQL, update policies, ingestion policies, materialized views
Monitor and optimize (30-35%)Optimización de queries, cache policy, retention, partitioning, materialized views para performance
🎯 Qué es CRÍTICO dominar:
  • Optimización de KQL (filter early, project early, orden de joins)
  • Materialized views vs stored functions vs update policies — cuándo cada uno
  • Todas las policies y su propósito (salen en preguntas de escenario)
  • Row-Level Security en KQL
  • Cache policy y retention policy (para performance y compliance)
🏛️

2. Anatomía del Eventhouse (repaso)

Recordatorio

Ya vimos el Eventhouse en el Módulo 8. Repaso rápido: un Eventhouse es un contenedor que hospeda una o más KQL databases.

Contenido de una KQL database

Cada KQL database puede contener:

  • Tables (donde viven los datos)
  • Stored procedures
  • Materialized views
  • Stored functions
  • Data streams
  • Shortcuts (a otras KQL databases o storage externo)

KQL queryset (embebido)

Con cada KQL database viene un KQL queryset embebido para escribir y ejecutar queries.

⚠️ Un Eventhouse ≠ una KQL database. Ojo cosita con esta distinción: el Eventhouse es el contenedor y la KQL database es el objeto donde viven los datos. Un Eventhouse puede tener varias KQL databases (para separar dominios, permisos, etc.).
💾

3. KQL databases: default y adicionales

Database default automática

Cuando creas un Eventhouse, se crea automáticamente una KQL database por defecto con el mismo nombre. Puedes usar esa o crear otras adicionales.

Por qué varias databases en un Eventhouse

  • Separar dominios: sales_db, iot_db, logs_db.
  • Permisos distintos: cada database con sus roles.
  • Retención distinta: unas con 30 días, otras con 5 años.
  • Compartir capacidad: mismo Eventhouse, distintas DBs.

Cómo crear una database adicional

Desde el Eventhouse:

  1. Click en New database.
  2. Elegir tipo: KQL database estándar (con sus propios datos) o database shortcut (referencia a otra database).
  3. Configurar nombre y descripción.
📥

4. Ingesta de datos: todas las formas

Hay muchísimas formas de ingestar datos en una KQL database:

Local files

Sube ficheros directamente desde tu equipo: CSV, TSV, JSON, Parquet, Avro, ORC, etc. Ideal para pruebas o cargas puntuales.

Azure Storage

  • Azure Blob Storage con SAS o managed identity.
  • Azure Data Lake Storage Gen2.

Amazon S3

Directamente desde buckets S3. Ingesta multi-cloud sin intermediarios.

Fuentes de streaming

  • Azure Event Hubs
  • Azure IoT Hub
  • Fabric Eventstream (el más común en Fabric)
  • Descubrimiento desde el Real-Time Hub

OneLake

Ingestar desde otros items de Fabric vía OneLake.

Data Factory copy y Dataflows

  • Copy Data activity desde pipelines.
  • Dataflows Gen2 como destino.

Conectores externos

  • Apache Kafka
  • Confluent Cloud Kafka
  • Apache Flink
  • MQTT (Message Queuing Telemetry Transport)
  • Amazon Kinesis
  • Google Cloud Pub/Sub

Menú Get Data

Todo se accede desde el menú Get Data de la KQL database. El asistente de la UI te guía por: elegir origen, configurar la conexión, definir la tabla destino (crear una nueva o usar existente), mapear el schema y configurar una transformación opcional.

🎯 Comparativa rápida

Necesito...Método
Carga puntual de un CSVLocal file upload
Ingesta continua de un streamEventstream o Event Hubs source
Batch periódico desde S3Data Factory pipeline
Datos multi-cloud (Kafka on-prem)Kafka connector
Datos de otro item de FabricOneLake ingestion
🔗

5. Database shortcuts

¿Qué son?

Los database shortcuts son referencias a KQL databases existentes en otros eventhouses o en databases de Azure Data Explorer. La idea: consultar datos de KQL databases externas como si estuvieran almacenados localmente, sin copiarlos realmente.

Casos de uso

  • Analítica cross-workspace: analizar datos de varios workspaces sin duplicar.
  • Migración gradual: exponer un ADX legacy en Fabric sin migrarlo.
  • Data mesh: cada dominio tiene su database y otros la consumen vía shortcuts.
  • Reducir costes de storage: no duplicar datos de acceso frecuente.
⚠️ Diferencia con los shortcuts de OneLake: los OneLake shortcuts (Módulo 1) apuntan a ficheros/carpetas (Delta, Parquet, CSV). Los database shortcuts (KQL) apuntan a KQL databases completas con todas sus tablas. Conceptos distintos, usos distintos.
🌊

6. OneLake availability (repaso profundo)

Recordatorio

Ya vimos la OneLake availability en el Módulo 8. Es la feature del Eventhouse que hace que los datos KQL estén también disponibles en OneLake en formato Delta Parquet, para que otros workloads de Fabric los consuman.

Activación

Se activa a nivel de database completa o a nivel de tabla individual, desde el editor del Eventhouse.

Adaptive Parquet file batching

Para evitar muchos ficheros pequeños, el Eventhouse hace un batching adaptativo.

  • Por defecto: hasta 3 horas de delay, o hasta alcanzar ficheros óptimos (200-256 MB).
  • Configurable: TargetLatencyInMinutes entre 5 minutos y 3 horas.

⚠️ Bajar mucho el delay → muchos ficheros pequeños → lecturas ineficientes en OneLake. Y la tabla es read-only una vez creada.

Integración cross-workload

Cuando activas la OneLake availability, otros workloads de Fabric pueden consumir los datos:

  • Power BI Direct Lake sobre las tablas KQL.
  • El Lakehouse puede crear shortcuts a las tablas KQL.
  • El Warehouse puede hacer joins con datos KQL.
  • Los notebooks pueden leerlas con Spark.

🎯 Cuándo activarla

SÍ activar: cuando necesitas integración cross-Fabric, reports de Power BI con datos KQL o data mesh entre workloads.

NO activar (default): si solo usas KQL para queries, sin integración externa, y quieres minimizar el overhead de escritura.

📝

7. KQL Queryset

Recordatorio

Cada KQL database viene con un queryset embebido. Es el espacio de trabajo para escribir queries.

Features avanzadas

Más allá de escribir queries, el queryset permite:

  • Guardar queries con nombre para uso futuro.
  • Multi-tab: varias pestañas de query simultáneas.
  • Compartir queries con otros usuarios para colaborar.
  • Visualizaciones: renderizar los resultados como gráficos inline.
  • Soportar KQL y T-SQL.

Visualizaciones en el queryset

Puedes convertir los resultados en bar charts, line charts, pie charts, column charts, time charts, cards y tablas. Se hace con el operador | render:

TaxiTrips
| summarize count() by taxi_id
| top 10 by count_
| render columnchart

Se visualiza directamente como column chart.

Querysets standalone

Además del queryset embebido, puedes crear querysets standalone en el workspace. Ventajas: consultar varias databases desde un único queryset y compartir queries entre usuarios independientemente de las databases.

🌸

8. KQL syntax fundamental (repaso profundo)

La filosofía de las pipes 🚰

KQL usa pipes (|) para encadenar operaciones. Los datos fluyen de izquierda a derecha y de arriba a abajo.

tabla
| operador1
| operador2
| operador3

Cada operador trabaja sobre los resultados del anterior. El orden importa mucho.

Ejemplo del concepto de embudo

TaxiTrips
| where fare_amount > 20         -- Filtro 1
| project trip_id, pickup_datetime, fare_amount  -- Selección
| take 10                        -- Limit

Interpretación: empieza con toda la tabla TaxiTrips, filtra a los trips con fare > 20 $, selecciona 3 columnas concretas y devuelve las 10 primeras filas que quedaron.

Query mínima: solo la tabla

TaxiTrips

Devuelve todas las columnas de la tabla, con el número de filas limitado por el default de la herramienta.

take para muestreo

TaxiTrips
| take 100

Devuelve 100 filas (sin orden garantizado). Útil para explorar la estructura sin procesar toda la tabla.

summarize para agregar

TaxiTrips
| summarize trip_count = count() by taxi_id

Cuenta los trips por taxi_id. Devuelve una tabla resumen con dos columnas: taxi_id y trip_count.

Los operadores más usados

OperadorFunción
takeN filas (sin orden garantizado)
whereFiltrar filas por condición
projectSeleccionar columnas concretas
extendAñadir columnas calculadas
summarizeAgregar (equivalente a GROUP BY)
sort by / order byOrdenar
topTop N con orden
limitLimitar el output
joinUnir tablas
unionCombinar tablas
renderVisualizar
countContar filas
distinctValores únicos

Funciones temporales críticas

| where timestamp > ago(30min)        // Últimos 30 min
| where timestamp > ago(1h)           // Última hora
| where timestamp > ago(1d)           // Último día
| where timestamp > ago(7d)           // Últimos 7 días
| where timestamp between (datetime(2026-01-01) .. datetime(2026-01-31))

bin() para agrupar por bucket temporal:

| summarize count() by bin(timestamp, 5m)
// Agrupa por bucket de 5 minutos

También: now(), ago(x), startofday(), endofday(), startofweek() y startofmonth().

Operadores de string

| where name contains "kawaii"       // Contains (case-insensitive)
| where name has "kawaii"            // Búsqueda por palabra completa (más rápido)
| where name startswith "kaw"        // Empieza por
| where name endswith "ii"           // Termina en
| where name matches regex "^k.*i$"  // Regex

has es más eficiente que contains porque busca palabras completas usando el índice.

🎯

9. Case sensitivity (CRÍTICO)

Este es un punto que pilla a MUCHA gente y cae en el examen.

⚠️ KQL es case-sensitive para TODO: nombres de tablas, nombres de columnas, nombres de funciones, operadores, keywords y valores de string. Todos los identificadores deben coincidir exactamente.

Ejemplos de errores comunes

-- ❌ ERROR: la tabla se llama TaxiTrips, no taxitrips
taxitrips
| take 10

-- ✅ CORRECTO
TaxiTrips
| take 10
-- ❌ ERROR: la columna es fare_amount, no Fare_Amount
TaxiTrips
| where Fare_Amount > 20

-- ✅ CORRECTO
TaxiTrips
| where fare_amount > 20

Los valores de string también

-- Estos son valores DISTINTOS
| where vendor_id == "VTS"    -- Match exacto
| where vendor_id == "vts"    -- No hace match (valor distinto)

Si necesitas un matching case-insensitive:

| where vendor_id =~ "vts"    -- =~ es igualdad case-insensitive

Consecuencia práctica

muy consistente con el naming de tus schemas. Si defines una tabla como TaxiTrips, refiérete siempre a ella como TaxiTrips.

10. Best practices de queries

Por qué importa la optimización

El rendimiento en las KQL databases depende de la cantidad de datos procesados. Entender cómo KQL procesa los datos te permite escribir queries que:

  • Van más rápido: reducen los datos escaneados (millones → miles).
  • 💰 Usan menos recursos: seleccionar 3 columnas en vez de 50 reduce el overhead.
  • 📈 Funcionan bien conforme crecen los datos: una query que hoy va bien con 1M de filas sigue yendo bien con 10M.
📌 Principio clave: cuantos menos datos procese tu query, más rápido irá.

Los 4 pilares de la optimización

  1. Filtrar pronto y de forma efectiva (sección 11).
  2. Reducir columnas pronto (sección 12).
  3. Optimizar las aggregations (sección 13).
  4. Optimizar los joins (sección 14).
🔍

11. Filter early: reducir data escaneada

Idea central

Filtrar reduce la cantidad de datos que tienen que procesar las operaciones siguientes. Las KQL databases usan índices y técnicas de organización de datos que hacen el filtrado temprano especialmente eficiente.

Filtrado temporal primero

El filtrado por tiempo es especialmente efectivo porque los Eventhouses típicamente contienen datos de series temporales (indexados por tiempo).

Bien ✅:

TaxiTrips
| where pickup_datetime > ago(30min)   // Filtra primero: usa el índice temporal
| project trip_id, vendor_id, pickup_datetime, fare_amount
| summarize avg_fare = avg(fare_amount) by vendor_id

El filtro temporal primero usa el índice de tiempo → salta cantidades enormes de datos.

Ordenar por selectividad

Ordena los filtros por cuántos datos eliminan. Como un embudo: pon primero el filtro que elimina más filas y luego los más específicos sobre lo que queda.

TaxiTrips
| where pickup_datetime > ago(1d)     // Filtro temporal primero: elimina la mayoría
| where vendor_id == "VTS"            // Vendor concreto: elimina más
| where fare_amount > 0               // Filtro de valor: elimina menos
| summarize trip_count = count()

Antipatrón: filtrar tarde

Mal ❌:

TaxiTrips
| project trip_id, pickup_datetime, fare_amount   // Proyecta primero
| summarize avg_fare = avg(fare_amount) by vendor_id  // Agrega primero
| where pickup_datetime > ago(30min)   // Filtra al final ❌

Aquí el filtro no evita el procesamiento de las filas antiguas: procesas todo antes de filtrar.

📏

12. Reduce columns early

Idea central

Proyectar (seleccionar) solo las columnas que necesitas reduce el uso de recursos. Especialmente importante cuando trabajas con tablas anchas (muchas columnas).

Ejemplo

Bien ✅:

TaxiTrips
| project trip_id, pickup_datetime, fare_amount   // Selecciona columnas pronto
| where pickup_datetime > ago(1d)                  // Después filtra
| summarize avg_fare = avg(fare_amount)

Proyectar pronto → procesar solo 3 columnas en vez de 50.

Cuándo combinarlo con el filtro

Mejor patrón: proyectar pronto + filtrar pronto, juntos. KQL lo optimizará bien.

TaxiTrips
| where pickup_datetime > ago(30min)              // Filtra primero
| project trip_id, vendor_id, pickup_datetime, fare_amount   // Proyecta después
| summarize avg_fare = avg(fare_amount) by vendor_id

En la práctica el optimizador los combina, pero es mejor escribir explícitamente el project temprano.

📊

13. Optimizar aggregations

Limitar resultados al explorar

Las aggregations son intensivas en recursos porque combinan muchos datos. Al explorar, limita los resultados:

TaxiTrips
| where pickup_datetime > ago(1d)
| summarize trip_count = count() by trip_id, vendor_id
| limit 1000                       // Limit para exploración

Sin limit, si hay muchas combinaciones, puedes tener millones de filas en el output.

Usar top en vez de sort + limit

Bien ✅:

TaxiTrips
| summarize count() by vendor_id
| top 10 by count_ desc

Menos eficiente:

TaxiTrips
| summarize count() by vendor_id
| sort by count_ desc
| take 10

top está optimizado exactamente para este caso de uso.

Varias aggregations en un solo summarize

Mejor ✅:

TaxiTrips
| summarize
    trip_count = count(),
    avg_fare = avg(fare_amount),
    max_fare = max(fare_amount),
    total = sum(fare_amount)
  by vendor_id

En vez de hacer 4 queries separadas.

🔀

14. Optimizar joins (el orden importa)

📌 🎯 Regla clave: la tabla más PEQUEÑA primero. Cuando haces join de tablas, pon la más pequeña primero. Razón: KQL procesa la primera tabla para hacer match con la segunda. Empezar con una tabla pequeña significa menos filas que procesar → join más eficiente.

Ejemplo

Bien ✅ (tabla pequeña primero):

// VendorInfo es pequeña (1000 vendors), TaxiTrips es grande (10M trips)
VendorInfo
| join kind=inner TaxiTrips on vendor_id

Evitar ❌:

// Empezar con la grande es menos eficiente
TaxiTrips
| join kind=inner VendorInfo on vendor_id

Filtrar ANTES del join

Otra optimización crucial: filtrar cada tabla antes de hacer el join.

Bien ✅:

VendorInfo
| where region == "NY"                     // Filtra la tabla pequeña
| join kind=inner (
    TaxiTrips
    | where pickup_datetime > ago(1d)      // Filtra la tabla grande
) on vendor_id

Menos eficiente:

VendorInfo
| join kind=inner TaxiTrips on vendor_id
| where region == "NY"                     // Filtra tarde ❌
| where pickup_datetime > ago(1d)          // Filtra tarde ❌

Tipos de join

KQL soporta varios kind=:

  • inner (default): solo los matches (menos filas).
  • leftouter: todos los de la izquierda + los matches de la derecha.
  • rightouter: todos los de la derecha + los matches de la izquierda.
  • fullouter: todos los de ambas.
  • leftanti: filas de la izquierda que NO tienen match en la derecha.
  • rightanti: filas de la derecha que NO tienen match en la izquierda.
  • leftsemi: filas de la izquierda que SÍ tienen match (sin columnas de la derecha).
  • rightsemi: filas de la derecha que SÍ tienen match.
  • innerunique: deduplicado automático en la izquierda.

15. Materialized views a fondo

Este es un tema estrella del módulo y del examen. Presta atención especial 💕

El problema que resuelven

Las KQL databases de los eventhouses contienen a menudo millones o miles de millones de filas de datos en streaming (IoT, logs, eventos). Ejecutar queries de agregación sobre esos datasets cada vez que las consultas consume mucho tiempo y cómputo.

La solución: materialized views

Las materialized views son agregaciones precomputadas que se actualizan automáticamente conforme llegan datos nuevos. En vez de recalcular las métricas desde toda la historia en cada query, la materialized view mantiene los resultados precomputados y solo procesa los datos nuevos para actualizarse.

Resultado: queries instantáneas en dashboards y reports, incluso con datasets enormes.

🌸 Analogía kawaii: piensa en un contador de galletas en la cafetería. Sin materialized view, cada vez que alguien pregunta "¿cuántas galletas hay?", vas y las cuentas todas. Con materialized view, mantienes un cartel actualizado con el recuento y, cuando llega una galleta nueva, sumas 1. Preguntar el recuento es instantáneo.
⚙️

16. Cómo funcionan por dentro

Las dos partes

Una materialized view consta de dos partes que trabajan juntas:

A) Materialized part 💾 — los resultados precomputados de los datos ya procesados, guardados en una "tabla oculta" interna.

B) Delta ⏱️ — los datos nuevos que han llegado desde la última actualización en background. Se procesan al vuelo al consultar.

Al consultar

Cuando haces una query a la materialized view, el sistema combina automáticamente ambas partes en tiempo de query y devuelve resultados frescos y actualizados.

Esto significa que las materialized views SIEMPRE devuelven datos actuales, independientemente de cuándo se ejecutó la última materialización en background.

Proceso en background

Mientras tanto, un proceso en background mueve periódicamente los datos de la parte delta a la parte materializada, manteniendo actualizados los resultados precomputados.

Este enfoque da la velocidad de los resultados precomputados con la frescura de los datos en tiempo real.

Diagrama mental

Streaming ingesta continua
        ↓
    Base table (raw)
        ↓
    ┌───┴───┐
    │       │
Materialized  Delta
   part      part
    (viejo)   (nuevo)
    │       │
    └───┬───┘
        ↓
   Query result (fresh!)

El sistema combina ambas al consultar → resultado siempre actual.

🛠️

17. Crear y consultar materialized views

Sintaxis para crearlas

Una materialized view encapsula un statement summarize de KQL que se auto-actualiza.

.create materialized-view TripsByVendor on table TaxiTrips
{
    TaxiTrips
    | summarize
        trips = count(),
        avg_fare = avg(fare_amount),
        total_revenue = sum(fare_amount)
      by vendor_id, pickup_date = format_datetime(pickup_datetime, "yyyy-MM-dd")
}

Interpretación: crea una materialized view llamada TripsByVendor, basada en la tabla TaxiTrips, que precomputa el recuento de trips, la tarifa media y el revenue total, agrupado por vendor y fecha.

Consultar una materialized view

Una vez creada, se consulta como una tabla normal:

TripsByVendor
| where pickup_date >= ago(7d)
| project pickup_date, vendor_id, trips, avg_fare, total_revenue
| sort by pickup_date desc, total_revenue desc

Sintaxis idéntica a la de una tabla. El motor sabe combinar la parte materializada y la delta de forma transparente.

Cuándo usar materialized views

: agregaciones pesadas sobre datos en streaming, dashboards con métricas que se consultan con frecuencia, y cuando es aceptable un retardo de milisegundos por el refresh en background.

NO: queries ad-hoc únicas, agregaciones muy complejas con lógica cambiante, o cuando necesitas tiempo real exacto milisegundo a milisegundo.

⚠️ Limitaciones: la query dentro de la materialized view debe ser un summarize; los cambios en el schema de la tabla origen pueden romper la view; y consume storage adicional para la parte materializada.
🔧

18. Stored functions

¿Qué son?

KQL permite encapsular una query como una función para facilitar su reutilización. Puedes especificar parámetros para que la función acepte valores variables.

Por qué usarlas

En eventhouses con datos en streaming y varios usuarios escribiendo queries:

  • Evitas escribir la misma lógica de filtrado/transformación una y otra vez.
  • Defines una vez y reutilizas en muchas queries.
  • Aseguras cálculos consistentes cuando varias personas del equipo aplican la misma lógica.

Crear una función

.create-or-alter function trips_by_min_passenger_count(num_passengers:long)
{
    TaxiTrips
    | where passenger_count >= num_passengers
    | project trip_id, pickup_datetime
}

Interpretación: crea (o actualiza) una función llamada trips_by_min_passenger_count, que acepta un parámetro num_passengers de tipo long, filtra los trips con ≥ N pasajeros y proyecta trip_id y pickup_datetime.

Llamar a la función

Se llama como si fuera una tabla:

trips_by_min_passenger_count(3)
| take 10

Encuentra 10 trips con al menos 3 pasajeros.

Casos de uso

  • Lógica de negocio centralizada: "definir un customer VIP", "definir un evento crítico".
  • Cálculos complejos reutilizables: convertir temperaturas, calcular distancia haversine.
  • Patrones de acceso comunes: "top productos del último mes por región".
  • Consistencia de equipo: todos usan la misma función = mismo resultado.

🎯 Materialized view vs stored function

Este contraste es muy examinable:

Materialized viewStored function
Almacena el resultado✅ Sí (precomputado)❌ No
Re-ejecuta al llamarla❌ No (combina partes)✅ Sí (en cada llamada)
Se actualiza automáticamente✅ SíN/A
Coste de storageNo
Coste de cómputoBajo (al consultar)Alto (reejecuta la query)
Ideal paraAgregaciones frecuentesLógica de query reutilizable
🔄

19. Update policies

¿Qué son?

Las update policies son mecanismos de automatización que se disparan cuando se ingiere data en una tabla origen. Ejecutan una query para transformar los datos ingeridos y guardan el resultado en una tabla destino.

Uso típico

Recibes logs raw en una tabla origen; la update policy se dispara, parsea el JSON, extrae campos y aplica lógica; y el resultado procesado va a la tabla destino. Beneficio: automatización en el momento de la ingesta, sin schedule externo.

Sintaxis básica

.alter table CleanLogs policy update
@'[{
    "IsEnabled": true,
    "Source": "RawLogs",
    "Query": "RawLogs | extend timestamp = todatetime(parse_json(payload).timestamp) | project timestamp, level, message",
    "IsTransactional": true,
    "PropagateIngestionProperties": false
}]'

Interpretación: cuando lleguen filas nuevas a RawLogs, ejecuta la query definida y guarda el resultado en CleanLogs.

Management commands

ComandoFunción
.show table X policy updateVer la policy actual
.alter table X policy updateDefinir la policy
.alter-merge table X policy updateAñadir a la policy existente
.delete table X policy updateEliminar la policy

Cuándo se disparan

Las update policies surten efecto cuando se ingieren o mueven datos a la tabla origen. Comandos que las disparan:

  1. .ingest (pull) — desde storage.
  2. .ingest (inline) — datos literales.
  3. .set | .append | .set-or-append | .set-or-replace.
  4. .move extents.
  5. .replace extents.
⚠️ Warning importante con .set-or-replace: por defecto, los datos de las tablas derivadas se reemplazan igual que en la tabla origen, y pueden perderse datos en tablas con relación de update policy. Recomendación: usa .set-or-append siempre que sea posible.

Opciones avanzadas

  • IsTransactional: si es true, la ingesta y la update policy son atómicas.
  • PropagateIngestionProperties: propaga las ingestion properties al destino.
  • ManagedIdentity: usa managed identity para la query (obligatorio si la tabla tiene RLS).
📚

20. Policies overview: todas las policies

Este es un tema muy examinable. Vamos a ver todas las policies que existen en las KQL databases con su propósito.

Tabla oficial completa

PolicyPropósito
Ingestion time policyAñade una columna oculta datetime con la hora de ingesta
ManagedIdentity policyControla qué managed identities pueden usarse y para qué
Merge policyReglas para fusionar datos de distintos extents en uno solo
Mirroring policyGestionar la mirroring policy y sus operaciones
Partitioning policyReglas de particionado de extents para una tabla o materialized view
Retention policyMecanismo que elimina automáticamente datos de tablas o materialized views
Restricted view access policyCapa extra de requisitos de permiso para acceder a la tabla
Row level security policyReglas de acceso a filas según pertenencia a grupos o contexto de ejecución
Row order policyMantiene un orden específico de filas dentro de un extent
Sandbox policyControla el uso y el comportamiento de los sandboxes (entornos aislados)
Sharding policyReglas de cómo se crean los extents
Streaming ingestion policyConfiguración de la ingesta de datos en streaming
Update policyPermite añadir datos a una tabla destino cuando se añaden a la origen
Query weak consistency policyControla el nivel de consistencia de los resultados de query
🎯 Cuáles son las MÁS examinables: Retention policy (limpieza), Cache policy (performance), Row-level security policy (seguridad), Restricted view access policy (seguridad), Update policy (transformación), Streaming ingestion policy (ingesta), Partitioning policy (performance) y Merge policy (performance). Las vemos una a una 💕

21. Retention policy

¿Qué hace?

La retention policy define cuánto tiempo se guardan los datos en una tabla antes de eliminarse automáticamente. Es el equivalente KQL a un TTL (Time-To-Live).

Configuración

Se configura por tabla:

.alter table MyTable policy retention
'{
    "SoftDeletePeriod": "30.00:00:00",
    "Recoverability": "Enabled"
}'
  • SoftDeletePeriod: cuánto tiempo antes de borrar (formato días.horas:minutos:segundos). Ejemplo: 30.00:00:00 = 30 días.
  • Recoverability: si se puede recuperar tras eliminar.

Casos de uso

  • Logs: retención típica de 30-90 días.
  • Telemetría IoT: 7-30 días para el hot, y mover a Lakehouse para el cold.
  • Compliance: retener 7+ años en tablas concretas.
  • Reducir costes de storage: eliminar datos antiguos sin uso.

Automático vs manual

La retention policy hace la limpieza automáticamente en background. No tienes que hacer nada manual.

22. Cache policy

¿Qué hace?

La cache policy controla cuántos datos se mantienen en la caché (premium storage rápido). Datos en caché = queries rapidísimas. Datos fuera de la caché (standard storage) = queries más lentas.

Configuración

.alter table MyTable policy caching hot = 7d

Aquí: mantén los últimos 7 días en hot cache.

Cómo elegir el periodo de caché

Regla: cachea lo que consultas con frecuencia. Si tus dashboards consultan los últimos 30 días → cache = 30d. Si consultas históricos rara vez → caché pequeña y retención larga en standard.

Relación con los storage tiers

Recuerda del Módulo 8: el OneLake Cache Storage (premium) da queries rápidas y es más caro; el OneLake Standard Storage es persistente y más barato. La cache policy determina qué va en la caché.

Ejemplo real

Tabla IoT_Logs con retention policy de 1 año y cache policy de 30 días:

  • Últimos 30 días → caché → queries instantáneas.
  • Días 31-365 → standard storage → queries más lentas pero accesibles.
  • Más de 365 días → eliminados.
🔐

23. Row-Level Security en KQL

¿Qué es?

Row-Level Security (RLS) = mecanismo que filtra las filas visibles a un usuario según su identidad o pertenencia a grupos. Cada usuario ve solo las filas que le corresponden, incluso ejecutando la misma query.

Cómo funciona en KQL

Se define una query de RLS que actúa como filtro implícito. La query devuelve las filas que el usuario actual puede ver.

.alter table SalesData policy row_level_security enable
'AnonymizeSensitiveData'

Donde AnonymizeSensitiveData es una stored function que define la lógica:

.create function AnonymizeSensitiveData() {
    SalesData
    | where region in ((current_principal_details().userPrincipalName |
                       lookup MyRegionMapping on user))
}

Forzar RLS temporalmente

Cuando la policy está deshabilitada, puedes forzarla en una query concreta:

set query_force_row_level_security;
SalesData
| take 100

Útil para testear queries de RLS sin activarla para todos los usuarios.

Permisos requeridos

Para activar/desactivar la RLS policy: permiso Table Admin como mínimo.

⚠️ Limitaciones importantes:
  1. No hay límite en el número de tablas con RLS.
  2. NO se puede configurar en External Tables.
  3. NO se puede activar RLS en tablas que: son referenciadas por una update policy sin managed identity, son referenciadas por un continuous export sin impersonation, o tienen configurada una restricted view access policy.
  4. La query de RLS NO puede referenciar otras tablas que también tengan RLS.
  5. La query de RLS NO puede referenciar tablas de otras databases.

Filter predicates vs block predicates

En KQL de Fabric solo se soportan los filter predicates — filtran las filas silenciosamente. (En SQL Server tradicional existen los block predicates para bloquear escrituras, pero en KQL de Fabric solo hay filter.)

🚫

24. Restricted view access policy

¿Qué hace?

Añade una capa extra de requisitos de permiso para acceder a la tabla. Con esta policy activa, incluso los usuarios con permiso normal necesitan un permiso adicional específico para ver la tabla.

Cuándo usarla

  • Datos muy sensibles que requieren una aprobación extra.
  • Cumplimiento de requisitos de seguridad estrictos.
  • Tablas con datos financieros o PII.
⚠️ Compatibilidad: el RLS y la restricted view access policy son mutuamente excluyentes. No puedes activar ambos en la misma tabla.
🌊

25. Streaming ingestion policy

¿Qué hace?

Configura la ingesta de datos en streaming. Con la streaming ingestion activada, los datos son consultables casi de inmediato (segundos) tras ser ingeridos, en vez de por lotes.

Cómo activarla

A nivel database:

.alter database MyDatabase policy streamingingestion enable

O a nivel tabla:

.alter table MyTable policy streamingingestion enable

Trade-offs

Con streaming ingestion activada: ✅ los datos están disponibles para consulta más rápido, pero ⚠️ hay más overhead de cómputo por fila y es más caro por fila.

Sin streaming (batch): ✅ más barato por fila y optimizado para alto volumen, pero ⚠️ con un retardo de segundos-minutos antes de poder consultar.

Cuándo usar cada uno

  • Streaming ingestion: dashboards en tiempo real, alertas críticas.
  • Batch ingestion: cargas masivas, analítica batch, optimización de costes.
🧩

26. Partitioning policy

¿Qué hace?

Define las reglas para particionar los extents de una tabla o materialized view. Beneficio: las queries que filtran por la partition key son mucho más rápidas (partition pruning).

Uso típico

Particionar por customer_id (si las queries filtran por customer), por tenant_id en escenarios multi-tenant, o por date / month para análisis de series temporales.

Ejemplo

.alter table SalesData policy partitioning
'{
    "PartitionKeys": [
        {
            "ColumnName": "customer_id",
            "Kind": "Hash",
            "Properties": {
                "Function": "XxHash64",
                "MaxPartitionCount": 128
            }
        }
    ]
}'

Cuándo NO particionar

  • Baja cardinalidad: pocas partition keys → poco beneficio.
  • Cardinalidad muy alta: demasiadas partition keys → overhead.
  • Las queries no filtran por la key: sin filtro no hay pruning.
🔀

27. Merge policy

¿Qué hace?

Define las reglas para fusionar datos de distintos extents en uno solo. En KQL los datos se almacenan en extents (bloques); muchos extents pequeños → mal rendimiento. El merge los consolida.

Similar al OPTIMIZE de Delta

Es un concepto parecido al OPTIMIZE de Delta Lake que vimos en el Módulo 4: consolidar ficheros pequeños en grandes. La diferencia: el OPTIMIZE de Delta es manual o programado, mientras que el merge de KQL es un proceso automático en background (configurable).

Configuración

.alter table MyTable policy merge
'{
    "AllowRebuild": true,
    "AllowMerge": true,
    "MaxRangeInHours": 8,
    "MinExtentsCountForMerge": 2,
    "Lookback": "Default"
}'

Los valores por defecto suelen funcionar bien. Solo tócalos si tienes casos específicos.

🤖

28. Copilot para KQL

Copilot for Real-Time Intelligence

Fabric incluye Copilot para RTI, que ayuda a generar queries KQL desde lenguaje natural, explicar queries existentes, sugerir optimizaciones y depurar queries que fallan.

Habilitación

Requiere que el admin habilite Copilot para el tenant/workspace. Una vez habilitado, aparece la opción en la barra de menú del queryset.

Uso

  1. Abrir el panel de Copilot.
  2. Escribir la pregunta en lenguaje natural: "Muéstrame las top 10 ventas del último día por región".
  3. Copilot genera el KQL.
  4. Puedes ejecutarlo directamente o editarlo antes.

NL2KQL

Natural Language to KQL es la feature específica que traduce lenguaje natural a KQL. Está integrada en Copilot. Especialmente útil si vienes de SQL y estás aprendiendo KQL.

⚠️

29. Trampas típicas y confusiones frecuentes

  • Trampa 1: "KQL es case-insensitive como SQL"KQL ES CASE-SENSITIVE PARA TODO: tablas, columnas, funciones, keywords y valores de string. Fundamental.
  • Trampa 2: "Las materialized views se actualizan por schedule" ❌ Se actualizan automáticamente conforme llegan datos. No requieren schedule.
  • Trampa 3: "Las materialized views devuelven datos desactualizados"Siempre devuelven datos actuales, combinando la parte materializada y la delta.
  • Trampa 4: "Las update policies se ejecutan por schedule"Se disparan cuando se ingiere data en la tabla origen. No son schedule-based.
  • Trampa 5: "Las stored functions almacenan resultados" ❌ Almacenan la LÓGICA de la query, no los resultados. Se re-ejecutan en cada llamada.
  • Trampa 6: "Puedo activar RLS y restricted view access policy a la vez" ❌ Son mutuamente excluyentes. Solo uno por tabla.
  • Trampa 7: "El RLS funciona en External Tables"NO se puede configurar RLS en External Tables.
  • Trampa 8: "El RLS puede referenciar otras tablas con RLS" ❌ La query de RLS NO puede referenciar otras tablas con RLS habilitado.
  • Trampa 9: "La cache policy elimina datos" ❌ La cache policy solo controla qué está en la caché premium. NO elimina datos. La que elimina es la retention policy.
  • Trampa 10: "La retention policy elimina datos instantáneamente" ❌ Es un proceso en background. Puede tardar en eliminar. No es en tiempo real.
  • Trampa 11: "Filtrar tarde es igual de eficiente"Filtrar pronto es CRÍTICO para el rendimiento. El orden importa mucho.
  • Trampa 12: "En los joins el orden no importa" ❌ La tabla más pequeña PRIMERO para ser eficiente. Sí importa.
  • Trampa 13: "El streaming ingestion siempre es mejor" ❌ Tiene overhead extra. Para cargas masivas, el batch es más eficiente y barato.
  • Trampa 14: "Cada Eventhouse tiene solo una KQL database" ❌ Un Eventhouse puede tener varias KQL databases. La default se crea automáticamente, pero puedes crear más.
  • Trampa 15: "has y contains son intercambiables"has busca palabras completas usando el índice → más rápido. contains busca la subcadena en cualquier posición. Usa has cuando aplique.
  • Trampa 16: "Las update policies afectan igual a .set-or-replace que a .set-or-append" ⚠️ Con .set-or-replace los datos de las tablas derivadas también se reemplazanpuede perderse data. Recomendación: .set-or-append siempre que se pueda.
  • Trampa 17: "Una materialized view se puede basar en cualquier query" ❌ Debe ser un statement summarize. Otros operadores no valen.
  • Trampa 18: "Puedo cambiar el schema de la tabla origen libremente" ⚠️ Los cambios en el schema del origen pueden romper las materialized views que dependen de él.
🆚

30. Comparativas clave (chuleta)

Materialized view vs Stored function vs Update policy

Materialized viewStored functionUpdate policy
TriggerAuto en background + al consultarOn-demand (llamada)Al ingerir datos
Almacena el resultado✅ Sí❌ No✅ Sí (en la tabla destino)
ActualizaciónAutomática (delta + materializada)N/AAutomática al ingerir
Tipo de querySolo summarizeCualquier queryCualquier query
Coste de storageNoSí (2 tablas)
Ideal paraAgregaciones frecuentesLógica de query reutilizableTransformación al ingerir

Impacto de filtrar pronto

Posición del filtroDatos procesados
Después del summarizeToda la tabla
Después del projectMenos, pero aún todo
Al principioSolo lo relevante

Orden de los joins

OrdenEficiencia
Tabla pequeña primeroÓptimo
Tabla grande primeroMenos eficiente
Sin filtro previoMalo
Con filtro previo en ambasÓptimo

Todas las policies (chuleta rápida)

PolicyPropósito clave
RetentionEliminar datos antiguos
CacheQué va en la caché premium
Row-level securityFiltrar filas por usuario
Restricted view accessCapa extra de permisos
Update policyAuto-transformar al ingerir
Streaming ingestionDatos consultables de inmediato
PartitioningDividir datos para el pruning
MergeConsolidar extents pequeños
ShardingCómo se crean los extents
Ingestion timeAñade el timestamp de ingesta
Managed identityControl del uso de las MI
MirroringGestiona el mirroring
Row orderOrden específico de filas
SandboxEntornos aislados

Métodos de ingesta

MétodoCuándo
Local file uploadCarga manual puntual
Azure Storage / S3Batch desde storage
EventstreamStreaming continuo
Event Hubs / IoT HubStreaming de alto volumen
Data Factory CopyOrquestado desde un pipeline
Kafka connectorKafka on-prem o multi-cloud
OneLakeCross-workload en Fabric

KQL vs T-SQL en una KQL DB

KQLT-SQL
Case sensitivitySensible en TODOInsensible
FilosofíaPipesAnidado
Funciones de series temporalesNativasLimitadas
Detección de anomalíasNativa
AprendizajeNuevoYa conocido
Uso recomendado✅ PreferidoMigración legacy
📚

31. Fuentes oficiales consultadas

🚀 ¡Módulo 10 completado! Ya dominas el motor Kusto por dentro y cierras la Fase 2 de Real-Time Intelligence. Sigue con el Módulo 11: Data Warehouse. 🌸