RedisDays Available Now On-Demand.

RedisGraph 2.8 Is Out!

Today we’re happy to announce the General Availability release of RedisGraph 2.8. This blog post details the major new features that are now available.

About RedisGraph

RedisGraph is a high-performance, memory-first graph data structure for Redis. RedisGraph supports graph multi-tenancy (it can hold many graphs simultaneously) and can serve multiple clients accessing the graphs simultaneously. It is now available also as part of Redis Stack.

Major new features in RedisGraph 2.8

  • Richer graph model
    • Multi-labeled nodes
  • Enhanced querying capabilities
    • Enhanced full-text search
    • Supporting more Cypher constructs, functions, and operators
  • Performance improvements
    • Indexes over relationship properties
    • Delta Matrices
    • Controllable node creation buffer
    • Benchmarks

https://redis.com/blog/redisgraph-2-8-is-generally-available/(opens in a new tab)

Richer graph model

Multi-labeled nodes

Many definitions of the Labelled Property Graph (LPG) data model (e.g. The Property Graph Database Model – Angles, 2018, and the ISO/IEC JTC 1/SC 32 – GQL draft) specify that a node can have multiple labels. Until v2.8, RedisGraph supported only a single label. As of now, we can add multiple labels to each node without any performance degradation or significant memory increase.

To create a node with multiple labels, you simply list all the labels separated by a colon:

GRAPH.QUERY g "CREATE (e:Employee:BoardMember {Name: 'Vincent Chan', Title: 'Web marketing lead'}) return e"

To match a node with multiple labels (AND condition), you should use the same colon notation as well:

GRAPH.QUERY g "MATCH (e:Employee:BoardMember) return e"

Enhanced Querying Capabilities

Enhanced full-text search

RedisGraph comes embedded with RedisSearch and is leveraged for secondary indexing but it can also be used for advanced indexing and searching. For example – finding nodes based on geographical proximity to a given point on earth, or scoring related items higher.

Version 2.8 adds the language and stopwords configuration options. language defines which language to use for stemming text – which is adding the base form of a word to the index. This allows the query for “going” to also return results for “go” and “gone”, for example. stopwords are very common words (like “is, the, an, and…”) that do not add much information to search but take up a lot of space in the index. These words are not indexed and ignored when searching. A query term with a stopword in it, like “in Paris” would be seen as “Paris” only.

To construct a full-text index on the title property of movies using the German language and using custom stopwords of all nodes with the label Movie:

GRAPH.QUERY DEMO_GRAPH "CALL db.idx.fulltext.createNodeIndex({ label: 'Movie', language: 'German', stopwords: ['a', 'ab'] }, 'title')"

RediSearch provides 3 additional field configuration options: 

  • weight – the importance of the text in the field
  • nostem – skip stemming when indexing text
  • phonetic – enable phonetic search on the text

To construct a full-text index on the title property with a phonetic search of all nodes with the label Movie:

GRAPH.QUERY DEMO_GRAPH "CALL db.idx.fulltext.createNodeIndex('Movie', {field: 'title', phonetic: 'dm:en'})"

Supporting more Cypher construct, functions, and operators

RedisGraph 2.8 extended Cypher support coverage:

  • Pattern comprehensions
  • Add support for allShortestPaths function
  • Cypher functions: keys, reduce, replace, none, and single
  • Copying of node attribute-sets in SET clauses
  • Filtering by node label in WHERE clauses
  • Cypher operators: XOR and ^

Pattern comprehensions

Pattern comprehension is a syntactic construct available in Cypher. While list comprehensions allow us to create a list based on existing lists, pattern comprehension is a way to populate a list with the results of matchings of a pattern. It will match the specified pattern as in a standard MATCH clause, with predicates as in a standard WHERE clause, but yield a specified projection.

For example, the following query would return a list containing all grant types received by male employees where the grant amount was larger than $1000.

GRAPH.QUERY g "CREATE (e:Employee {gender:'Male'})-[:granted]->(g:Grant {type: 'Research', amount: 2000})"

GRAPH.QUERY g "MATCH (e:Employee {gender:'Male'}) RETURN [(e)-[:granted]->(g:Grant) WHERE g.amount > 1000 | g.type] AS grantTypes"

