Chapter 13 · Application Drivers and Bolt: Sessions, Routing, Parameters, Result Streaming, and Connection Pools

Read/Write Routing, Bookmarks, Retry Logic, Error Classification, and Observability Correlation

Route and retry deliberately, use bookmarks only for required causal dependencies, and correlate client requests with database work without overclaiming consistency.

Advanced170–215 minutesRouting/bookmark/retry correlation labNeo4j 2026.07.1 Community · Cypher 25Python driver 6.3.0 · Bolt 6/5 awareLast reviewed: September 2026

Learning outcomes

AtlasMart writes an order and immediately serves a read from another service instance. In a cluster, routing and replication mean “which server answered?” matters. The driver can route reads/writes and carry causal bookmarks, but bookmarks are not a blanket serializability guarantee and indiscriminate retries can duplicate external side effects.

01

Use routing-enabled URI/access modes without treating read mode as authorization.

02

Explain routing-table refresh and the direct-member bypass created by bolt://.

03

Use bookmarks to establish causal read-after-write dependencies across sessions where required.

04

Let managed transactions retry only retryable database work and keep callbacks idempotent.

05

Correlate client request IDs, transaction metadata, result summaries and server observations.

Chapter 13 baseline · reviewed 9 September 2026

The mandatory lab continues Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 where query-language version matters, container atlasmart-neo4j, loopback Bolt 127.0.0.1:7687, authentication neo4j/atlasmart-course-2026, and no TLS on the loopback-only disposable instance. The current official Python driver is neo4j 6.3.0 (Python 3.10–3.14), whose API supports Bolt 6.0–6.1, Bolt 5.0–5.8 and Bolt 4.4. Neo4j 5.26.30 remains the LTS comparison line.

Evidence boundary

This generation environment does not connect to the AtlasMart Neo4j container, so no handshake, routing-table, pool, TLS, bookmark, latency or retry output is fabricated. Each exercise gives commands and invariants to capture on your machine. True multi-member read/write routing requires an Enterprise cluster or Aura deployment; the mandatory Community path proves the driver/session/pool/stream semantics locally and labels cluster-only observations separately.

1. Routing is placement, not security

Client choice Routing intent Important boundary
RoutingControl.READ / execute_read() route read work to eligible readers in a cluster the driver does not parse Cypher for authorization; read mode is not a security control
RoutingControl.WRITE / execute_write() route write work to an eligible primary/writer default for execute_query() unless overridden
neo4j:// obtain/refresh routing table requires advertised/discoverable addresses appropriate to deployment
bolt:// direct connection to named member bypasses routing table; caller owns availability/role implications

2. Parameterized read/write with explicit routing intent

Python · write then read
records, write_summary, _ = store.driver.execute_query(    """    CYPHER 25    MERGE (o:Order {orderId:$orderId})    ON CREATE SET o.createdAt=datetime()    SET o.status=$status    RETURN o.orderId AS orderId    """,    orderId="O-DRIVER-1301", status="PAID",    database_="neo4j", routing_=RoutingControl.WRITE,)records, read_summary, _ = store.driver.execute_query(    """    CYPHER 25    MATCH (o:Order {orderId:$orderId})    RETURN o.status AS status    """,    orderId="O-DRIVER-1301", database_="neo4j",    routing_=RoutingControl.READ,)

execute_query() uses a driver bookmark manager by default, so sequential calls on that driver can be causally chained. In high-throughput systems, evaluate the latency cost of bookmark coordination and only require cross-session causal waiting where the business dependency needs it.

3. Bookmarks express “after this state,” not global serializability

Python · explicitly chain two sessions
with store.driver.session(database="neo4j") as writer:    writer.execute_write(lambda tx: tx.run(        "MERGE (o:Order {orderId:$id}) SET o.status='PAID'",        id="O-BOOKMARK-1").consume())    bookmark = writer.last_bookmarks()with store.driver.session(database="neo4j", bookmarks=bookmark) as reader:    status = reader.execute_read(lambda tx: tx.run(        "MATCH (o:Order {orderId:$id}) RETURN o.status AS status",        id="O-BOOKMARK-1").single()["status"])    print(status)

The reader will not execute before the bookmark state is established on the chosen server. This is a causal dependency. It does not order unrelated clients globally, eliminate transaction races, or replace uniqueness/locking/invariant design.

4. Retry only work the driver classifies as retryable

