MATCH (n) without a label pays PostgreSQL table-inheritance planning cost proportional to the total number of vertex labels in the graph
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 72/100
調査の方向性
Start by reading src/backend/parser/cypher_clause.c:7125-7141 and run the supplied N-label SQL benchmark with EXPLAIN (ANALYZE, BUFFERS). Confirm how planning time changes for labelled and unlabelled MATCH queries, then inspect the manual's MATCH section. Done should clearly document the label-count planning cost, or provide the agreed warning if that scope is chosen.
索引モデルが issue の本文から書いたものです。
説明
Environment: Apache AGE 1.7.0, PostgreSQL 17. Graph with ~45,700 vertices (label
:Function among others) and ~139,000 :CALLS edges, on the order of a few dozen distinct
vertex labels total. Standard btree expression indexes present on the per-label child tables.
Symptom
An unlabelled MATCH (n) is dramatically more expensive to plan than the same pattern with
a label, even when both return comparable row counts and neither touches many rows at
execution time:
MATCH (n) RETURN n LIMIT 1
Planning Time: 133.367 ms
MATCH (n:Function) RETURN n LIMIT 1
Planning Time: 0.312 ms
That's 427x, and it's entirely in planning, not execution — the execution time for both
is negligible by comparison. This is easy to miss in practice because Cypher hides the
relational model underneath, so nothing about the query looks like it should be
label-count-sensitive.
Mechanism
Read in source (src/backend/parser/cypher_clause.c:7125-7141, AGE 1.7.0): when a MATCH
pattern has no label, the generated RTE points at the parent table (_ag_label_vertex for
nodes, the equivalent for edges) with inheritance active (inh = true). Every vertex label is
implemented as a PostgreSQL child table of that parent (<graph>."<Label>"), so with
inheritance active the planner has to expand and cost every child table — and every index on
every child table — during planning. With a label given, the RTE points directly at that one
child table, no expansion needed.
This is not an AGE executor bug — it's standard PostgreSQL inheritance-planning behavior,
working as designed. The gap is that nothing surfaces this cost to a Cypher user before they
hit it: an unlabelled MATCH (n) reads as a small, generic query, but its planning cost scales
with the total number of labels defined in the graph, independent of how selective the
pattern is or how many rows match.
Minimal reproduction (anyone can run this)
SELECT create_graph('label_scale_bench');
-- create N distinct vertex labels and insert a handful of vertices into each
DO $$
BEGIN
FOR i IN 1..50 LOOP
PERFORM create_vlabel('label_scale_bench', format('L%s', i));
EXECUTE format(
$q$SELECT * FROM cypher('label_scale_bench', $c$ CREATE (:L%s {v: 1}) $c$) AS (v agtype)$q$,
i
);
END LOOP;
END $$;
-- compare planning time, unlabelled vs labelled
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM cypher('label_scale_bench', $$ MATCH (n) RETURN n LIMIT 1 $$) AS (n agtype);
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM cypher('label_scale_bench', $$ MATCH (n:L1) RETURN n LIMIT 1 $$) AS (n agtype);
Repeat with N = 10, 50, 100, 200 labels and chart Planning Time for the unlabelled form against
N. We'd expect it to grow with the label count (at least linearly, likely worse once per-child
index stats get pulled in); the labelled form should stay flat.
Suggested fix (cheap end first)
This doesn't need an executor rewrite to be worth fixing:
- Document it — a line in the manual's
MATCHsection noting that an unlabelledMATCH
scans/plans over every vertex (or edge) label in the graph, and that this cost grows with
the number of distinct labels, would let people opt into labelling on purpose instead of
discovering it via a planning-time cliff. - Consider a
NOTICE/HINTwhen planning an unlabelledMATCHagainst a graph with more
than some threshold of labels, pointing at the label-count cost. - Longer term, this is presumably related to how much PostgreSQL's own inheritance-planning
cost can be reduced (partition pruning heuristics,constraint_exclusion, etc.) — but that's
a much bigger scope than this issue is asking for.
Happy to run the N-label benchmark script above and post the growth curve if that'd help
prioritize this.
- 主要言語
- C
- スター
- 4.9k
- フォーク
- 529
- 平均マージ
- 8日 15時間
- マージ済み PR(30日)
- 3
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/age のほかの issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
apache/age#2576 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 30/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
BasedHardware/omi#20401 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
php/frankenphp#2688 ·
メンテナーはふだん 1 日以内に返信
-
area/ysql kind/bug priority/medium
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
yugabyte/yugabyte-db#34584 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
ExpressLRS/ExpressLRS#3806 ·
メンテナーはふだん 2 日以内に返信