Kusto 照会言語 (KQL) グラフ セマンティクスのベスト プラクティス
この記事では、さまざまなユース ケースやシナリオで KQL のグラフ セマンティクス機能を効果的かつ効率的に使用する方法について説明します。 構文と演算子を使用してグラフを作成およびクエリする方法と、それらを他の KQL の機能と関数と統合する方法を示します。 また、メモリやパフォーマンスの制限を超えるグラフの作成、不適切または互換性のないフィルター、プロジェクション、集計の適用など、ユーザーが一般的な落とし穴やエラーを回避するのにも役立ちます。
グラフのサイズ
make-graph 演算子は、グラフのメモリ内表現を作成します。 グラフ構造自体とそのプロパティで構成されます。 グラフを作成するときは、適切なフィルター、プロジェクション、集計を使用して、関連するノードとエッジとそのプロパティのみを選択します。
次の例は、ノードとエッジの数とそのプロパティを減らす方法を示しています。 このシナリオでは、Bob がマネージャーを Alice から Eve に変更し、ユーザーは組織のグラフの最新の状態のみを表示したいと考えています。 グラフのサイズを小さくするために、ノードは最初に組織のプロパティによってフィルター処理され、その後、 project-away 演算子を使用してグラフからプロパティが削除されます。 エッジでも同じことが起こります。 次に、ummarize 演算子arg_maxと共に使用して、グラフの最後の既知の状態を取得します。
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
出力
employee | topManager |
---|---|
Bob | Mallory |
グラフの最後の既知の状態
グラフの Size 例では、 summarize
演算子と arg_max
集計関数を使用して、グラフのエッジの最後の既知の状態を取得する方法を示しました。 最後の既知の状態の取得は、コンピューティング集中型の操作です。
次のように、クエリのパフォーマンスを向上させるために具体化されたビューを作成することを検討してください。
モデルの一部としてバージョンの概念を持つテーブルを作成します。 後でグラフ時系列を作成するために使用できる
datetime
列を使用することをお勧めします。.create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime) .create table reportsTo (employee:string, manager:string, modificationDate: datetime)
各テーブルの具体化されたビューを作成し、 arg_max集計 関数を使用して、従業員の 過去の既知の状態 と 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 }
具体化されたビューの具体化されたコンポーネントのみが使用され、追加のフィルターとプロジェクションが適用されるようにする 2 つの関数を作成します。
.create function currentEmployees () { materialized_view('employees_MV') | where stateOfEmployment == "employed" } .create function reportsTo_lastKnownState () { materialized_view('reportsTo_MV') | project-away modificationDate }
具体化されたクエリを使用すると、大規模なグラフに対するクエリの高速化と効率が向上します。 また、グラフの最新の状態に対するコンカレンシーが高くなり、待機時間が短いクエリも可能になります。 ユーザーは、必要に応じて、従業員と reportsTo テーブルに基づいてグラフ履歴のクエリを実行できます
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
グラフの時間移動
一部のシナリオでは、特定の時点でのグラフの状態に基づいてデータを分析する必要があります。 グラフの時間移動では、arg_max集計関数を使用して、時間フィルターと集計の組み合わせを使用します。
次の KQL ステートメントは、グラフの興味深い時点を定義するパラメーターを持つ関数を作成します。 既製のグラフを返します。
.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
}
この関数を設定すると、ユーザーはクエリを作成して、2022 年 6 月のグラフに基づいて Bob のトップ マネージャーを取得できます。
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
出力
employee | topManager |
---|---|
Bob | Dave |
複数のノードとエッジの種類の処理
複数のノードの種類で構成されるグラフを使用して時系列データをコンテキスト化することが必要な場合があります。 このシナリオを処理する 1 つの方法は、正規モデルで表される汎用プロパティ グラフを作成することです。
場合によっては、複数のノードタイプを持つグラフを使用して時系列データをコンテキスト化することが必要になる場合があります。 次のような正規モデルに基づく汎用プロパティ グラフを作成することで、問題にアプローチできます。
- ノード
- nodeId (string)
- label (string)
- properties (dynamic)
- エッジ
- source (string)
- destination (string)
- label (string)
- properties (dynamic)
次の例は、データを正規モデルに変換する方法とクエリを実行する方法を示しています。 グラフのノードとエッジのベース テーブルには、スキーマが異なります。
このシナリオには、設備が正常に動作しない理由と、その修正を担当する工場マネージャーが含まれます。 マネージャーは、生産現場の資産グラフと、毎日変更されるメンテナンス スタッフ階層を組み合わせたグラフを使用することにしました。
次のグラフは、資産とその時系列 (速度、温度、圧力など) の関係を示しています。 演算子とアセット ( pump など) は、 operates エッジを介して接続されます。 オペレーター自体は、管理に関するレポートを作成します。
これらのエンティティのデータは、クラスターに直接格納することも、Azure Cosmos DB、Azure SQL、Azure Digital Twin などの別のサービスへのクエリ フェデレーションを使用して取得することもできます。 この例を示すために、次の表形式データがクエリの一部として作成されます。
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"
];
employees、sensors、およびその他のエンティティとリレーションシップは、正規のデータ モデルを共有しません。 union 演算子を使用して、データを結合および正規化できます。
次のクエリでは、センサー データを時系列データと結合して、異常な読み取り値を持つセンサーを見つけます。 次に、プロジェクションを使用してグラフ ノードの共通モデルを作成します。
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));
エッジも同様の方法で変換されます。
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" );
正規化されたノードとエッジ データを使用すると、次のように、 make-graph 演算子を使用してグラフを作成できます。
let graph = edges
| make-graph source --> destination with nodes on nodeId;
作成したら、パス パターンを定義し、必要な情報を投影します。 パターンは、タグ ノードから始まり、その後にアセットの可変長エッジが続きます。 その資産は、可変長エッジ ( reportsTo と呼ばれる) を介してトップ マネージャーに報告するオペレーターによって操作されます。 グラフ一致演算子の制約セクションこのインスタンス場所では、異常があり、特定の日に操作されたタグにタグを減らします。
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)
出力
tagWithAnomaly | impactedAsset | operatorName | responsibleManager |
---|---|---|---|
温度 | ポンプ | Eve | Mallory |
グラフ 一致のプロジェクションは、温度センサーが指定した日に異常を示した情報を出力します。 最終的にマローリーに報告するイブによって運営されました。 この情報を使用すると、工場のマネージャーは Eve と Mallory に連絡して、異常をより深く理解できます。