execute_read()/execute_write() managed transaction callbacks may be executed more than once when a retryable failure occurs, until the retry budget is exhausted. The callback therefore needs idempotent database behavior and must not send an email, charge a card or mutate non-transactional global state on each attempt.

Python · transaction callback with request metadata
from neo4j import unit_of_work@unit_of_work(timeout=5.0, metadata={"app":"atlasmart-api","operation":"reserve"})def reserve(tx, reservation_id, product_id):    record = tx.run(        """        CYPHER 25        MERGE (r:Reservation {reservationId:$reservationId})        ON CREATE SET r.createdAt=datetime()        WITH r        MATCH (p:Product {productId:$productId})        MERGE (r)-[:RESERVES]->(p)        RETURN r.reservationId AS id        """,        reservationId=reservation_id, productId=product_id,    ).single(strict=True)    return record["id"]with store.driver.session(database="neo4j") as session:    reservation_id = session.execute_write(        reserve, "RSV-1301", "P-1001")

Generate the idempotency key RSV-1301 outside the callback and enforce its uniqueness. Perform external side effects after the database operation through an outbox/reconciliation design, not directly inside a retryable callback.

5. Error classification and ambiguous outcomes

Python · classify instead of retrying everything
from neo4j.exceptions import Neo4jError, ServiceUnavailable, AuthErrortry:    do_database_work()except AuthError:    raise  # credentials/configuration: do not blind-retryexcept Neo4jError as exc:    if exc.is_retryable():        # explicit transactions may choose a bounded retry policy.        # managed transactions already implement retries.        raise    raiseexcept ServiceUnavailable:    # map to your availability policy; outcome may need reconciliation    raise
Ambiguous commit outcome

A client timeout or connection loss does not prove a transaction failed to commit. For business-critical writes, use idempotency keys and reconciliation queries so a retry can discover/confirm the already-committed state rather than duplicating it.

6. Correlate client and server work

Python · Query metadata with request correlation
from neo4j import Queryrequest_id = "req-20260909-00042"query = Query(    "CYPHER 25 MATCH (o:Order {orderId:$id}) RETURN o.status AS status",    timeout=3.0,    metadata={"app":"atlasmart-api", "request_id":request_id, "operation":"order-read"},)with store.driver.session(database="neo4j") as session:    result = session.run(query, id="O-5001")    record = result.single(strict=True)    summary = result.consume()    print(request_id, record["status"],          summary.result_available_after, summary.result_consumed_after)
Cypher · server-side visibility when supported/authorized
SHOW TRANSACTIONS YIELD transactionId, currentQuery, metaData, elapsedTime, statusWHERE metaData.request_id = 'req-20260909-00042'RETURN transactionId, currentQuery, metaData, elapsedTime, status;

SHOW TRANSACTIONS visibility/privileges differ by edition and user. Community local admin can use it for learning, while production access should follow least privilege.

Production judgment

Question Safe interpretation
Should every read use a bookmark? No. Use causal coordination when a read must observe a particular prior write; extra waiting can cost latency.
Can READ routing stop writes? No. Access mode is routing intent, not authorization.
Can every exception be retried? No. Retry only documented retryable/transient conditions, within a budget, and only for idempotent work.
Does timeout mean rollback? No. Outcome can be ambiguous after network/client timeout; reconcile by stable business identity.
What makes routing observable? Driver pool/debug logs for diagnosis plus server/request metadata; true multi-role behavior needs a cluster/Aura environment.

Check your understanding

  1. What does a bookmark guarantee?
  2. Why should a managed transaction callback avoid sending an email?
  3. Why use neo4j:// instead of bolt:// for a cluster?
  4. What should you do after an ambiguous timeout on an idempotent order write?
  5. Are driver debug logs a stable telemetry API?
Review the answers

1. A later transaction can wait until the represented database state is established, supporting causal ordering; it is not global serializability.

2. The callback may be executed more than once during retries, so a non-transactional side effect can duplicate.

3. It enables routing discovery and role-aware placement instead of pinning the named address.

4. Query by the stable order/idempotency key and reconcile the committed state before deciding to repeat.

5. No. Their exact format is not an API contract; use them for diagnosis, not production parsing.

Summary and next step

Routing, bookmarks and retries are reliable only when repository methods own stable identities, transaction boundaries and observability. Lesson 5 assembles those parts into a small AtlasMart service layer.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.