From eb9780ae0f4e53e4f497d66be65df473be77ce45 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Tue, 21 Jul 2026 15:34:28 +0200 Subject: [PATCH 1/3] Revise cost of transactions python --- .../modules/ROOT/pages/performance.adoc | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/python-manual/modules/ROOT/pages/performance.adoc b/python-manual/modules/ROOT/pages/performance.adoc index 30a49d57..4a7f76a7 100644 --- a/python-manual/modules/ROOT/pages/performance.adoc +++ b/python-manual/modules/ROOT/pages/performance.adoc @@ -45,42 +45,46 @@ driver.session() [[transactions-cost]] == Be aware of the cost of transactions -When submitting queries through xref:query-simple.adoc[`.execute_query()`] or through xref:transactions.adoc#managed-transactions[`.execute_read/write()`], the driver wraps them into a <>. -This behavior ensures that the database always ends up in a consistent state, regardless of what happens during the execution of a transaction (power outages, software crashes, etc). -As a further robustness layer, the driver also retries failed transactions with an exponential backoff. - -Creating a safe execution context around a query yields an overhead that is small, but that adds up as the number of transactions increases. -When each query is sent as a transaction of its own, if one transaction fails and needs to be rolled back, all the other transactions are unaffected. -This is the safest mode of execution with respect to failures, but also the slowest due to the overhead of transactions scaling with the number of queries. - -.Each query as a separate transaction (low throughput) +Queries are always run within <> on the server, so that the database always ends up in a consistent state regardless of what happens during a transaction execution (power outages, software crashes, etc). +Queries submitted via xref:query-simple.adoc[`.execute_query()`] or xref:transactions.adoc#managed-transactions[`.execute_read/write()`] are also automatically retried in case of failure. +A transactions yields an overhead that is small, but that adds up as the number of transactions increases. + +As your workload grows, evaluate what level of isolation each query needs and be intentional about your usage of transactions. +There are essentially three approaches to running queries depending on your needs for _safety_ and _performance_. +Remember that there's no free lunch and that it's always a trade-off between the two. + +1. **One transaction ⟷ One query (lowest throughput, highest safety)** + +The safest mode of execution with respect to failures, because of the isolation and auto-retry guarantees, but also the slowest due to the overhead of transactions scaling with the number of queries. ++ +.Run 1000 queries, each as a separate transaction [source, python] ---- for i in range(1000): driver.execute_query("", database_="") - # or session.execute_read/write() calls + # or session.execute_read/write() calls, each with one query ---- -A more performant approach is to group all queries into a single transaction. -In this way, the transaction as a whole is isolated from others, but individual queries in the transaction are not isolated, and failure of one results in a rollback of all queries. - -.Group queries into one transaction (higher throughput) +2. **One transaction ⟷ Multiple queries (higher throughput, high safety)** + +When multiple queries are grouped into a single transaction, the transaction as a whole is isolated from others, whereas individual queries in the transaction are not isolated and failure of one query results in a rollback of all queries. ++ +.Run 10 transactions with 100 queries each [source, python] ---- def query(tx): - for i in range(1000): + for i in range(100): tx.run("") with driver.session(database="") as session: - people = session.execute_read(query) + for i in range(10): + people = session.execute_read(query) ---- -An even faster approach is to skip `.execute_read/write()` and call `.run()` directly on the session. -The queries run as auto-commit transactions and are still isolated from other concurrent queries, but if any of them fail, they will not be retried. -With this method, you trade some robustness for more throughput, as the queries are shot to the server as fast as it can handle. -One upper limit on the client size is given by the size of the connection pool: each call to `.run()` borrows a connection, so the amount of parallel work is limited by the number of available connections. - -.Queries as auto-commit transactions (highest throughput) +3. **Auto-commit transactions (highest throughput, lowest safety)** + +xref:query-advanced.adoc#implicit-transactions[Auto-commit transactions] shoot queries to the server as fast as it can handle. +Failed queries are not retried, and there's no exact guarantee on when changes get written to the database. +One upper limit on the client size is given by the size of the connection pool: each call to `session.run()` borrows a connection, so the amount of parallel work is limited by the number of available connections. ++ +.Run 1000 queries as auto-commit transactions [source, python] ---- with driver.session(database="") as session: From 4dc1cea0ecb05cd473e39825ce79825b84b96833 Mon Sep 17 00:00:00 2001 From: Stefano Date: Tue, 1 Sep 2026 12:21:44 +0200 Subject: [PATCH 2/3] Apply batched suggestions from code review Co-authored-by: Stefano --- python-manual/modules/ROOT/pages/performance.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-manual/modules/ROOT/pages/performance.adoc b/python-manual/modules/ROOT/pages/performance.adoc index 4a7f76a7..2700ea4e 100644 --- a/python-manual/modules/ROOT/pages/performance.adoc +++ b/python-manual/modules/ROOT/pages/performance.adoc @@ -46,7 +46,7 @@ driver.session() == Be aware of the cost of transactions Queries are always run within <> on the server, so that the database always ends up in a consistent state regardless of what happens during a transaction execution (power outages, software crashes, etc). -Queries submitted via xref:query-simple.adoc[`.execute_query()`] or xref:transactions.adoc#managed-transactions[`.execute_read/write()`] are also automatically retried in case of failure. +The driver automatically retries transactions started with xref:query-simple.adoc[`.execute_query()`] or xref:transactions.adoc#managed-transactions[`.execute_read/write()`] in case of failure. A transactions yields an overhead that is small, but that adds up as the number of transactions increases. As your workload grows, evaluate what level of isolation each query needs and be intentional about your usage of transactions. From 4e6bc9229672f98eb2ecbd1f26afeba97e64a234 Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 30 Sep 2026 08:19:53 +0200 Subject: [PATCH 3/3] Update python-manual/modules/ROOT/pages/performance.adoc Co-authored-by: Robsdedude --- python-manual/modules/ROOT/pages/performance.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-manual/modules/ROOT/pages/performance.adoc b/python-manual/modules/ROOT/pages/performance.adoc index 2700ea4e..7d0b708c 100644 --- a/python-manual/modules/ROOT/pages/performance.adoc +++ b/python-manual/modules/ROOT/pages/performance.adoc @@ -82,7 +82,7 @@ with driver.session(database="") as session: 3. **Auto-commit transactions (highest throughput, lowest safety)** + xref:query-advanced.adoc#implicit-transactions[Auto-commit transactions] shoot queries to the server as fast as it can handle. Failed queries are not retried, and there's no exact guarantee on when changes get written to the database. -One upper limit on the client size is given by the size of the connection pool: each call to `session.run()` borrows a connection, so the amount of parallel work is limited by the number of available connections. +One upper limit on the client side is given by the size of the connection pool: each call to `session.run()` borrows a connection, so the amount of parallel work is limited by the number of available connections. + .Run 1000 queries as auto-commit transactions [source, python]