Оптимизация производительности запросов GQL для графа в Microsoft Fabric

В этой статье приводятся рекомендации по написанию запросов GQL (язык запросов Graph), которые выполняются прогнозируемо и эффективно при работе с графом в Microsoft Fabric. Рекомендации основаны на текущем поведении платформы и документированных ограничениях.

Сведения о жестких ограничениях на размер графа, размер результата и время ожидания запроса см. в разделе "Текущие ограничения". Несколько рекомендаций в этой статье также связаны с тем, как вы разрабатываете схему графа. Дополнительные сведения см. в разделе "Проектирование схемы графа".

Ранняя фильтрация в шаблонах

Поместите фильтры внутри шаблонов графа, а не в последующих инструкциях. Предложения уровня WHERE шаблона сокращают количество промежуточных результатов перед выполнением соединений и последующими инструкциями, что снижает общие затраты на выполнение.

Рекомендуется: Фильтруйте во время сопоставления шаблонов.

-- Pattern-level WHERE reduces intermediate results
MATCH (p:Person WHERE p.birthday < 19940101)-[:workAt]->(c:Company WHERE c.id > 1000)
RETURN p.firstName, p.lastName, c.name

Избегайте: поздней фильтрации с использованием отдельного оператора FILTER.

-- Statement-level filter runs after all pattern matches are produced
MATCH (p:Person)-[:workAt]->(c:Company)
FILTER p.birthday < 19940101 AND c.id > 1000
RETURN p.firstName, p.lastName, c.name

Оба запроса возвращают одинаковые результаты, но первая версия позволяет обработчику запросов обрезать строки ранее в процессе оценки.

Подсказка

Думайте о уровне WHERE шаблона как аналогично условию SQL JOIN ... ON . Он ограничивает совпадения на этапе оценки, а не после фильтрации всего результирующего набора.

Возвращает только необходимые свойства

Возвращайте только те свойства узла и ребра, которые требуются вашим сценарием. Избегайте возврата полных узлов или использования RETURN * , если требуется только подмножество свойств.

В графе таблицы OneLake поддерживают свойства узлов. При выборе ненужных свойств увеличиваются объемы данных для чтения, расходы на сериализацию и размер ответа. Во время моделирования графа вручную выберите столбцы из исходной таблицы, которую необходимо добавить в качестве свойств типа узла.

Рекомендуется: Узкая проекция.

MATCH (p:Person)-[:workAt]->(c:Company)
RETURN p.firstName, p.lastName, c.name

Избегайте: Возврата полных узлов.

MATCH (p:Person)-[:workAt]->(c:Company)
RETURN *

Замечание

Добавляйте только свойства типа узла во время моделирования графа, если они необходимы для запросов или анализа. Меньшее количество свойств на узел уменьшает как нагрузку на хранилище, так и нагрузку на запросы.

Ограничение размера результирующих наборов

При запросе узлов или связей, которые могут иметь высокую кратность, используйте LIMIT или другие ограничивающие условия. Неограниченные совпадения графа могут создавать очень большие множества результатов, которые приближаются к ограничениям платформы.

Рекомендуется: Ограниченные результаты.

MATCH (p:Person)-[:knows]->(friend:Person)
RETURN p.firstName, friend.firstName
LIMIT 1000

Избежать: Неограниченное совпадение с высокой кардинальностью.

MATCH (p:Person)-[:knows]->(friend:Person)
RETURN p.firstName, friend.firstName

Это важно

Обрезка ответов графа происходит, если их размер превышает 64 МБ, и производительность агрегации может быть нестабильной, если результаты превышают 128 МБ. Используйте FILTER, LIMIT и GROUP BY, чтобы сохранить результаты в пределах этих границ. Дополнительные сведения см. в разделе "Текущие ограничения".

Держите обходы поверхностными и направленными

Избегайте глубоко вложенных или очень сложных шаблонов графов. Используйте простые, целевые обходы, которые напрямую отвечают на конкретный вопрос. Каждый дополнительный прыжок в шаблоне переменной длины может экспоненциально увеличить количество путей, вычисляемых подсистемой, особенно в плотно подключенных графах.

Рекомендуется: Жёсткие ограничения.

-- Use the narrowest hop range that answers your question
MATCH (p:Person)-[:knows]->{1,3}(friend:Person)
RETURN p.firstName, friend.firstName
LIMIT 1000

Избегать: максимальной глубины обхода, если нет четкой необходимости.

-- Exploring the full 8-hop limit on a dense graph is expensive
MATCH (p:Person)-[:knows]->{1,8}(friend:Person)
RETURN *

Это важно

Graph поддерживает до восьми прыжков в шаблонах переменной длины. Даже в этом случае используйте самые строгие границы вашего сценария. В примере {1,3} шаблон значительно дешевле, чем {1,8} на том же графике.

Использование TRAIL для предотвращения избыточных обходов