Add support for allShortestPaths function

The allShortestPaths function returns all the shortest paths between a pair of entities matching all criteria. Both entities must be bound in an earlier WITH-demarcated scope.

GRAPH.QUERY DEMO_GRAPH "MATCH (c:Actor {name: 'Charlie Sheen'}), (k:Actor {name: 'Kevin Bacon'}) WITH c, k MATCH p = allShortestPaths((c)-[:PLAYED_WITH*]->(k)) RETURN nodes(p) as actors"

This query will produce all paths of the minimum length connecting the actor node representing Charlie Sheen to the one representing Kevin Bacon. There are several 2-hop paths between the two actors, and all of these will be returned. The computation of paths then terminates, as we are not interested in any paths of length greater than 2.

A minimal length (must be 1) and maximal length (must be at least 1) for the search may be specified. Zero or more relationship types may be specified (e.g. [:R|Q*1..3]). No property filters may be introduced in the pattern.

Add support for keys Cypher function

The keys function accepts a node, a relationship, or a map as an input, and returns an array of all the keys that the input contains.

MATCH (a) RETURN keys(a)
MATCH ()-[e]->() RETURN keys(e)
RETURN keys({a:1, b:2})

Add support for reduce Cypher function

The reduce function accepts a starting value and a list. It then updates the value by evaluating an expression against each element of the list.

GRAPH.QUERY g "RETURN reduce(sum = 0, n IN range(1,10) | sum + n)"

The output of this function will be 55 – the sum of the integers between 1 and 10.

GRAPH.QUERY g "RETURN reduce(arr = [], n IN range(1,10) | arr + [n*n])"

The output of this function will be an array containing the squares of the integers between 1 and 10.

Add support for replacing Cypher string function

Replaces all the occurrences of a given substring with another. The function receives 3 parameters: the original string, the occurrences to replace, and what to replace them with.

GRAPH.QUERY g "RETURN replace('abc*efg', '*', 'd')"
The return value will be ‘abcdefg’.

This function can also delete substrings by substituting them with an empty string (‘’).

Add support for none and single Cypher function

Given a list, none returns true if a predicate does not hold for any element, while single returns true if the given predicate holds for only one element.

GRAPH.QUERY g "RETURN none(x IN range(1,10) WHERE x>10)"
GRAPH.QUERY g "RETURN single(x IN range(1,10) WHERE x>9)"

These functions are similar to all and any functions.

One possible use case is path filtering:

graph.query DEMO_GRAPH “MATCH p = (a {name:’Johnny Depp’})-[*2..5]->(b {name:’Kevin Bacon’}) WHERE none(n IN nodes(p) WHERE n.year > 1970) RETURN p”

This query will return all paths of length 2 to 5 from Johnny Depp to Kevin Bacon, which include no actor born after 1970.

Add support for copying of node attribute sets in SET clauses

The SET clause can be used to replace or append all the property values of one node with the property values of another node.

The following query will match two entities, and then replace all a’s properties with b’s properties:

GRAPH.QUERY g "MATCH (a {v: 1}), (b {v: 2}) SET a = b"

The following query will match two entities, and then add (or replace the values) of a’s properties with b’s properties.

GRAPH.QUERY g "MATCH (a {v: 1}), (b {v: 2}) SET a += b"

We can also change a relationship’s type without changing the properties:

GRAPH.QUERY g "MATCH (a)-[b]->(c) WHERE ID(b)=0 CREATE (a)-[d:bar]->(c) SET d=b DELETE b RETURN d"

Add support for filtering by node label in WHERE clause

It is now possible to filter by node label or relationship type also in the WHERE clause:

GRAPH.QUERY g "MATCH (a) WHERE a:L RETURN a"
GRAPH.QUERY g "MATCH (a)-[b]-(c) WHERE b:L RETURN b"

Add support for Cypher operators XOR and ^

GRAPH.QUERY g "RETURN true XOR true”
GRAPH.QUERY g "RETURN 2 ^ 3”

The results are false and 8, respectively.

Performance Improvements

Indexes over relationship properties

For nodes, we can introduce an index by issuing the following command

GRAPH.QUERY g "CREATE INDEX FOR (n:GRANTS) ON (n.GrantedBy)"

