Compartir a través de


Procedimientos recomendados para la semántica de grafos

Se aplica a: ✅Microsoft FabricAzure Data Explorer✅Azure MonitorMicrosoft Sentinel

Graph semanitcs admite dos enfoques principales para trabajar con gráficos: gráficos transitorios creados en memoria para cada consulta y gráficos persistentes definidos como modelos de grafos e instantáneas dentro de la base de datos. En este artículo se proporcionan procedimientos recomendados para ambos métodos, lo que le permite seleccionar el enfoque óptimo y usar la semántica de grafos KQL de forma eficaz.

En esta guía se describe lo siguiente:

  • Estrategias de creación y optimización de grafos
  • Técnicas de consulta y consideraciones de rendimiento
  • Diseño de esquema para gráficos persistentes
  • Integración con otras características de KQL
  • Problemas comunes que se deben evitar

Enfoques de modelado de grafos

Hay dos enfoques para trabajar con gráficos: transitorios y persistentes.

Gráficos transitorios

Creado dinámicamente mediante el make-graph operador . Estos gráficos solo existen durante la ejecución de consultas y son óptimos para el análisis ad hoc o exploratorio en conjuntos de datos pequeños a medianos.

Gráficos persistentes

Se define mediante modelos de grafos y instantáneas de grafos. Estos gráficos se almacenan en la base de datos, admiten el esquema y el control de versiones, y están optimizados para análisis repetidos, a gran escala o de colaboración.

Procedimientos recomendados para gráficos transitorios

Los gráficos transitorios, creados en memoria mediante el make-graph operador , son ideales para el análisis ad hoc, la creación de prototipos y los escenarios en los que la estructura del grafo cambia con frecuencia o solo requiere un subconjunto de datos disponibles.

Optimización del tamaño del grafo para el rendimiento

make-graph crea una representación en memoria, incluidas las propiedades y la estructura. Optimizar el rendimiento por:

  • Aplicar filtros al principio : seleccione solo los nodos, bordes y propiedades pertinentes antes de la creación del grafo.
  • Uso de proyecciones : eliminación de columnas innecesarias para minimizar el consumo de memoria
  • Aplicar agregaciones : resumir los datos cuando corresponda para reducir la complejidad del grafo

Ejemplo: Reducción del tamaño del grafo mediante filtrado y proyección

En este escenario, Bob cambió los administradores de Alice a Eve. Para ver solo el estado de la organización más reciente al minimizar el tamaño del grafo:

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

Salida:

empleado Gerente de alto nivel
Bob Mallory

Mantenimiento del estado actual con vistas materializadas

En el ejemplo anterior se mostró cómo obtener el último estado conocido mediante summarize y arg_max. Esta operación puede ser de proceso intensivo, por lo que considere la posibilidad de usar vistas materializadas para mejorar el rendimiento.

Paso 1: Creación de tablas con control de versiones

Crear tablas con un mecanismo de control de versiones para series temporales de grafos.

.create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime)

.create table reportsTo (employee:string, manager:string, modificationDate: datetime)

Paso 2: Crear vistas materializadas

Utiliza la función de agregación arg_max para determinar el estado más reciente:

.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
}

Paso 3: Creación de funciones auxiliares

Asegúrese de que solo se usa el componente materializado y aplique filtros adicionales:

.create function currentEmployees () {
    materialized_view('employees_MV')
    | where stateOfEmployment == "employed"
}

.create function reportsTo_lastKnownState () {
    materialized_view('reportsTo_MV')
    | project-away modificationDate
}

Este enfoque proporciona consultas más rápidas, mayor simultaneidad y menor latencia para el análisis de estado actual, a la vez que se conserva el acceso a los datos históricos.

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

Implementación del viaje en el tiempo del grafo

El análisis de datos basados en estados históricos de grafos proporciona un contexto temporal valioso. Implemente esta funcionalidad de "viaje en el tiempo" mediante la combinación de filtros de tiempo con summarize y 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
}

Ejemplo de uso:

Consulta el administrador principal de Bob según el estado del gráfico de junio 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

Salida:

empleado Gerente de alto nivel
Bob Dave

Gestión de múltiples tipos de nodos y aristas

Al trabajar con gráficos complejos que contienen varios tipos de nodo, use un modelo de grafo de propiedades canónicas. Defina nodos con atributos como nodeId (cadena), label (cadena) y properties (dinámico), mientras que los bordes incluyen source (cadena), destination (cadena), label (cadena) y properties (dinámicos).

