Bookmarking is generally handled automatically by the driver and it is a way to ensure that subsequent transactions read from the same node as the ones that ran before.
The code block below uses the same session to firstly write some data, in this case adding an actor node and then reads that same node that has just been written, this means bookmarking is in effect and will guarantee we can read the previous write.
from neo4j import GraphDatabase
# create the driver object
uri = "xxxxx"
driver = GraphDatabase.driver(uri, auth=("neo4j", "xxxxx"))
def get_actors(tx, actor_name):
result = tx.run(
"MATCH (a:Actor) "
"WHERE a.name = $actor_name "
"RETURN a as actorsTx",
actor_name=actor_name) # Named parameters
return [record["actorsTx"] for record in result]
def write_actors(tx, actor_name):
result = tx.run(
"MERGE (a:Actor {name: $actor_name}) RETURN a.name as Name",
actor_name=actor_name)
return [record["Name"] for record in result]
try:
with driver.session() as session:
# Run a write query
write_actors = session.execute_write(write_actors, actor_name="Keanu Reeves")
for actor in write_actors:
print(f"Wrote actor: {actor}")
# Run a read query to read the written node within same function
actors = session.execute_read(get_actors, actor_name="Keanu Reeves")
for actor in actors:
print(f"Found actor: {actor}")
except Exception as e:
print(f"Exception while running query: {e}")
The second block of code below has been refactored to use two independent driver sessions, in this format we are not guaranteed to be able to read the previous write:
from neo4j import GraphDatabase
# create the driver object
uri = "xxxxx"
driver = GraphDatabase.driver(uri, auth=("neo4j", "xxxxx"))
def get_actors(tx, actor_name):
result = tx.run(
"MATCH (a:Actor) "
"WHERE a.name = $actor_name "
"RETURN a as actorsTx",
actor_name=actor_name) # Named parameters
return [record["actorsTx"] for record in result]
def write_actors(tx, actor_name):
result = tx.run(
"MERGE (a:Actor {name: $actor_name}) RETURN a.name as Name",
actor_name=actor_name)
return [record["Name"] for record in result]
try:
with driver.session() as session:
# Run a write query
write_actors = session.execute_write(write_actors, actor_name="Keanu Reeves")
for actor in write_actors:
print(f"Wrote actor: {actor}")
except Exception as e:
print(f"Exception while running write query: {e}")
try:
with driver.session() as session:
# Run the unit of work within a Read Transaction
actors = session.execute_read(get_actors, actor_name="Keanu Reeves")
for actor in actors:
print(f"Found actor: {actor}")
except Exception as e:
print(f"Exception while running read query: {e}")
The reason for this is that as they are different sessions, they don't share the same bookmark; so it is possible they can interact with different nodes. A write must happen on the leader, whereas a read can happen on any node, but it is typical for read queries to be given to followers rather than the leader. If the read happens on a different node, before the new data has propagated to the rest of the cluster, you can come across a situation where the newly written node may not be found.
If you decide to run this code yourself, you may not see this behaviour, as unless an instance is under a reasonable level of load this is unlikely to happen. For more demanding workloads this is essential to understand.
Comments
0 comments
Please sign in to leave a comment.