Используйте режим обхода TRAIL для предотвращения повторного прохождения одного и того же ребра движком запросов. В плотных графах циклы могут привести к экспоненциальному увеличению числа путей. TRAIL гарантирует, что каждый край просматривается не более одного раза за путь, что повышает правильность и производительность.

-- TRAIL prevents revisiting the same :knows edge
MATCH TRAIL (src:Person)-[:knows]->{1,4}(dst:Person)
WHERE src.firstName = 'Alice' AND dst.firstName = 'Bob'
RETURN count(*) AS numPaths

Без TRAIL этого один и тот же запрос на циклическом графе может создавать гораздо больший (и часто избыточный) результирующий набор.

Использование общих переменных для эффективных соединений

Если запросу требуются данные из нескольких связей, используйте общую переменную для объединения шаблонов в одной сущности. Без общей переменной шаблоны могут производить декартовый продукт - каждое сочетание совпадений из обоих шаблонов - что приводит к гораздо большему результирующем набору.

Рекомендуется: Общая переменная p присоединяет шаблоны.

-- Single shared variable ensures an efficient join
MATCH (p:Person)-[:workAt]->(c:Company),
      (p)-[:isLocatedIn]->(city:City)
RETURN p.firstName, c.name AS company, city.name AS city
LIMIT 1000

Не используйте независимые шаблоны, не имеющие общую переменную.

-- Without a shared variable, this produces a cartesian product
MATCH (p1:Person)-[:workAt]->(c:Company),
      (p2:Person)-[:isLocatedIn]->(city:City)
RETURN p1.firstName, c.name, p2.firstName, city.name

Декартово произведение соединяет каждый результат из одного шаблона с каждым результатом из другого. Если Person-workAt->Company соответствует 1000 строкам и Person-isLocatedIn->City соответствует 500 строкам, запрос возвращает 1000 × 500 = 500 000 строк. Добавление общей переменной ограничивает соединение, поэтому возвращаются только соответствующие пары.

Определение ограничений ключей на узлах

Определите ограничения ключа узла в типе графа. Ограничения ключей позволяют системе оптимизировать запросы, которые ищут определенные узлы по их ключевым свойствам, как и индексы первичного ключа в реляционных базах данных.

Например, если ваш тип графа определяет id в качестве ключа для узлов Person:

CONSTRAINT person_pk
  FOR (n:Person) REQUIRE n.id IS KEY

Затем запросы, которые фильтруют по id, могут использовать этот ключ для прямого поиска.

-- Fast: the engine can look up person 12345 directly using the key
MATCH (p:Person WHERE p.id = 12345)-[:workAt]->(c:Company)
RETURN p.firstName, c.name

Без фильтра в свойстве ключа движок должен сканировать каждый Person узел.

-- Slower: scans all Person nodes before traversing
MATCH (p:Person)-[:workAt]->(c:Company)
RETURN p.firstName, c.name

Подсказка

Если вам нужен определенный узел, отфильтруйте его ключевое свойство в шаблоне MATCH , чтобы воспользоваться заданным ограничением.

Выбор соответствующих типов данных

Выберите наиболее конкретный тип данных для каждого свойства во время моделирования графа. Выбор правильных типов важен как для эффективности хранения, так и для производительности запросов. Например, числовые сравнения свойств INT быстрее, чем сравнения строк по эквивалентным STRING значениям.

Сведения о поддерживаемых типах данных см. в разделе "Текущие ограничения" — типы данных и поддерживаемые типы свойств.

По возможности извлекайте связанные сущности в одном шаблоне графа, вместо отправки отдельных запросов, которые проходят по тем же краям отдельно. Объединение обходов позволяет избежать сопоставления избыточных шаблонов и предотвращает проблему запроса N+1, при которой один начальный запрос активирует отдельный запрос для каждой строки результата.

Рекомендуется: Единый объединенный шаблон.

MATCH (c:Customer)-[:purchased]->(o:Order)-[:contains]->(product:Product)
RETURN c.id, o.id, product.name
LIMIT 1000

Избегайте: Два отдельных запроса, которые используют одно и то же Customer → Order ребро.

-- Query 1: fetch 100 orders
MATCH (c:Customer)-[:purchased]->(o:Order)
RETURN c.id, o.id

-- Query 2: run once per order to get products (N+1 problem)
MATCH (o:Order)-[:contains]->(product:Product)
RETURN o.id, product.name

Тестирование запросов к реалистичным томам данных

Запросы, которые хорошо выполняются в небольших наборах данных, могут не масштабироваться линейно. Проверьте запросы с помощью объемов данных, представляющих ожидаемую рабочую нагрузку.

  • Предпочитайте консервативные фигуры запросов, включающие фильтры и ограничения.
  • Избегайте разведочных запросов "возвращающие все" к большим графам.
  • Отслеживайте длительность запроса относительно 20-минутного ограничения времени ожидания.