Práticas recomendadas para semântica de grafo KQL (Kusto Query Language)
Este artigo explica como usar o recurso de semântica de grafo no KQL de forma eficaz e eficiente para diferentes casos de uso e cenários. Ele mostra como criar e consultar gráficos com a sintaxe e os operadores e como integrá-los a outros recursos e funções do KQL. Ele também ajuda os usuários a evitar armadilhas ou erros comuns, como criar gráficos que excedem os limites de memória ou desempenho ou aplicar filtros, projeções ou agregações inadequados ou incompatíveis.
Tamanho do gráfico
O operador make-graph cria uma representação na memória de um gráfico. Consiste na própria estrutura do grafo e suas propriedades. Ao fazer um gráfico, use filtros, projeções e agregações apropriados para selecionar apenas os nós e bordas relevantes e suas propriedades.
O exemplo a seguir mostra como reduzir o número de nós e arestas e suas propriedades. Nesse cenário, Bob mudou de gerente de Alice para Eve e o usuário só deseja ver o estado mais recente do gráfico de sua organização. Para reduzir o tamanho do gráfico, os nós são primeiro filtrados pela propriedade da organização e, em seguida, a propriedade é removida do gráfico usando o operador project-away. O mesmo acontece com as bordas. Em seguida, summarize junto com arg_max é usado para obter o último estado conhecido do gráfico.
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
Saída
funcionário | topManager |
---|---|
Bob | Mallory |
Último estado conhecido do gráfico
O exemplo Tamanho do gráfico demonstrou como obter o último estado conhecido das bordas de um gráfico usando summarize
o operador e a arg_max
função de agregação. A obtenção do último estado conhecido é uma operação com uso intensivo de computação.
Considere a criação de uma exibição materializada para melhorar o desempenho da consulta, da seguinte maneira:
Crie tabelas que tenham alguma noção de versão como parte de seu modelo. Recomendamos usar uma
datetime
coluna que você pode usar posteriormente para criar uma série temporal de gráfico..create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime) .create table reportsTo (employee:string, manager:string, modificationDate: datetime)
Crie uma exibição materializada para cada tabela e use a função de agregação arg_max para determinar o último estado conhecido de funcionários e a relação 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 }
Crie duas funções que garantam que somente o componente materializado da exibição materializada seja usado e que filtros e projeções adicionais sejam aplicados.
.create function currentEmployees () { materialized_view('employees_MV') | where stateOfEmployment == "employed" } .create function reportsTo_lastKnownState () { materialized_view('reportsTo_MV') | project-away modificationDate }
A consulta resultante usando materialized torna a consulta mais rápida e eficiente para gráficos maiores. Ele também permite consultas de simultaneidade mais alta e latência mais baixa para o estado mais recente do gráfico. O usuário ainda pode consultar o histórico do gráfico com base nas tabelas employees e reportsTo , se necessário
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
Viagem no tempo do gráfico
Alguns cenários exigem que você analise dados com base no estado de um gráfico em um ponto específico no tempo. A viagem no tempo do gráfico usa uma combinação de filtros de tempo e resume usando a função de agregação arg_max.
A instrução KQL a seguir cria uma função com um parâmetro que define o ponto interessante no tempo para o gráfico. Ele retorna um gráfico 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
}
Com a função instalada, o usuário pode criar uma consulta para obter o gerente superior de Bob com base no gráfico em junho 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
Saída
funcionário | topManager |
---|---|
Bob | Dave |
Lidando com vários tipos de nó e borda
Às vezes, é necessário contextualizar dados de séries temporais com um gráfico que consiste em vários tipos de nós. Uma maneira de lidar com esse cenário é criar um gráfico de propriedades de uso geral que é representado por um modelo canônico.
Ocasionalmente, pode ser necessário contextualizar dados de séries temporais com um gráfico que tenha vários tipos de nós. Você pode abordar o problema criando um gráfico de propriedades de uso geral baseado em um modelo canônico, como o seguinte.
- Nós
- nodeId (cadeia de caracteres)
- rótulo (cadeia de caracteres)
- Propriedades (dinâmicas)
- Bordas
- fonte (cadeia de caracteres)
- destino (cadeia de caracteres)
- rótulo (cadeia de caracteres)
- Propriedades (dinâmicas)
O exemplo a seguir mostra como transformar os dados em um modelo canônico e como consultá-los. As tabelas base para os nós e bordas do gráfico têm esquemas diferentes.
Esse cenário envolve um gerente de fábrica que deseja descobrir por que o equipamento não está funcionando bem e quem é responsável por consertá-lo. O gerente decide usar um gráfico que combina o gráfico de ativos do chão de fábrica e a hierarquia da equipe de manutenção que muda todos os dias.
O gráfico a seguir mostra as relações entre os ativos e suas séries temporais, como velocidade, temperatura e pressão. Os operadores e os ativos, como bombas, são conectados através da borda de operação . Os próprios operadores se reportam à gerência.
Os dados dessas entidades podem ser armazenados diretamente no cluster ou adquiridos usando a federação de consulta para um serviço diferente, como o Azure Cosmos DB, o SQL do Azure ou o Gêmeo Digital do Azure. Para ilustrar o exemplo, os seguintes dados tabulares são criados como parte da 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"
];
Os funcionários, sensores e outras entidades e relacionamentos não compartilham um modelo de dados canônico. Você pode usar o operador union para combinar e canonizar os dados.
A consulta a seguir une os dados do sensor com os dados da série temporal para localizar os sensores que têm leituras anormais. Em seguida, ele usa uma projeção para criar um modelo comum para os nós do gráfico.
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));
As arestas são transformadas de maneira semelhante.
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" );
Com os dados de nós e arestas canonizados, você pode criar um gráfico usando o operador make-graph, da seguinte maneira:
let graph = edges
| make-graph source --> destination with nodes on nodeId;
Depois de criado, defina o padrão de caminho e projete as informações necessárias. O padrão começa em um nó de tag seguido por uma aresta de comprimento variável para um ativo. Esse ativo é operado por um operador que se reporta a um gerente de alto escalão por meio de uma borda de comprimento variável, chamada reportsTo. A seção constraints do operador graph-match, neste caso, em que, reduz as tags para aquelas que têm uma anomalia e foram operadas em um dia 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)
Saída
tagWithAnomaly | Ativo impactado | nome do operador | responsávelGerente |
---|---|---|---|
temperatura | Bomba | Ev | Mallory |
A projeção em correspondência de gráfico gera a informação de que o sensor de temperatura mostrou uma anomalia no dia especificado. Foi operado por Eve, que finalmente se reporta a Mallory. Com essas informações, o gerente da fábrica pode entrar em contato com Eve e potencialmente Mallory para entender melhor a anomalia.