Neo4j 4.2 Reads on the Leader
Neo4j’s cluster setup is great for ease of understanding and use, but it had one weakness that affected a particular use case.
The use case is the user who wants the High Availability (HA) of a cluster, but is not actually doing a large amount of transactions, or is doing something like a batch load in the quiet periods of the day.
The problem isn’t that the cluster doesn’t work – of course it does – it’s that you end up with a server that is largely doing nothing during the day, as it can only accept Write queries.
Why?
The core part of the cluster has 2 server types, a Leader and Followers. You write to the Leader, read from the Followers. In server versions < 4.2 (so 3.x, 4.1.x etc) you can only write to Leaders, you can’t read from them.
So in a heavy Read, light Write scenario your Leader will be underutilised.
4.2. Solution
Neo4j 4.2 comes with a new cluster_allow_reads_on_leader setting that allows you to use the Leader as a reader as well.
How?
The Official Drivers work by connecting to the server and requesting a routing table by calling:
CALL dbms.routing.getRoutingTable({})
Which will return us:
╒═════╤══════════════════════════════════════════════════════════════════════╕ │"ttl"│"servers" │ ╞═════╪══════════════════════════════════════════════════════════════════════╡ │300 │[{"addresses":["127.0.0.1:7403"],"role":"WRITE"},{"addresses":["127.0.│ │ │0.1:7401","127.0.0.1:7402"],"role":"READ"},{"addresses":["127.0.0.1:74│ │ │02","127.0.0.1:7403","127.0.0.1:7401"],"role":"ROUTE"}] │ └─────┴──────────────────────────────────────────────────────────────────────┘
Which we can break apart into their roles, so the Write
{"addresses":["127.0.0.1:7403"],"role":"WRITE"}
and the READ:
{"addresses":["127.0.0.1:7401","127.0.0.1:7402"],"role":"READ"}
When we add the causal_clustering.cluster_allow_reads_on_leader=true
setting to the servers – we can see:
╒═════╤══════════════════════════════════════════════════════════════════════╕ │"ttl"│"servers" │ ╞═════╪══════════════════════════════════════════════════════════════════════╡ │300 │[{"addresses":["127.0.0.1:7403"],"role":"WRITE"},{"addresses":["127.0.│ │ │0.1:7402","127.0.0.1:7401","127.0.0.1:7403"],"role":"READ"},{"addresse│ │ │s":["127.0.0.1:7402","127.0.0.1:7403","127.0.0.1:7401"],"role":"ROUTE"│ │ │}] │ └─────┴──────────────────────────────────────────────────────────────────────┘
Which, again, taking apart – Writer:
{"addresses":["127.0.0.1:7403"],"role":"WRITE"},
and Readers:
{"addresses":["127.0.0.1:7402","127.0.0.1:7401","127.0.0.1:7403"],"role":"READ"}
In this case, we get the Leader address (127.0.0.1:7403
) in the READ
list as well.
This means that the drivers can automatically route any Read transaction to the Leader as well as the Followers.
Dynamicism
The other really good news is that this is a dynamic configuration element, so you don’t have to restart a server to get the benefits. This opens the opportunity of setting it to false
when you have a Write load coming in, and true
when you’re in a predominantly Read load.
4.1 or lower workaround
Not everyone can update to 4.2
straight away, so is there anything you can do to utilise that Leader server in the quiet periods?
Yes… but…
You can point a driver specifically to an instance – if you use the neo4j
or bolt+routing
schemas you’ll get routing taking place, but if you switch to just bolt
you will target just a single instance.
The but here is that – in doing so – you lose the load balancing of the driver, and you’ll need to know which of the servers is actually the Leader for when you do send a Write query. If your load really is only read queries – then you don’t have to know which is the Leader.