Condividi tramite


Procedure consigliate per la semantica del grafo di Linguaggio di query Kusto (KQL)

Questo articolo illustra come usare la funzionalità semantica del grafo in KQL in modo efficace ed efficiente per diversi casi d'uso e scenari. Illustra come creare ed eseguire query su grafici con la sintassi e gli operatori e come integrarli con altre funzionalità e funzioni KQL. Consente inoltre agli utenti di evitare errori o insidi comuni, ad esempio la creazione di grafici che superano i limiti di memoria o prestazioni o l'applicazione di filtri non adatti o incompatibili, proiezioni o aggregazioni.

Dimensioni del grafico

L'operatore make-graph crea una rappresentazione in memoria di un grafico. È costituito dalla struttura del grafo e dalle relative proprietà. Quando si crea un grafico, usare filtri, proiezioni e aggregazioni appropriati per selezionare solo i nodi e i bordi pertinenti e le relative proprietà.

Nell'esempio seguente viene illustrato come ridurre il numero di nodi e archi e le relative proprietà. In questo scenario Bob ha cambiato manager da Alice a Eve e l'utente vuole visualizzare solo lo stato più recente del grafico per la propria organizzazione. Per ridurre le dimensioni del grafico, i nodi vengono prima filtrati in base alla proprietà dell'organizzazione e quindi la proprietà viene rimossa dal grafico usando l'operatore project-away. Lo stesso accade per i bordi. Viene quindi usato l'operatore summarize insieme a arg_max per ottenere l'ultimo stato noto del grafico.

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

Output

Dipendente topManager
Bob Mallory

Ultimo stato noto del grafico

L'esempio Di dimensioni del grafico ha illustrato come ottenere l'ultimo stato noto dei bordi di un grafico usando summarize l'operatore e la arg_max funzione di aggregazione. Ottenere l'ultimo stato noto è un'operazione a elevato utilizzo di calcolo.

Prendere in considerazione la creazione di una vista materializzata per migliorare le prestazioni delle query, come indicato di seguito:

  1. Creare tabelle con una certa nozione di versione come parte del modello. È consigliabile usare una datetime colonna che in un secondo momento è possibile usare per creare una serie temporale del grafo.

    .create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime)
    
    .create table reportsTo (employee:string, manager:string, modificationDate: datetime)
    
  2. Creare una vista materializzata per ogni tabella e usare la funzione di aggregazione arg_max per determinare l'ultimo stato noto dei dipendenti e la relazione 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. Creare due funzioni che assicurano che venga usato solo il componente materializzato della vista materializzata e vengano applicati filtri e proiezioni aggiuntivi.

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

La query risultante che usa materializzata rende la query più veloce ed efficiente per grafici più grandi. Consente anche una concorrenza più elevata e query di latenza inferiori per lo stato più recente del grafico. L'utente può comunque eseguire query sulla cronologia del grafo in base ai dipendenti e alle tabelle reportsTo , se necessario

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

Tempo di spostamento a grafo

Alcuni scenari richiedono di analizzare i dati in base allo stato di un grafico in un momento specifico. Il tempo di spostamento del grafico usa una combinazione di filtri temporali e riepiloga usando la funzione di aggregazione arg_max.

L'istruzione KQL seguente crea una funzione con un parametro che definisce il punto nel tempo interessante per il grafico. Restituisce un grafico pronto.

.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 funzione sul posto, l'utente può creare una query per ottenere il top manager di Bob basato sul grafico nel mese di giugno 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

Output

Dipendente topManager
Bob Dave

Gestione di più nodi e tipi di arco

A volte è necessario contestualizzare i dati delle serie temporali con un grafo costituito da più tipi di nodo. Un modo per gestire questo scenario consiste nel creare un grafico delle proprietà per utilizzo generico rappresentato da un modello canonico.

In alcuni casi, potrebbe essere necessario contestualizzare i dati delle serie temporali con un grafo con più tipi di nodo. È possibile affrontare il problema creando un grafico delle proprietà per utilizzo generico basato su un modello canonico, ad esempio il seguente.

  • Nodi
    • nodeId (string)
    • label (stringa)
    • proprietà (dinamiche)
  • Bordi
    • source (string)
    • destination (string)
    • label (stringa)
    • proprietà (dinamiche)

Nell'esempio seguente viene illustrato come trasformare i dati in un modello canonico e come eseguirne una query. Le tabelle di base per i nodi e i bordi del grafico hanno schemi diversi.

Questo scenario implica un responsabile della fabbrica che vuole scoprire perché le apparecchiature non funzionano bene e chi è responsabile della correzione. Il manager decide di usare un grafico che combina il grafico degli asset del piano di produzione e la gerarchia del personale di manutenzione che cambia ogni giorno.

Il grafico seguente mostra le relazioni tra asset e le relative serie temporali, ad esempio velocità, temperatura e pressione. Gli operatori e gli asset, ad esempio la pompa, sono collegati tramite il bordo delle operazioni . Gli operatori stessi segnalano la gestione.

Infografica sullo scenario del grafico delle proprietà.

I dati per tali entità possono essere archiviati direttamente nel cluster o acquisiti usando la federazione di query in un servizio diverso, ad esempio Azure Cosmos DB, Azure SQL o Gemelli digitali di Azure. Per illustrare l'esempio, i dati tabulari seguenti vengono creati come parte della query:

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"
];

I dipendenti, i sensori e altre entità e relazioni non condividono un modello di dati canonico. È possibile usare l'operatore union per combinare e canonizzare i dati.

La query seguente unisce i dati del sensore ai dati delle serie temporali per trovare i sensori con letture anomale. Usa quindi una proiezione per creare un modello comune per i nodi 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));

I bordi vengono trasformati in modo simile.

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 i dati canonizzati nodi e archi, è possibile creare un grafo usando l'operatore make-graph, come indicato di seguito:

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

Dopo la creazione, definire il modello di percorso e proiettare le informazioni necessarie. Il modello inizia in corrispondenza di un nodo tag seguito da un bordo di lunghezza variabile a un asset. Tale asset viene gestito da un operatore che segnala a un top manager tramite un arco di lunghezza variabile, denominato reportsTo. La sezione vincoli dell'operatore graph-match, in questa istanza in cui, riduce i tag a quelli con anomalia e gestiti in un giorno specifico.

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)

Output

tagWithAnomaly impactedAsset operatorName responsibleManager
temperatura Pompa Eve Mallory

La proiezione in corrispondenza del grafico restituisce le informazioni visualizzate dal sensore di temperatura che hanno mostrato un'anomalia nel giorno specificato. Fu gestito da Eve che alla fine riferisce a Mallory. Con queste informazioni, il responsabile della fabbrica può contattare Eve e potenzialmente Mallory per ottenere una migliore comprensione dell'anomalia.