Compartir a través de


Procedimientos recomendados para la semántica de grafos de Lenguaje de consulta Kusto (KQL)

En este artículo se explica cómo usar la característica de semántica de grafos en KQL de forma eficaz y eficaz para diferentes casos de uso y escenarios. Muestra cómo crear y consultar gráficos con la sintaxis y los operadores, y cómo integrarlos con otras funciones y características de KQL. También ayuda a los usuarios a evitar errores o errores comunes, como crear gráficos que superen los límites de memoria o rendimiento, o aplicar filtros, proyecciones o agregaciones no adecuados o incompatibles.

Tamaño del grafo

El operador make-graph crea una representación en memoria de un grafo. Consta de la propia estructura del grafo y sus propiedades. Al crear un gráfico, use filtros, proyecciones y agregaciones adecuados para seleccionar solo los nodos y bordes pertinentes y sus propiedades.

En el ejemplo siguiente se muestra cómo reducir el número de nodos y bordes y sus propiedades. En este escenario, Bob cambió el administrador de Alice a Eve y el usuario solo quiere ver el estado más reciente del gráfico para su organización. Para reducir el tamaño del grafo, los nodos se filtran primero por la propiedad organization y, a continuación, la propiedad se quita del grafo mediante el operador project-away. Lo mismo sucede con los bordes. A continuación, se usa el operador summarize junto con arg_max para obtener el último estado conocido del grafo.

let allEmployees = datatable(organization: string, name:string, age:long)
[
  "R&D", "Alice", 32,
  "R&D","Bob", 31,
  "R&D","Eve", 27,
  "R&D","Mallory", 29,
  "Marketing", "Alex", 35
];
let allReports = datatable(employee:string, manager:string, modificationDate: datetime)
[
  "Bob", "Alice", datetime(2022-05-23),
  "Bob", "Eve", datetime(2023-01-01),
  "Eve", "Mallory", datetime(2022-05-23),
  "Alice", "Dave", datetime(2022-05-23)
];
let filteredEmployees =
    allEmployees
    | where organization == "R&D"
    | project-away age, organization;
let filteredReports =
    allReports
    | summarize arg_max(modificationDate, *) by employee
    | project-away modificationDate;
filteredReports
| make-graph employee --> manager with filteredEmployees on name
| graph-match (employee)-[hasManager*2..5]-(manager)
  where employee.name == "Bob"
  project employee = employee.name, topManager = manager.name

Salida

empleado topManager
Bob Mallory

Último estado conocido del gráfico

En el ejemplo Tamaño del grafo se muestra cómo obtener el último estado conocido de los bordes de un grafo mediante summarize el operador y la función de arg_max agregación. La obtención del último estado conocido es una operación de proceso intensivo.

Considere la posibilidad de crear una vista materializada para mejorar el rendimiento de las consultas, como se indica a continuación:

  1. Cree tablas que tengan alguna noción de versión como parte de su modelo. Se recomienda usar una datetime columna que puede usar más adelante para crear una serie temporal de grafos.

    .create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime)
    
    .create table reportsTo (employee:string, manager:string, modificationDate: datetime)
    
  2. Cree una vista materializada para cada tabla y use la función de agregación arg_max para determinar el último estado conocido de los empleados y la relación reportsTo .

    .create materialized-view employees_MV on table employees
    {
        employees
        | summarize arg_max(modificationDate, *) by name
    }
    
    .create materialized-view reportsTo_MV on table reportsTo
    {
        reportsTo
        | summarize arg_max(modificationDate, *) by employee
    }
    
  3. Cree dos funciones que garantizan que solo se use el componente materializado de la vista materializada y se apliquen filtros y proyecciones adicionales.

    .create function currentEmployees () {
        materialized_view('employees_MV')
        | where stateOfEmployment == "employed"
    }
    
    .create function reportsTo_lastKnownState () {
        materialized_view('reportsTo_MV')
        | project-away modificationDate
    }
    

La consulta resultante mediante materializada hace que la consulta sea más rápida y eficaz para gráficos más grandes. También permite una mayor simultaneidad y consultas de menor latencia para el estado más reciente del gráfico. El usuario todavía puede consultar el historial de grafos en función de las tablas employees e reportsTo , si es necesario.

let filteredEmployees =
    currentEmployees
    | where organization == "R&D"
    | project-away organization;
reportsTo_lastKnownState
| make-graph employee --> manager with filteredEmployees on name
| graph-match (employee)-[hasManager*2..5]-(manager)
  where employee.name == "Bob"
  project employee = employee.name, reportingPath = hasManager.manager

Viaje de tiempo del grafo

Algunos escenarios requieren que analice los datos en función del estado de un grafo en un momento dado específico. El recorrido de tiempo del grafo usa una combinación de filtros de tiempo y resume el uso de la función de agregación arg_max.

La siguiente instrucción KQL crea una función con un parámetro que define el momento dado interesante para el grafo. Devuelve un gráfico listo.

.create function graph_time_travel (interestingPointInTime:datetime ) {
    let filteredEmployees =
        employees
        | where modificationDate < interestingPointInTime
        | summarize arg_max(modificationDate, *) by name;
    let filteredReports =
        reportsTo
        | where modificationDate < interestingPointInTime
        | summarize arg_max(modificationDate, *) by employee
        | project-away modificationDate;
    filteredReports
    | make-graph employee --> manager with filteredEmployees on name
}

Con la función en contexto, el usuario puede crear una consulta para obtener el administrador superior de Bob en función del gráfico en junio de 2022.

graph_time_travel(datetime(2022-06-01))
| graph-match (employee)-[hasManager*2..5]-(manager)
  where employee.name == "Bob"
  project employee = employee.name, reportingPath = hasManager.manager

Salida

