Skip to content

v0.6.1

Choose a tag to compare

@prrao87 prrao87 released this 03 Nov 02:24
85fe9a1

What's Changed

No breaking changes, and no query returns different rows. Upgrading needs no code changes.

use_csr=True is now at least as fast as the join path on whole-relationship queries

Until 0.6.0, turning the CSR on could make some queries slower. A query that starts from every node of a label, such as "who has the most followers?", reads every edge of the relationship either way. The join path handled that well, but the CSR produced one row per edge and then grouped those rows again, so it lost by 1.3x to 3x. In 0.6.1 these queries are faster with the CSR than without it. You can leave use_csr=True on for traversal workloads without checking each query against the join path.

On graph-benchmark (100,000 people, 2.4 million FOLLOWS edges, tables in memory; median ms):

Query What it asks Join path use_csr=True, 0.6.0 use_csr=True, 0.6.1
q1 Top 3 most-followed people 12.5 28.8 7.6
q2 City of the most-followed person 30.6 91.8 12.2
q8 Number of two-hop follow paths 19.5 29.4 13.3
q9 Two-hop paths, filtered by age 18.1 23.5 12.8

The other graph-benchmark queries (q3 to q7) are unchanged, and so are the GDC SF1 suites (see below).

How

The planner now avoids building one row per edge when a query only needs a count, or a node's properties once:

  • Counting neighbours in place. When a query counts matches, the CSR counts each node's neighbours directly from its neighbour list and returns one row per node, instead of one row per edge for a hash aggregate to group again. This covers counting two-hop paths (RETURN count(*) over (a)-->(b)-->(c), with filters on any of the three nodes), and counting a node's neighbours before following the rest of the pattern (RETURN person.name, count(follower) over (follower)-->(person)-->(city)). Filters on the counted neighbours still apply.
  • Grouping by id, then looking properties up. RETURN b.id, b.name, count(*) groups by b's id alone and fetches b.name once per group afterwards, rather than carrying the name through every row. 0.5.1 added this for WITH b; it now applies to grouped properties too.
  • Fetching less. A hop under such a lookup no longer reads the looked-up properties for every edge, and a relationship variable that nothing reads ([r1:FOLLOWS]) no longer makes a hop fetch edge rows.

In explain(), a hop counted in place shows degrees=[...] on its CsrExpandExec line. The CSR guide's new When it helps section explains which queries gain from the CSR, how to spot a hop that reads a whole relationship, and that the planner falls back to a join only for hops the CSR can't express, never for speed.

Benchmarks and trade-offs

On both GDC SF1 suites (30 custom queries and the 14 Interactive complex queries), in memory and lazy modes, 0.6.1 is within noise of 0.6.0: the geometric means of the ratios are 1.010 to 1.014, and every answer matches. IC12 measured 10 to 12% slower, but that is within its swing between runs of identical builds (17 to 20 ms). #113 lists every query.

PRs

  • perf(csr): match the join path on whole-relationship queries by @prrao87 in #113
  • chore: bump version 0.6.0 → 0.6.1 by @prrao87 in #114

Full Changelog: v0.6.0...v0.6.1