Ejemplo: Análisis de mantenimiento de fábrica

Considere la posibilidad de que un administrador de fábrica investigue problemas de equipos y personal responsable. El escenario combina gráficos de activos de equipos de producción con jerarquía de personal de mantenimiento:

Gráfico de personas de fábrica, equipamiento y medidas

Los datos de esas entidades se pueden almacenar directamente en el clúster o adquirirse mediante la federación de consultas en un servicio diferente. Para ilustrar el ejemplo, se crean los siguientes datos tabulares como parte de la consulta:

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

Los empleados, sensores y otras entidades y relaciones no comparten un modelo de datos canónico. El operador union se puede usar para combinar y estandarizar los datos.

La consulta siguiente combina los datos del sensor con los datos de serie temporal para identificar sensores con lecturas anómalos y, a continuación, usa una proyección para crear un modelo común para los nodos 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));

Los bordes se transforman de forma similar.

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 los nodos estandarizados y los datos de bordes, puede crear un grafo mediante el operador make-graph.

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

Una vez creado el gráfico, defina el patrón de ruta de acceso y proyecta la información necesaria. El patrón comienza en un nodo de etiqueta, seguido de una arista de longitud variable a un activo. Ese recurso lo opera un operador que informa a un administrador superior a través de un borde de longitud variable denominado reportsTo. La sección de restricciones del operador graph-match, en este caso, la cláusula where filtra las etiquetas a aquellas con una anomalía que fueron operadas en un día 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)

Salida

EtiquetaConAnomalía activo afectado NombreDelOperador gerente responsable
temperatura Bomba Víspera Mallory

La proyección de graph-match muestra que el sensor de temperatura mostró una anomalía en el día especificado. El sensor fue operado por Eve, quien en última instancia informa a Mallory. Con esta información, el gerente de fábrica puede ponerse en contacto con Eve y, si es necesario, Mallory para comprender mejor la anomalía.

Procedimientos recomendados para gráficos persistentes

Los gráficos persistentes, definidos mediante modelos de grafos y instantáneas de grafos, proporcionan soluciones sólidas para las necesidades avanzadas de análisis de grafos. Estos gráficos se destacan en escenarios que requieren un análisis repetido de relaciones de datos grandes, complejas o en evolución, y facilitan la colaboración al permitir que los equipos compartan definiciones de grafos estandarizadas y resultados analíticos coherentes. Al conservar estructuras de grafos en la base de datos, este enfoque mejora significativamente el rendimiento de las consultas periódicas y admite funcionalidades de control de versiones sofisticadas.

Uso del esquema y la definición para la coherencia y el rendimiento

Un esquema claro para el modelo de grafos es esencial, ya que especifica los tipos de nodo y borde junto con sus propiedades. Este enfoque garantiza la coherencia de los datos y permite realizar consultas eficaces. Utilice la Definition sección para especificar cómo se construyen los nodos y los bordes a partir de los datos tabulares a través de los pasos AddNodes y AddEdges.

Aprovechamiento de etiquetas estáticas y dinámicas para el modelado flexible

Al modelar el grafo, puede usar enfoques de etiquetado estático y dinámico para lograr una flexibilidad óptima. Las etiquetas estáticas son ideales para los tipos de nodo y borde bien definidos que rara vez cambian, definirlos en la Schema sección y hacer referencia a ellos en la Labels matriz de los pasos. En los casos en los que los tipos de nodo o borde están determinados por valores de datos (por ejemplo, cuando el tipo se almacena en una columna), use etiquetas dinámicas especificando un LabelsColumn elemento en el paso para asignar etiquetas en tiempo de ejecución. Este enfoque es especialmente útil para gráficos con esquemas heterogéneos o en evolución. Ambos mecanismos se pueden combinar de forma eficaz: puede definir una Labels matriz para etiquetas estáticas y también especificar un LabelsColumn para incorporar etiquetas adicionales de los datos, lo que proporciona máxima flexibilidad al modelar gráficos complejos con categorización fija y controlada por datos.

Ejemplo: Uso de etiquetas dinámicas para varios tipos de nodo y borde

En el ejemplo siguiente se muestra una implementación eficaz de etiquetas dinámicas en un gráfico que representa relaciones profesionales. En este escenario, el gráfico contiene personas y empresas como nodos, con relaciones de empleo que forman los bordes entre ellos. La flexibilidad de este modelo procede de determinar los tipos de nodo y borde directamente de las columnas de los datos de origen, lo que permite que la estructura del grafo se adapte orgánicamente a la información subyacente.

