Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Semantyka grafów obsługuje dwa podstawowe podejścia do pracy z grafami: przejściowe grafy tworzone w pamięci dla każdego zapytania oraz trwałe grafy zdefiniowane jako modele grafów i zrzuty w bazie danych. Ten artykuł zawiera najlepsze rozwiązania dla obu metod, co pozwala wybrać optymalne podejście i efektywnie używać semantyki grafów KQL.
Te wskazówki obejmują:
- Strategie tworzenia i optymalizacji grafu
- Zagadnienia dotyczące technik zapytań i aspektów wydajnościowych
- Projekt schematu dla grafów trwałych
- Integracja z innymi funkcjami języka KQL
- Typowe pułapki do unikania
Podejścia do modelowania grafu
Istnieją dwa podejścia do pracy z grafami: przejściowe i trwałe.
Wykresy przejściowe
Utworzono dynamicznie przy użyciu make-graph
operatora . Te wykresy istnieją tylko podczas wykonywania zapytań i są optymalne dla analizy ad hoc lub eksploracyjnej na małych i średnich zestawach danych.
Wykresy trwałe
Zdefiniowano przy użyciu modeli grafów i migawek grafów. Te grafy są przechowywane w bazie danych, obsługują schemat i przechowywanie wersji oraz są zoptymalizowane pod kątem powtarzanych, dużych lub wspólnych analiz.
Najlepsze rozwiązania dotyczące wykresów przejściowych
Wykresy przejściowe, utworzone w pamięci przy użyciu make-graph
operatora, są idealne do analizy ad hoc, tworzenia prototypów i scenariuszy, w których struktura grafu zmienia się często lub wymaga tylko podzestawu dostępnych danych.
Optymalizowanie rozmiaru grafu pod kątem wydajności
Obiekt make-graph
tworzy reprezentację w pamięci, w tym zarówno strukturę, jak i właściwości. Optymalizowanie wydajności poprzez:
- Wczesne stosowanie filtrów — wybierz tylko odpowiednie węzły, krawędzie i właściwości przed utworzeniem grafu
- Używanie projekcji — usuwanie niepotrzebnych kolumn w celu zminimalizowania zużycia pamięci
- Stosowanie agregacji — podsumowywanie danych tam, gdzie jest to właściwe, aby zmniejszyć złożoność grafu
Przykład: zmniejszenie rozmiaru grafu przez filtrowanie i projekcję
W tym scenariuszu Bob zmienił menedżerów z Alice na Eve. Aby wyświetlić tylko najnowszy stan organizacji przy jednoczesnym zminimalizowaniu rozmiaru grafu:
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
Wyjście:
pracownik | główny menedżer |
---|---|
Robert | Mallory |
Utrzymywanie bieżącego stanu za pomocą zmaterializowanych widoków
W poprzednim przykładzie pokazano, jak uzyskać ostatni znany stan przy użyciu metod summarize
i arg_max
. Ta operacja może być intensywnie obciążana obliczeniami, dlatego rozważ użycie zmaterializowanych widoków w celu zwiększenia wydajności.
Krok 1. Tworzenie tabel z przechowywaniem wersji
Tworzenie tabel z mechanizmem przechowywania wersji dla serii czasowych grafu:
.create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime)
.create table reportsTo (employee:string, manager:string, modificationDate: datetime)
Krok 2. Tworzenie zmaterializowanych widoków
Użyj funkcji agregującej arg_max, aby określić najnowszy stan:
.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
}
Krok 3. Tworzenie funkcji pomocnika
Upewnij się, że używany jest tylko zmaterializowany składnik i zastosuj dodatkowe filtry:
.create function currentEmployees () {
materialized_view('employees_MV')
| where stateOfEmployment == "employed"
}
.create function reportsTo_lastKnownState () {
materialized_view('reportsTo_MV')
| project-away modificationDate
}
Takie podejście zapewnia szybsze zapytania, większą współbieżność i mniejsze opóźnienie dla bieżącej analizy stanu przy jednoczesnym zachowaniu dostępu do danych historycznych.
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
Implementacja podróży czasowej grafu
Analizowanie danych na podstawie historycznych stanów grafu zapewnia cenny kontekst czasowy. Zaimplementuj tę funkcję "podróży czasowej", łącząc filtry czasu z elementami summarize
i :arg_max
.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
}
Przykład użycia:
Zapytanie o najwyższego menedżera Boba na podstawie stanu grafu z czerwca 2022 r.:
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
Wyjście:
pracownik | główny menedżer |
---|---|
Robert | Dave |
Obsługa wielu typów węzłów i krawędzi
Podczas pracy z złożonymi grafami zawierającymi wiele typów węzłów należy użyć kanonicznego modelu grafu właściwości. Zdefiniuj węzły z atrybutami, takimi jak nodeId
(ciąg), label
(ciąg) i properties
(dynamiczny), a krawędzie obejmują source
(ciąg), destination
(ciąg), label
(ciąg) i properties
(dynamiczny).
Przykład: Analiza konserwacji fabryki
Rozważ kierownika fabryki badającego problemy sprzętu i odpowiedzialnego personelu. Scenariusz łączy grafy zasobów sprzętu produkcyjnego z hierarchią personelu konserwacyjnego:
Dane dla tych jednostek można przechowywać bezpośrednio w klastrze lub uzyskiwać przy użyciu federacji zapytań w innej usłudze. Aby zilustrować przykład, następujące dane tabelaryczne są tworzone w ramach zapytania:
let sensors = datatable(sensorId:string, tagName:string, unitOfMeasure: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"
];
Pracownicy, czujniki i inne jednostki i relacje nie współużytkują kanonicznego modelu danych. Operator unii może służyć do łączenia i standaryzacji danych.
Poniższe zapytanie łączy dane czujnika z danymi szeregów czasowych w celu zidentyfikowania czujników z nieprawidłowymi odczytami, a następnie używa projekcji do utworzenia wspólnego modelu dla węzłów grafu.
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));
Krawędzie są przekształcane w podobny sposób.
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" );
Za pomocą ustandaryzowanych węzłów i danych krawędzi można utworzyć graf przy użyciu operatora make-graph
let graph = edges
| make-graph source --> destination with nodes on nodeId;
Po utworzeniu grafu zdefiniuj wzorzec ścieżki i przeprojektuj wymagane informacje. Wzorzec rozpoczyna się od węzła tagu, po którym następuje krawędź o zmiennej długości prowadząca do zasobu. Ten zasób jest obsługiwany przez operatora, który zgłasza się do najwyższego menedżera za pośrednictwem krawędzi o zmiennej długości o nazwie reportsTo. Sekcja ograniczeń operatora dopasowania grafu, w tym przypadku klauzula where , filtruje tagi do tych z anomalią, która była obsługiwana w określonym dniu.
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)
Wynik
tagWithAnomaly | zasób objęty wpływem | Nazwa operatora | odpowiedzialnyKierownik |
---|---|---|---|
temperatura | Pompa | Przeddzień | Mallory |
Projekcja w programie graph-match
pokazuje, że czujnik temperatury wykazywał anomalię w określonym dniu. Czujnik był obsługiwany przez Eve, który ostatecznie zgłasza się do Mallory. Dzięki tym informacjom kierownik fabryki może skontaktować się z Eve i, w razie potrzeby, Mallory, aby lepiej zrozumieć anomalię.
Najlepsze rozwiązania dotyczące grafów trwałych
Trwałe grafy zdefiniowane przy użyciu modeli grafów i migawek grafów zapewniają niezawodne rozwiązania dla zaawansowanych potrzeb analizy grafów. Te wykresy doskonale sprawdzają się w scenariuszach wymagających wielokrotnej analizy dużych, złożonych lub zmieniających się relacji danych oraz ułatwiają współpracę, umożliwiając zespołom udostępnianie ustandaryzowanych definicji grafu i spójnych wyników analitycznych. Utrzymując struktury grafów w bazie danych, takie podejście znacznie zwiększa wydajność cyklicznych zapytań i obsługuje zaawansowane możliwości przechowywania wersji.
Używanie schematu i definicji w celu zapewnienia spójności i wydajności
Jasny schemat dla modelu grafu jest niezbędny, ponieważ określa typy węzłów i krawędzi wraz z ich właściwościami. Takie podejście zapewnia spójność danych i umożliwia wydajne wykonywanie zapytań. Skorzystaj z sekcji Definition
, aby określić sposób konstruowania węzłów i krawędzi na podstawie danych tabelarycznych poprzez kroki AddNodes
i AddEdges
.
Korzystanie z etykiet statycznych i dynamicznych na potrzeby elastycznego modelowania
Podczas modelowania grafu można użyć metod etykietowania statycznego i dynamicznego w celu uzyskania optymalnej elastyczności. Etykiety statyczne są idealne dla dobrze zdefiniowanych typów węzłów i krawędzi, które rzadko się zmieniają — zdefiniuj je w Schema
sekcji i odwołaj się do nich w Labels
tablicy kroków. W przypadku, gdy typy węzłów lub krawędzi są określane przez wartości danych (na przykład gdy typ jest przechowywany w kolumnie), użyj etykiet dynamicznych, określając LabelsColumn
w kroku, aby przypisać etykiety w czasie wykonywania. Takie podejście jest szczególnie przydatne w przypadku grafów z heterogenicznymi lub ewoluującymi schematami. Oba mechanizmy można skutecznie łączyć — można zdefiniować tablicę Labels
dla etykiet statycznych, a także określić , LabelsColumn
aby uwzględnić dodatkowe etykiety z danych, zapewniając maksymalną elastyczność podczas modelowania złożonych grafów z kategoryzacją opartą zarówno na stałe, jak i opartej na danych.
Przykład: używanie etykiet dynamicznych dla wielu typów węzłów i krawędzi
W poniższym przykładzie pokazano skuteczną implementację etykiet dynamicznych na grafie reprezentującym relacje zawodowe. W tym scenariuszu wykres zawiera osoby i firmy jako węzły, a relacje zatrudnienia tworzą krawędzie między nimi. Elastyczność tego modelu wynika z określania typów węzłów i krawędzi bezpośrednio z kolumn w danych źródłowych, dzięki czemu struktura grafu dostosowuje się w sposób organiczny do podstawowych informacji.
.create-or-alter graph_model ProfessionalNetwork ```
{
"Schema": {
"Nodes": {
"Person": {"Name": "string", "Age": "long"},
"Company": {"Name": "string", "Industry": "string"}
},
"Edges": {
"WORKS_AT": {"StartDate": "datetime", "Position": "string"}
}
},
"Definition": {
"Steps": [
{
"Kind": "AddNodes",
"Query": "Employees | project Id, Name, Age, NodeType",
"NodeIdColumn": "Id",
"Labels": ["Person"],
"LabelsColumn": "NodeType"
},
{
"Kind": "AddEdges",
"Query": "EmploymentRecords | project EmployeeId, CompanyId, StartDate, Position, RelationType",
"SourceColumn": "EmployeeId",
"TargetColumn": "CompanyId",
"Labels": ["WORKS_AT"],
"LabelsColumn": "RelationType"
}
]
}
}
```
To dynamiczne podejście do etykietowania zapewnia wyjątkową elastyczność podczas modelowania grafów z wieloma typami węzłów i krawędzi, eliminując konieczność modyfikowania schematu za każdym razem, gdy nowy typ jednostki pojawia się w danych. Oddzielenie modelu logicznego od implementacji fizycznej umożliwia ciągłe rozwijanie grafu w celu reprezentowania nowych relacji bez konieczności wprowadzania zmian strukturalnych w bazowym schemacie.
Strategie partycjonowania wielodzierżawności dla scenariuszy ISV na dużą skalę
W dużych organizacjach, szczególnie w scenariuszach ISV, grafy mogą składać się z wielu miliardów węzłów i krawędzi. Ta skala stanowi unikatowe wyzwania, które wymagają podejścia do partycjonowania strategicznego w celu utrzymania wydajności podczas zarządzania kosztami i złożonością.
Zrozumienie wyzwania
Środowiska wielodostępne na dużą skalę często wykazują następujące cechy:
- Miliardy węzłów i krawędzi — grafy w skali przedsiębiorstwa, które przekraczają tradycyjne możliwości bazy danych grafów
- Rozkład rozmiaru najemcy — zwykle jest zgodny z prawem potęgowym, w którym 99,9% najemców ma małe i średnie grafy, podczas gdy 0,1% ma ogromne grafy
- Wymagania dotyczące wydajności — potrzeba analizy w czasie rzeczywistym (bieżących danych) i możliwości analizy historycznej
- Zagadnienia dotyczące kosztów — równoważenie kosztów infrastruktury i możliwości analitycznych
Partycjonowanie według granic naturalnych
Najbardziej skutecznym podejściem do zarządzania grafami na dużą skalę jest podział wzdłuż granic naturalnych, zazwyczaj identyfikatorów najemców lub jednostek organizacyjnych.
Kluczowe strategie partycjonowania:
- Partycjonowanie oparte na najemcy — oddziela grafy według klienta, organizacji lub jednostki biznesowej
- Partycjonowanie geograficzne — dzielenie według regionu, kraju lub lokalizacji centrum danych
- Partycjonowanie czasowe — oddzielanie według okresów na potrzeby analizy historycznej
- Partycjonowanie funkcjonalne — podział według domeny biznesowej lub obszaru aplikacji
Przykład: Wielootenantowa struktura organizacyjna
// Partition employees and reports by tenant
let tenantEmployees =
allEmployees
| where tenantId == "tenant_123"
| project-away tenantId;
let tenantReports =
allReports
| where tenantId == "tenant_123"
| summarize arg_max(modificationDate, *) by employee
| project-away modificationDate, tenantId;
tenantReports
| make-graph employee --> manager with tenantEmployees on name
| graph-match (employee)-[hasManager*1..5]-(manager)
where employee.name == "Bob"
project employee = employee.name, reportingChain = hasManager.manager
Podejście hybrydowe: przejściowe a trwałe wykresy według rozmiaru dzierżawy
Najbardziej opłacalna strategia łączy zarówno przejściowe, jak i trwałe wykresy oparte na cechach dzierżawy:
Małe i średnie dzierżawy (99,9% dzierżaw)
Użyj wykresów przejściowych dla większości najemców:
Zalety:
- Zawsze up-to— zawsze aktualne dane — nie jest wymagana konserwacja migawki
- Mniejsze obciążenie operacyjne — brak modelu grafu ani zarządzania migawkami
- Ekonomiczne — brak dodatkowych kosztów magazynowania dla struktur grafów
- Natychmiastowa dostępność — brak opóźnień przetwarzania wstępnego
Wzorzec implementacji:
.create function getTenantGraph(tenantId: string) {
let tenantEmployees =
employees
| where tenant == tenantId and stateOfEmployment == "employed"
| project-away tenant, stateOfEmployment;
let tenantReports =
reportsTo
| where tenant == tenantId
| summarize arg_max(modificationDate, *) by employee
| project-away modificationDate, tenant;
tenantReports
| make-graph employee --> manager with tenantEmployees on name
}
// Usage for small tenant
getTenantGraph("small_tenant_456")
| graph-match (employee)-[reports*1..3]-(manager)
where employee.name == "Alice"
project employee = employee.name, managerChain = reports.manager
Duzi najemcy (0,1% najemców)
Użyj persistent graphs dla największych najemców:
Zalety:
- Skalowalność — obsługa wykresów przekraczających ograniczenia pamięci
- Optymalizacja wydajności — eliminowanie opóźnień budowy dla złożonych zapytań
- Zaawansowana analiza — obsługa zaawansowanych algorytmów grafu i analizy
- Analiza historyczna — wiele migawek na potrzeby porównania danych czasowych
Wzorzec implementacji:
// Create graph model for large tenant (example: Contoso)
.create-or-alter graph_model ContosoOrgChart ```
{
"Schema": {
"Nodes": {
"Employee": {
"Name": "string",
"Department": "string",
"Level": "int",
"JoinDate": "datetime"
}
},
"Edges": {
"ReportsTo": {
"Since": "datetime",
"Relationship": "string"
}
}
},
"Definition": {
"Steps": [
{
"Kind": "AddNodes",
"Query": "employees | where tenant == 'Contoso' and stateOfEmployment == 'employed' | project Name, Department, Level, JoinDate",
"NodeIdColumn": "Name",
"Labels": ["Employee"]
},
{
"Kind": "AddEdges",
"Query": "reportsTo | where tenant == 'Contoso' | summarize arg_max(modificationDate, *) by employee | project employee, manager, modificationDate as Since | extend Relationship = 'DirectReport'",
"SourceColumn": "employee",
"TargetColumn": "manager",
"Labels": ["ReportsTo"]
}
]
}
}
```
// Create snapshot for Contoso
.create graph snapshot ContosoSnapshot from ContosoOrgChart
// Query Contoso's organizational graph
graph("ContosoOrgChart")
| graph-match (employee)-[reports*1..10]-(executive)
where employee.Department == "Engineering"
project employee = employee.Name, executive = executive.Name, pathLength = array_length(reports)
Najlepsze praktyki dotyczące scenariuszy dla niezależnych dostawców oprogramowania (ISV)
- Zacznij od przejściowych grafów — rozpocznij wszystkie nowe dzierżawy z przejściowymi grafami dla uproszczenia
- Monitorowanie wzorców wzrostu — wdrażanie automatycznego wykrywania użytkowników wymagających trwałych grafów
- Tworzenie migawek wsadowych — planowanie aktualizacji migawek w okresach niskiego użycia
- Izolacja najemców — Zapewnić prawidłową izolację modeli grafowych i migawek między najemcami.
- Zarządzanie zasobami — użyj grup roboczych, aby zapobiec wpływowi dużych zapytań klientów na mniejszych klientów
- Optymalizacja kosztów — regularne przeglądanie i optymalizowanie trwałego/przejściowego progu na podstawie rzeczywistych wzorców użycia
Takie podejście hybrydowe umożliwia organizacjom zapewnienie zawsze aktualnej analizy danych dla większości dzierżaw, zapewniając jednocześnie możliwości analizy w skali przedsiębiorstwa dla największych dzierżaw, optymalizując zarówno koszty, jak i wydajność w całej bazie klientów.