Bonnes pratiques pour la sémantique de graphe Langage de requête Kusto (KQL)
Cet article explique comment utiliser la fonctionnalité sémantique de graphe dans KQL efficacement et efficacement pour différents cas d’usage et scénarios. Il montre comment créer et interroger des graphiques avec la syntaxe et les opérateurs, et comment les intégrer à d’autres fonctionnalités et fonctions KQL. Il permet également aux utilisateurs d’éviter les pièges ou erreurs courants, tels que la création de graphiques qui dépassent les limites de mémoire ou de performances, ou en appliquant des filtres, des projections ou des agrégations inadaptés ou incompatibles.
Taille du graphique
L’opérateur make-graph crée une représentation en mémoire d’un graphique. Il se compose de la structure de graphe elle-même et de ses propriétés. Lorsque vous effectuez un graphique, utilisez des filtres, des projections et des agrégations appropriés pour sélectionner uniquement les nœuds et arêtes appropriés et leurs propriétés.
L’exemple suivant montre comment réduire le nombre de nœuds et de bords et leurs propriétés. Dans ce scénario, Bob a changé de responsable d’Alice à Eve et l’utilisateur souhaite uniquement voir l’état le plus récent du graphique pour son organisation. Pour réduire la taille du graphique, les nœuds sont d’abord filtrés par la propriété de l’organisation, puis la propriété est supprimée du graphique à l’aide de l’opérateur project-away. La même chose se produit pour les arêtes. Ensuite , l’opérateur de synthèse avec arg_max est utilisé pour obtenir le dernier état connu du graphique.
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
Sortie
employé | topManager |
---|---|
Bob | Mallory |
Dernier état connu du graphique
L’exemple Taille du graphique montre comment obtenir le dernier état connu des bords d’un graphique à l’aide summarize
de l’opérateur et de la arg_max
fonction d’agrégation. L’obtention du dernier état connu est une opération nécessitant beaucoup de ressources de calcul.
Envisagez de créer une vue matérialisée pour améliorer les performances des requêtes, comme suit :
Créez des tables qui ont une certaine notion de version dans le cadre de leur modèle. Nous vous recommandons d’utiliser une
datetime
colonne que vous pouvez utiliser ultérieurement pour créer une série chronologique de graphique..create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime) .create table reportsTo (employee:string, manager:string, modificationDate: datetime)
Créez une vue matérialisée pour chaque table et utilisez la fonction d’agrégation arg_max pour déterminer le dernier état connu des employés et la relation 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 }
Créez deux fonctions qui garantissent que seul le composant matérialisé de la vue matérialisée est utilisé et que d’autres filtres et projections sont appliqués.
.create function currentEmployees () { materialized_view('employees_MV') | where stateOfEmployment == "employed" } .create function reportsTo_lastKnownState () { materialized_view('reportsTo_MV') | project-away modificationDate }
La requête obtenue à l’aide de matérialisé rend la requête plus rapide et plus efficace pour les graphiques plus volumineux. Il permet également une concurrence plus élevée et des requêtes à latence plus faible pour l’état le plus récent du graphique. L’utilisateur peut toujours interroger l’historique des graphiques en fonction des employés et des tables reportsTo , si nécessaire
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
Voyage dans le temps du graphique
Certains scénarios vous obligent à analyser les données en fonction de l’état d’un graphique à un moment donné. Le voyage temporel du graphique utilise une combinaison de filtres de temps et de synthèses à l’aide de la fonction d’agrégation arg_max.
L’instruction KQL suivante crée une fonction avec un paramètre qui définit le point intéressant dans le temps pour le graphique. Elle retourne un graphique prêt à l’être.
.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
}
Avec la fonction en place, l’utilisateur peut créer une requête pour obtenir le top manager de Bob en fonction du graphique en juin 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
Sortie
employé | topManager |
---|---|
Bob | Dave |
Traitement de plusieurs types de nœuds et de périphérie
Parfois, il est nécessaire de contextualiser les données de série chronologique avec un graphique qui se compose de plusieurs types de nœuds. Une façon de gérer ce scénario consiste à créer un graphique de propriétés à usage général représenté par un modèle canonique.
Parfois, vous devrez peut-être contextualiser des données de série chronologique avec un graphique qui a plusieurs types de nœuds. Vous pouvez aborder le problème en créant un graphique de propriétés à usage général basé sur un modèle canonique, tel que le suivant.
- Nœuds
- nodeId (chaîne)
- étiquette (chaîne)
- propriétés (dynamiques)
- Bords
- source (chaîne)
- destination (chaîne)
- étiquette (chaîne)
- propriétés (dynamiques)
L’exemple suivant montre comment transformer les données en modèle canonique et comment l’interroger. Les tables de base pour les nœuds et les bords du graphique ont des schémas différents.
Ce scénario implique un responsable d’usine qui veut savoir pourquoi l’équipement ne fonctionne pas bien et qui est responsable de la correction. Le responsable décide d’utiliser un graphique qui combine le graphique des ressources du plancher de production et de la hiérarchie du personnel de maintenance qui change chaque jour.
Le graphique suivant montre les relations entre les ressources et leur série chronologique, telles que la vitesse, la température et la pression. Les opérateurs et les actifs, tels que la pompe, sont connectés par le biais du bord d’exploitation. Les opérateurs eux-mêmes signalent jusqu’à la gestion.
Les données de ces entités peuvent être stockées directement dans votre cluster ou acquises à l’aide de la fédération de requêtes vers un autre service, tel qu’Azure Cosmos DB, Azure SQL ou Azure Digital Twin. Pour illustrer l’exemple, les données tabulaires suivantes sont créées dans le cadre de la requête :
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"
];
Les employés, les capteurs et d’autres entités et relations ne partagent pas de modèle de données canonique. Vous pouvez utiliser l’opérateur union pour combiner et canoniser les données.
La requête suivante joint les données de capteur aux données de série chronologique pour rechercher les capteurs qui ont des lectures anormales. Ensuite, il utilise une projection pour créer un modèle commun pour les nœuds de graphe.
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));
Les bords sont transformés de la même façon.
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" );
Avec les données de nœuds canonisés et de bords, vous pouvez créer un graphique à l’aide de l’opérateur make-graph, comme suit :
let graph = edges
| make-graph source --> destination with nodes on nodeId;
Une fois créé, définissez le modèle de chemin d’accès et projetez les informations requises. Le modèle commence à un nœud d’étiquette suivi d’un bord de longueur variable vers une ressource. Cette ressource est exploitée par un opérateur qui signale à un gestionnaire supérieur via une arête de longueur variable, appelée reportsTo. La section contraintes de l’opérateur de correspondance de graphique, dans cette instance, réduit les balises aux étiquettes qui ont une anomalie et qui ont été exploitées sur une journée spécifique.
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)
Sortie
tagWithAnomaly | impactedAsset | operatorName | responsableManager |
---|---|---|---|
température | Pump | Eve | Mallory |
La projection dans la correspondance graphique génère les informations que le capteur de température a montré une anomalie le jour spécifié. Elle a été exploitée par Eve qui signale finalement à Mallory. Avec ces informations, le responsable de l’usine peut contacter Eve et potentiellement Mallory pour mieux comprendre l’anomalie.