it is now possible to introduce indexes for relationships as well:

GRAPH.QUERY g "CREATE INDEX FOR ()-[r:R]-() ON (r.prop)"

Consider the following query:

GRAPH.QUERY g "MATCH (a)-[r:R {prop:5}]-(b) return *"

Let’s observe the execution plan before creating an index:

redis:6379> GRAPH.EXPLAIN g "MATCH (a)-[r:R {prop:5}]-(b) return *"

1) "Results"
2) "    Project"
3) "        Filter"
4) "            Conditional Traverse | (a)-[r:R]->(b)"
5) "                All Node Scan | (a)"

And this is the execution plan for the same query after creating an index:

redis:6379> GRAPH.EXPLAIN g "MATCH (a)-[r:R {prop:5}]-(b) return *"

1) "Results"
2) "    Project"
3) "        Edge By Index Scan | [r:R]"

Delta Matrices

Since version 2.8, graph nodes and relationships additions and deletions are much faster, as they are first updated in small delta matrices. The main matrices are then bulk-updated.

In RedisGraph, graphs are represented with adjacency matrices. Every node label and every relationship type in the graph have their own matrix. Prior, every time a new node was added to the graph, all the matrices needed to be resized, and the bigger the database, the more time this took.

redisgraph 2.8 illustration

Since v2.8, the time it takes to insert new nodes and relationships is substantially smaller and does not depend on the size of the graph anymore. This optimization was achieved by introducing two delta matrices for every matrix in the graph: one for node additions (D+) and one for node deletions (D-). Node additions and deletions are reflected in their appropriate delta matrix, and once a delta matrix reaches a threshold of 10000 nodes (configurable via the DELTA_MAX_PENDING_CHANGES configuration parameter), it is synchronized with the main matrix in a single bulk operation, emptied, and the same cycle can start again.

Controllable node creation buffer

A new load-time configuration parameter, NODE_CREATION_BUFFER, controls the amount of memory reserved in matrices for future node creations. For example, when set to 16,384, the matrices will have extra space for 16384 nodes upon creation. Whenever the extra space is depleted, the matrices’ size will increase by 16384.

Reducing this value will reduce memory consumption, but cause performance degradation due to the increased frequency of matrix reallocations. Conversely, increasing it might improve performance for write-heavy workloads but will increase memory consumption.

If the passed argument was not a power of 2, it will be rounded to the next-greatest power of 2 to improve memory alignment.

Benchmarks

We also added many other performance enhancements in addition to delta matrices. We demonstrate these improvements below using the LDBC SNB benchmark.
The LDBC SNB (Linked Data Benchmark Council – Social Network Benchmarks) is the industry-standard benchmark for comparing graph database real-world reads and writes workloads.

Overall data load is much faster in RedisGraph 2.8:

  • LDBC scale factor 1:
    RedisGraph 2.8 is 1.92 times faster than RedisGraph 2.4
  • LDBC scale factor 10:
    RedisGraph 2.8 is 2.00 times faster than RedisGraph 2.4
redisgraph 2.8 dataset graph

LDBC queries (both read and write) are executed much faster in RedisGraph 2.8:

  • Read queries:
    RedisGraph 2,8 is 2.32 times faster than RedisGraph 2.4
  • Write queries:
    RedisGraph 2,8 is 1.09 times faster than RedisGraph 2.4
redisgraph 2.8 latency graph

Restoring and syncing data (RDB and AOF) is much faster as well (up to several orders of magnitude faster in some situations).

RedisGraph is part of Redis Stack

RedisGraph is now part of Redis Stack. You can download the latest Redis Stack Server binaries for macOS, Ubuntu, or Redhat, or install with Docker, Homebrew, or Linux.

Experience RedisGraph using RedisInsight

RedisInsight is a visual tool for developers that provides an excellent way to explore the data from RedisTimes during development using Redis or Redis Stack. 

redisgraph 2.8 graph query image

You can execute graph queries and observe the results directly from the graphical user interface. RedisInsight can now visualize RedisGraph query results.

In addition, RedisInsight contains quick guides and tutorials for learning RedisGraph interactively.


Learn more on RedisGraph on redis.io and developer.redis.com.