empleado topManager
Bob Dave

Tratar con varios tipos de nodo y borde

A veces es necesario contextualizar los datos de serie temporal con un gráfico que consta de varios tipos de nodo. Una manera de controlar este escenario es crear un gráfico de propiedades de uso general representado por un modelo canónico.

En ocasiones, es posible que tenga que contextualizar los datos de series temporales con un gráfico que tenga varios tipos de nodo. Puede abordar el problema mediante la creación de un gráfico de propiedades de uso general basado en un modelo canónico, como el siguiente.

  • Nodos
    • nodeId (cadena)
    • label (string)
    • propiedades (dinámicas)
  • Bordes
    • source (string)
    • destination (string)
    • label (string)
    • propiedades (dinámicas)

En el ejemplo siguiente se muestra cómo transformar los datos en un modelo canónico y cómo consultarlos. Las tablas base de los nodos y bordes del grafo tienen esquemas diferentes.

Este escenario implica a un administrador de fábrica que quiere averiguar por qué el equipo no funciona bien y quién es responsable de corregirlo. El administrador decide usar un gráfico que combina el gráfico de activos del piso de producción y la jerarquía del personal de mantenimiento que cambia todos los días.

En el gráfico siguiente se muestran las relaciones entre los recursos y su serie temporal, como la velocidad, la temperatura y la presión. Los operadores y los recursos, como la bomba, están conectados a través del borde operativo . Los propios operadores informan a la administración.

Infografía en el escenario del gráfico de propiedades.

Los datos de esas entidades se pueden almacenar directamente en el clúster o adquirirse mediante la federación de consultas en un servicio diferente, como Azure Cosmos DB, Azure SQL o Azure Digital Twin. Para ilustrar el ejemplo, se crean los siguientes datos tabulares como parte de la consulta:

let sensors = datatable(sensorId:string, tagName:string, unitOfMeasuree:string)
[
  "1", "temperature", "°C",
  "2", "pressure", "Pa",
  "3", "speed", "m/s"
];
let timeseriesData = datatable(sensorId:string, timestamp:string, value:double, anomaly: bool )
[
    "1", datetime(2023-01-23 10:00:00), 32, false,
    "1", datetime(2023-01-24 10:00:00), 400, true,
    "3", datetime(2023-01-24 09:00:00), 9, false
];
let employees = datatable(name:string, age:long)
[
  "Alice", 32,
  "Bob", 31,
  "Eve", 27,
  "Mallory", 29,
  "Alex", 35,
  "Dave", 45
];
let allReports = datatable(employee:string, manager:string)
[
  "Bob", "Alice",
  "Alice", "Dave",
  "Eve", "Mallory",
  "Alex", "Dave"
];
let operates = datatable(employee:string, machine:string, timestamp:datetime)
[
  "Bob", "Pump", datetime(2023-01-23),
  "Eve", "Pump", datetime(2023-01-24),
  "Mallory", "Press", datetime(2023-01-24),
  "Alex", "Conveyor belt", datetime(2023-01-24),
];
let assetHierarchy = datatable(source:string, destination:string)
[
  "1", "Pump",
  "2", "Pump",
  "Pump", "Press",
  "3", "Conveyor belt"
];

Los empleados, sensores y otras entidades y relaciones no comparten un modelo de datos canónico. Puede usar el operador union para combinar y canonizar los datos.

La consulta siguiente combina los datos del sensor con los datos de serie temporal para encontrar los sensores que tienen lecturas anómalos. A continuación, usa una proyección para crear un modelo común para los nodos del grafo.

let nodes =
    union
        (
            sensors
            | join kind=leftouter
            (
                timeseriesData
                | summarize hasAnomaly=max(anomaly) by sensorId
            ) on sensorId
            | project nodeId = sensorId, label = "tag", properties = pack_all(true)
        ),
        ( employees | project nodeId = name, label = "employee", properties = pack_all(true));

Los bordes se transforman de forma similar.

let edges =
    union
        ( assetHierarchy | extend label = "hasParent" ),
        ( allReports | project source = employee, destination = manager, label = "reportsTo" ),
        ( operates | project source = employee, destination = machine, properties = pack_all(true), label = "operates" );

Con los nodos canonizados y los datos perimetrales, puede crear un grafo mediante el operador make-graph, como se indica a continuación:

let graph = edges
| make-graph source --> destination with nodes on nodeId;

Una vez creado, defina el patrón de ruta de acceso y proyecta la información necesaria. El patrón comienza en un nodo de etiqueta seguido de un borde de longitud variable a un recurso. Ese recurso está operado por un operador que informa a un administrador superior a través de un borde de longitud variable, denominado reportsTo. La sección de restricciones del operador graph-match, en este caso , reduce las etiquetas a las que tienen una anomalía y se operaron en un día específico.

graph
| graph-match (tag)-[hasParent*1..5]->(asset)<-[operates]-(operator)-[reportsTo*1..5]->(topManager)
    where tag.label=="tag" and tobool(tag.properties.hasAnomaly) and
        startofday(todatetime(operates.properties.timestamp)) == datetime(2023-01-24)
        and topManager.label=="employee"
    project
        tagWithAnomaly = tostring(tag.properties.tagName),
        impactedAsset = asset.nodeId,
        operatorName = operator.nodeId,
        responsibleManager = tostring(topManager.nodeId)

Salida

tagWithAnomaly impactedAsset operatorName responsibleManager
temperatura Bombeo Eve Mallory

La proyección de la coincidencia de grafos genera la información que el sensor de temperatura mostró una anomalía en el día especificado. Fue operado por Eve que en última instancia informa a Mallory. Con esta información, el gerente de fábrica puede ponerse en contacto con Eve y potencialmente Mallory para comprender mejor la anomalía.