.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"
      }
    ]
  }
}
```

Este enfoque de etiquetado dinámico proporciona una flexibilidad excepcional al modelar gráficos con numerosos tipos de nodo y borde, lo que elimina la necesidad de modificar el esquema cada vez que aparece un nuevo tipo de entidad en los datos. Al desacoplar el modelo lógico de la implementación física, el gráfico puede evolucionar continuamente para representar nuevas relaciones sin necesidad de cambios estructurales en el esquema subyacente.

Estrategias de creación de particiones multiinquilino para escenarios de ISV a gran escala

En organizaciones grandes, especialmente en escenarios de ISV, los gráficos pueden constar de varios miles de millones de nodos y bordes. Esta escala presenta desafíos únicos que requieren enfoques estratégicos de creación de particiones para mantener el rendimiento a la vez que se administran los costos y la complejidad.

Descripción del desafío

Los entornos multiinquilino a gran escala suelen mostrar las siguientes características:

  • Miles de millones de nodos y bordes : gráficos a escala empresarial que superan las funcionalidades tradicionales de la base de datos de grafos
  • Distribución del tamaño de los inquilinos - Normalmente sigue una ley de potencia donde 99,9% de los inquilinos tienen gráficos pequeños a medianos, mientras que 0,1% tienen gráficos muy grandes.
  • Requisitos de rendimiento : necesidad de análisis en tiempo real (datos actuales) y funcionalidades de análisis histórico
  • Consideraciones sobre los costos : equilibrio entre los costos de infraestructura y las funcionalidades analíticas

Particionamiento por límites naturales

El enfoque más eficaz para administrar grafos a gran escala es crear particiones por límites naturales, normalmente identificadores de inquilino o unidades organizativas:

Estrategias de creación de particiones clave:

  • Creación de particiones basada en inquilinos : separar gráficos por cliente, organización o unidad de negocio
  • Creación de particiones geográficas : división por región, país o ubicación del centro de datos
  • Creación de particiones temporales : separación por períodos de tiempo para el análisis histórico
  • Creación de particiones funcionales : división por dominio empresarial o área de aplicación

Ejemplo: Estructura organizativa multiinquilino

// 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

Enfoque híbrido: gráficos transitorios frente a persistentes por tamaño de inquilino

La estrategia más rentable combina gráficos transitorios y persistentes en función de las características del inquilino:

Inquilinos pequeños a medianos (99,9% de inquilinos)

Use gráficos transitorios para la mayoría de los inquilinos:

Ventajas:

  • Always up-to-date data : no se requiere mantenimiento de instantáneas
  • Menor sobrecarga operativa : no hay ningún modelo de grafo ni administración de instantáneas
  • Económico - sin costos de almacenamiento adicionales para estructuras de grafos
  • Disponibilidad inmediata : no hay retrasos en el procesamiento previo

Patrón de implementación:

.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

Inquilinos grandes (0.1% de inquilinos)

Usa gráficos persistentes para los inquilinos más grandes.

Ventajas:

  • Escalabilidad : control de gráficos que superan las limitaciones de memoria
  • Optimización del rendimiento : eliminación de la latencia de construcción para consultas complejas
  • Análisis avanzado : compatibilidad con sofisticados algoritmos y análisis de grafos
  • Análisis histórico : varias instantáneas para la comparación temporal

Patrón de implementación:

// 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)

Procedimientos recomendados para escenarios de ISV

  1. Comience con gráficos transitorios - inicie todas las nuevas implementaciones con gráficos transitorios para facilitar la administración
  2. Supervisión de patrones de crecimiento : implementación de la detección automática de inquilinos que requieren gráficos persistentes
  3. Creación de instantáneas por lotes - programar actualizaciones de instantáneas durante períodos de uso bajo
  4. Aislamiento de inquilinos : asegúrese de que los modelos de grafos y las instantáneas están correctamente aislados entre inquilinos
  5. Administración de recursos : use grupos de cargas de trabajo para evitar que las consultas de inquilinos grandes afecten a inquilinos más pequeños.
  6. Optimización de costos : revise y optimice periódicamente el umbral persistente o transitorio en función de los patrones de uso reales.

Este enfoque híbrido permite a las organizaciones proporcionar análisis de datos siempre actuales para la mayoría de los inquilinos, al tiempo que ofrece funcionalidades de análisis a escala empresarial para los inquilinos más grandes, optimizando tanto el costo como el rendimiento en toda la base de clientes.