From 22003a84fde218bf3a490d5ddc49da8a57ca7a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:48:42 +0100 Subject: [PATCH 01/12] initial --- ...ions-additions-removals-compatibility.adoc | 16 ++ .../subqueries-in-transactions.adoc | 271 ++++++++++++++---- 2 files changed, 238 insertions(+), 49 deletions(-) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index f46f870fc..adc5f3789 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -205,6 +205,22 @@ RETURN count(p) AS count | Feature | Details +a| +label:functionality[] +label:new[] +[source, cypher, role=noheader] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY 1 SECOND THEN FAIL +---- + +| New error handling option for `CALL { ... } IN TRANSACTIONS`: `ON ERROR RETRY`. +This option applies an exponential delay between retries for transaction batches failing due to transient errors, with an optional maximum retry duration, and handles failure based on a specified fallback error mode if the transaction doesn't succeed within the given time. +For more information, see xref:subqueries/subqueries-in-transactions#on-error-retry[`CALL` subqueries in transactions -> `ON ERROR RETRY`]. + a| label:functionality[] label:new[] diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index 3390e4667..0db24b9e3 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -27,7 +27,7 @@ CALL { } IN [[concurrency] CONCURRENT] TRANSACTIONS [OF batchSize ROW[S]] [REPORT STATUS AS statusVar] -[ON ERROR {CONTINUE | BREAK | FAIL}]; +[ON ERROR {CONTINUE | BREAK | FAIL | RETRY [FOR] [duration SEC[OND[S]]] [THEN {CONTINUE | BREAK | FAIL}]}] ---- == Loading CSV data @@ -309,34 +309,34 @@ CALL (g) { ---- ==== - [[error-behavior]] == Error behavior -Users can choose one of three different option flags to control the behavior -in case of an error occurring in any of the inner transactions of `CALL { ... } IN TRANSACTIONS`: +`CALL { ... } IN TRANSACTIONS` has four different behavioral options in case an error occurs in any of the inner transactions: xref:subqueries/subqueries-in-transactions.adoc#on-error-continue[`ON ERROR CONTINUE`], xref:subqueries/subqueries-in-transactions.adoc#on-error-break[`ON ERROR BREAK`], xref:subqueries/subqueries-in-transactions.adoc#on-error-fail[`ON ERROR FAIL`], and xref:subqueries/subqueries-in-transactions.adoc#on-error-retry[`ON ERROR RETRY`]. + +[IMPORTANT] +If an error occurs, any inner transactions that were successfully committed remain unchanged and are not rolled back. However, any inner transactions that failed are fully rolled back. This behavior applies regardless of which `ON ERROR` option is used. + -* `ON ERROR CONTINUE` to ignore a recoverable error and continue the execution of subsequent inner transactions. +[[on-error-continue]] +=== `ON ERROR CONTINUE` + +`ON ERROR CONTINUE` ignores recoverable errors and continues the execution of subsequent inner transactions. The outer transaction succeeds. -It will cause the expected variables from the failed inner query to be bound as null for that specific transaction. -* `ON ERROR BREAK` to ignore a recoverable error and stop the execution of subsequent inner transactions. The outer transaction succeeds. -It will cause expected variables from the failed inner query to be bound as null for all onward transactions (including the failed one). -* `ON ERROR FAIL` to acknowledge a recoverable error and stop the execution of subsequent inner transactions. The outer transaction fails. This is the default behavior if no flag is explicitly specified. +When an inner query fails, `ON ERROR CONTINUE` ensures the outer transaction continues, returning `null` for the failed inner query. -[IMPORTANT] -==== -On error, any previously committed inner transactions remain committed, and are not rolled back. Any failed inner transactions are rolled back. -==== +.`ON ERROR CONTINUE` +===== -In the following example, the last subquery execution in the second inner transaction fails -due to division by zero. +In the below query, the last subquery execution in the second inner transaction fails +due to division by zero: -.Query +.Subquery with failing transaction [source, cypher, role=test-fail] ---- UNWIND [4, 2, 1, 0] AS i CALL (i) { - CREATE (:Person {num: 100/i}) + CREATE (:Person {num: 100/i}) // Note, fails when i = 0 } IN TRANSACTIONS OF 2 ROWS RETURN i ---- @@ -347,9 +347,9 @@ RETURN i / by zero (Transactions committed: 1) ---- -When the failure occurred, the first transaction had already been committed, so the database contains two example nodes. +Since the failure occurred after the first transaction was committed, the database retains the successfully created nodes. -.Query +.Return nodes created prior to failed transaction [source, cypher] ---- MATCH (e:Person) @@ -360,14 +360,16 @@ RETURN e.num [role="queryresult",options="header,footer",cols="1* SEC[OND[S]]`, where is an `INTEGER` or `FLOAT` value representing seconds that is greater than or equal to `0`. +Decimal values are allowed, and the `` can be set with a parameter. + +The duration timer starts when the first retry is scheduled. +As a result, regardless of the specified duration, a minimum of one retry will be attempted. +If the transaction still fails with a transient error, a new attempt with made unless the duration has expired. +For example, a value of `0` (or close to `0`) will result in a retry, but guarantees that only a single retry will be attempted. + +.Set a maximum time limit for retries +===== + +In this example, the retry duration is explicitly set to `2.5` seconds. +This means that the transaction will be retried until it succeeds or until `2.5` seconds have elapsed. + +.`ON ERROR RETRY` with a set duration +[source, cypher] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY FOR 2.5 SECONDS +---- + +.Parameter +[source, parameters] +---- +{ + "duration": 10 +} +---- + +.`ON ERROR RETRY` using a parameter for the duration +[source, cypher] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY FOR $duration SECONDS +---- +===== + + +[[fallback-error-handling]] +==== Fallback error handling options + +`ON ERROR RETRY` can be combined with the other xref:subqueries/subqueries-in-transactions.adoc#error-behavior[`ON ERROR` options] via the `THEN` clause to specify a fallback behavior. +The fallback behavior specifies what will happen if a transaction has not succeeded within the time limit. +Specifically: + +* `ON ERROR RETRY ... THEN CONTINUE` (default): the query will ignore recoverable errors and continue with the execution of subsequent inner transactions. +The outer transaction will succeed, and `null` will be returned for any failed inner transactions. +See xref:subqueries/subqueries-in-transactions.adoc#on-error-continue[`ON ERROR CONTINUE`] for more information about this behavior. + +[NOTE] +Because `THEN CONTINUE` is the default fallback option it does not have to be specified. + +* `ON ERROR RETRY ... THEN BREAK`: the query will ignore recoverable errors and stop the execution of subsequent inner transactions. +The out transaction succeeds, and `null` will be returned for the failed inner transaction and all subsequent ones. +See xref:subqueries/subqueries-in-transactions.adoc#on-error-break[`ON ERROR BREAK`] for more information about this behavior. + +* `ON ERROR RETRY ... THEN FAIL`: the query will acknowledge a recoverable error and stop the execution of subsequent inner transactions, causing the outer transaction to fail. +all subsequent ones. +See xref:subqueries/subqueries-in-transactions.adoc#on-error-fail[`ON ERROR FAIL`] for more information about this behavior. + + +// tag::tabs[] +[.tabs] + +.ON ERROR RETRY THEN CONTINUE +[source,cypher] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY FOR 1 SECOND THEN CONTINUE +---- + +.ON ERROR RETRY THEN BREAK +[source,cypher] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY FOR 1 SECOND THEN BREAK +---- + +.ON ERROR RETRY THEN FAIL +[source,cypher] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY FOR 1 SECOND THEN FAIL +---- + +// end::tabs[] + [[status-report]] == Status report @@ -516,7 +689,7 @@ The status value is a map value with the following fields: Example of reporting status with `ON ERROR CONTINUE`: .Query -[source, cypher, indent=0, role=test-result-skip] +[source, cypher, role=test-result-skip] ---- UNWIND [1, 0, 2, 4] AS i CALL (i) { @@ -526,7 +699,7 @@ CALL (i) { OF 1 ROW ON ERROR CONTINUE REPORT STATUS AS s -RETURN n.num, s; +RETURN n.num, s ---- .Result @@ -543,7 +716,7 @@ RETURN n.num, s; Example of reporting status with `ON ERROR BREAK`: .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [1, 0, 2, 4] AS i CALL (i) { @@ -553,7 +726,7 @@ CALL (i) { OF 1 ROW ON ERROR BREAK REPORT STATUS AS s -RETURN n.num, s.started, s.committed, s.errorMessage; +RETURN n.num, s.started, s.committed, s.errorMessage ---- .Result @@ -580,7 +753,7 @@ CALL (i) { OF 1 ROW ON ERROR FAIL REPORT STATUS AS s -RETURN n.num, s.errorMessage; +RETURN n.num, s.errorMessage ---- .Error @@ -700,7 +873,7 @@ CALL (row) { MERGE (m)-[r:RELEASED_IN]->(y) } IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status WITH status -WHERE status.errorMessage IS NOT NULL +WHERE status.errorMessage IS NOT null RETURN status.transactionId AS transaction, status.committed AS commitStatus, status.errorMessage AS errorMessage ---- From 886f571ef23abfa40160a0a3a5fedd286f5932e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:54:47 +0100 Subject: [PATCH 02/12] mistake --- modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index 0db24b9e3..b3fa63058 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -873,7 +873,7 @@ CALL (row) { MERGE (m)-[r:RELEASED_IN]->(y) } IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status WITH status -WHERE status.errorMessage IS NOT null +WHERE status.errorMessage IS NOT NULL RETURN status.transactionId AS transaction, status.committed AS commitStatus, status.errorMessage AS errorMessage ---- From 8a52d4441b6e8ed16a29c9d7a6ebd38fd61ef726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:43:50 +0100 Subject: [PATCH 03/12] add concurrency example --- .../subqueries-in-transactions.adoc | 229 +++++++++++++----- 1 file changed, 164 insertions(+), 65 deletions(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index b3fa63058..f0a36923b 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -538,7 +538,6 @@ RETURN n.num === `ON ERROR RETRY` `ON ERROR RETRY` uses an exponential delay between retry attempts for transaction batches that fail due to transient errors (i.e. errors where retrying a transaction can be expected to give a different result), with an optional xref:subqueries/subqueries-in-transactions.adoc#specify-retry-duration[maximum retry duration]. -The time between retries increases after each failure. If the transaction still fails after the maximum duration, the failure is handled according to an optionally specified xref:subqueries/subqueries-in-transactions.adoc#fallback-error-handling[fallback error handling mode] (`THEN CONTINUE` (default), `THEN BREAK`, `THEN FAIL`). `ON ERROR RETRY` increases query robustness by handling transient errors without manual intervention. @@ -831,10 +830,16 @@ For example, when creating or deleting a relationship, a write lock is taken on A deadlock happens when two transactions are blocked by each other because they are attempting to concurrently modify a node or a relationship that is locked by the other transaction (for more information about locks and deadlocks in Neo4j, see link:{neo4j-docs-base-uri}/operations-manual/current/database-internals/concurrent-data-access/#_locks[Operations Manual -> Concurrent data access]. A deadlock may occur when using `CALL { ... } IN CONCURRENT TRANSACTIONS` if the transactions for two or more batches try to take the same locks in an order that results in a circular dependency between them. -If so, the impacted transactions are always rolled back, and an error is thrown unless the query is appended with `ON ERROR CONTINUE` or `ON ERROR BREAK`. +If so, the impacted transactions are always rolled back, and an error is thrown unless the query is appended with one of the following error options: + +* xref:subqueries/subueries-in-transactions.adoc#on-error-continue[`ON ERROR CONTINUE`] +* xref:subqueries/subueries-in-transactions.adoc#on-error-break[`ON ERROR BREAK`] +* xref:subqueries/subueries-in-transactions.adoc#on-error-retry[`ON ERROR RETRY`] label:new[Introduced in Neo4j 2025.03] + +The latter is particularly suited for concurrent transactions, because it retries recoverable transient errors with exponential backoff between retries until the xref:subqueries/subqueries-in-transactions.adoc#specify-retry-duration[maximum retry duration] has been reached. .Dealing with deadlocks -==== +===== The following query tries to create `Movie` and `Year` nodes connected by a `RELEASED_IN` relationship. Note that there are only three different years in the CSV file, meaning that only three `Year` nodes should be created. @@ -860,10 +865,7 @@ ForsetiClient[transactionId=64, clientId=12] can't acquire ExclusiveLock{owner=F Client[63] waits for [ForsetiClient[transactionId=64, clientId=12]]] ---- -The following query uses `ON ERROR CONTINUE` to bypass any deadlocks and continue with the execution of subsequent inner transactions. -It returns the `transactionID`, `commitStatus` and `errorMessage` of the failed transactions. - -.Query using `ON ERROR CONTINUE` to ignore deadlocks and complete outer transaction +.Query using `ON ERROR RETRY` to ignore deadlocks and complete outer transaction [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row @@ -871,7 +873,7 @@ CALL (row) { MERGE (m:Movie {movieId: row.movieId}) MERGE (y:Year {year: row.year}) MERGE (m)-[r:RELEASED_IN]->(y) -} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status +} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON CONTINUE REPORT STATUS as status WITH status WHERE status.errorMessage IS NOT NULL RETURN status.transactionId AS transaction, status.committed AS commitStatus, status.errorMessage AS errorMessage @@ -880,68 +882,165 @@ RETURN status.transactionId AS transaction, status.committed AS commitStatus, st .Result [source, "queryresult"] ---- -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| transaction | commitStatus | errorMessage | -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -| "neo4j-transaction-169" | FALSE | "ForsetiClient[transactionId=169, clientId=11] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=168, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(46) because holders of that lock are waiting for ForsetiClient[transactionId=169, clientId=11]. | -| | \ Wait list:ExclusiveLock[ | -| | \ Client[168] waits for [ForsetiClient[transactionId=169, clientId=11]]]" | -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ----- - -.Click to see an example of failed transactions being retried using Cypher -[%collapsible] -===== -While failed transactions may be more efficiently retried using a link:{neo4j-docs-base-uri}/create-applications[driver], below is an example how failed transactions can be retried within the same Cypher query: - -[NOTE] -The query below uses xref:clauses/filter.adoc#filter-with-where[`FILTER`] (introduced in Neo4j 2025.03) as a replacement for `WITH * WHERE `. - -.Query retrying failed transactions ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| transaction | commitStatus | errorMessage | ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | +| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. | +| | \ Wait list:ExclusiveLock[ | +| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" | ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +---- + +These are transient errors, meaning that re-running the transactions may be successful. +To retry the any failed inner transactions, use the error option `ON ERROR RETRY`, which retries any failing transactions until the maximum retry duration has been reached. + +The following query uses `ON ERROR RETRY` to retry the above query for a maximum of `3` seconds. +Note that `ON ERROR RETRY` by default falls back to the error option `THEN CONTINUE`, which ensures that any deadlocks are bypassed and that subsequent inner transactions are executed. + +.Retrying any failed inner transactions with `ON ERROR RETRY` [source, cypher] ---- -LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row -CALL (row) { - MERGE (m:Movie {movieId: row.movieId}) - MERGE (y:Year {year: row.year}) - MERGE (m)-[r:RELEASED_IN]->(y) -} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status -FILTER status.committed = false +CYPHER 25 LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row CALL (row) { - MERGE (m:Movie {movieId: row.movieId}) - MERGE (y:Year {year: row.year}) - MERGE (m)-[r:RELEASED_IN]->(y) -} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR FAIL + MERGE (m:Movie {movieId: row.movieId}) + MERGE (y:Year {year: row.year}) + MERGE (m)-[r:RELEASED_IN]->(y) +} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR RETRY FOR 3 SECONDS REPORT STATUS AS status +RETURN status.transactionID as transaction, status.committed AS successfulTransaction +---- + +The result shows that all transactions are now successful: + +.Result +[source, "queryresult"] ---- ++-------------------------------------------------+ +| transaction | successfulTransaction | ++-------------------------------------------------+ +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-500" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-501" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-502" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-504" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-503" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-505" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-506" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-507" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-508" | TRUE | +| "neo4j-transaction-509" | TRUE | +| "neo4j-transaction-509" | TRUE | +| "neo4j-transaction-509" | TRUE | ++-------------------------------------------------+ +---- + ===== -==== [[restrictions]] == Restrictions From bd571b839dd31ffd21990e1f59863bb1808bdfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:47:55 +0100 Subject: [PATCH 04/12] fix xrefs --- .../ROOT/pages/subqueries/subqueries-in-transactions.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index f0a36923b..b120408b5 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -832,9 +832,9 @@ A deadlock happens when two transactions are blocked by each other because they A deadlock may occur when using `CALL { ... } IN CONCURRENT TRANSACTIONS` if the transactions for two or more batches try to take the same locks in an order that results in a circular dependency between them. If so, the impacted transactions are always rolled back, and an error is thrown unless the query is appended with one of the following error options: -* xref:subqueries/subueries-in-transactions.adoc#on-error-continue[`ON ERROR CONTINUE`] -* xref:subqueries/subueries-in-transactions.adoc#on-error-break[`ON ERROR BREAK`] -* xref:subqueries/subueries-in-transactions.adoc#on-error-retry[`ON ERROR RETRY`] label:new[Introduced in Neo4j 2025.03] +* xref:subqueries/subqueries-in-transactions.adoc#on-error-continue[`ON ERROR CONTINUE`] +* xref:subqueries/subqueries-in-transactions.adoc#on-error-break[`ON ERROR BREAK`] +* xref:subqueries/subqueries-in-transactions.adoc#on-error-retry[`ON ERROR RETRY`] label:new[Introduced in Neo4j 2025.03] The latter is particularly suited for concurrent transactions, because it retries recoverable transient errors with exponential backoff between retries until the xref:subqueries/subqueries-in-transactions.adoc#specify-retry-duration[maximum retry duration] has been reached. From 0377adad5db814696818ade64a88ec991ec3ab42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:54:32 +0100 Subject: [PATCH 05/12] remove CYPHER 25 --- modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index b120408b5..e92569939 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -927,7 +927,7 @@ Note that `ON ERROR RETRY` by default falls back to the error option `THEN CONTI .Retrying any failed inner transactions with `ON ERROR RETRY` [source, cypher] ---- -CYPHER 25 LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row +LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row CALL (row) { MERGE (m:Movie {movieId: row.movieId}) MERGE (y:Year {year: row.year}) From 0b2513b2481ee1541dd2af7398fdf3cbc95a6ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:36:15 +0100 Subject: [PATCH 06/12] fix additions tables --- ...ions-additions-removals-compatibility.adoc | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index ee0867c3c..0dae30619 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -312,6 +312,34 @@ RETURN cosh(0.5), coth(0.5), sinh(0.5), tanh(0.5) For more information, see xref:functions/mathematical-trigonometric.adoc[Mathematical functions - trigonometric]. |=== +[[cypher-deprecations-additions-removals-2025.03]] +== Neo4j 2025.03 + +=== New features + +[cols="2", options="header"] +|=== +| Feature +| Details + +a| +label:functionality[] +label:new[] +[source, cypher, role=noheader] +---- +UNWIND range(1, 100) AS i +CALL (i) { + MERGE (u:User {id: i}) + ON CREATE SET u.created = timestamp() +} IN TRANSACTIONS ON ERROR RETRY 1 SECOND THEN FAIL +---- + +| New error handling option for `CALL { ... } IN TRANSACTIONS`: `ON ERROR RETRY`. +This option applies an exponential delay between retries for transaction batches failing due to transient errors, with an optional maximum retry duration, and handles failure based on a specified fallback error mode if the transaction doesn't succeed within the given time. +For more information, see xref:subqueries/subqueries-in-transactions#on-error-retry[`CALL` subqueries in transactions -> `ON ERROR RETRY`]. + +|=== + [[cypher-deprecations-additions-removals-2025.01]] == Neo4j 2025.01 From d33cbd37e4a515689e9927aeea7b005e945df082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:47:28 +0100 Subject: [PATCH 07/12] add default duration information --- .../ROOT/pages/subqueries/subqueries-in-transactions.adoc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index e92569939..89c22a657 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -547,7 +547,7 @@ It is particularly suitable for xref:subqueries/subqueries-in-transactions.adoc# ===== The below example demonstrates a basic retry scenario. -If a transient error occurs during the creation of a `User` node, the transaction will be retried with an implementation-defined default duration. +If a transient error occurs during the creation of a `User` node, the transaction will be retried for the default maximum retry duration (`30` seconds). If the retry succeeds, the query continues. If the retry fails after the default duration, it behaves like xref:subqueries/subqueries-in-transactions#on-error-continue[`ON ERROR CONTINUE`] (the default fallback). .`ON ERROR RETRY` @@ -565,8 +565,12 @@ CALL (i) { [[specify-retry-duration]] ==== Specify a maximum retry duration -A maximum retry duration can be specified as follows: `ON ERROR RETRY [FOR] SEC[OND[S]]`, where is an `INTEGER` or `FLOAT` value representing seconds that is greater than or equal to `0`. +The default maximum retry duration is `30` seconds. +A new maximum retry duration can be configured using the link:{neo4j-docs-base-uri}/operations-manual/current/configuration/configuration-settings/#config_dbms.cypher.transactions.default_subquery_retry_timeout[`dbms.cypher.transactions.default_subquery_retry_timeout`] setting. + +A maximum retry duration can be specified for individual queries as follows: `ON ERROR RETRY [FOR] SEC[OND[S]]`, where is an `INTEGER` or `FLOAT` value representing seconds that is greater than or equal to `0`. Decimal values are allowed, and the `` can be set with a parameter. +Note that this overrides the default value. The duration timer starts when the first retry is scheduled. As a result, regardless of the specified duration, a minimum of one retry will be attempted. From 1174bb58a5886508966ea91cf398fdff22a3965e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:59:02 +0100 Subject: [PATCH 08/12] reword --- modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index 89c22a657..4af040609 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -566,7 +566,7 @@ CALL (i) { ==== Specify a maximum retry duration The default maximum retry duration is `30` seconds. -A new maximum retry duration can be configured using the link:{neo4j-docs-base-uri}/operations-manual/current/configuration/configuration-settings/#config_dbms.cypher.transactions.default_subquery_retry_timeout[`dbms.cypher.transactions.default_subquery_retry_timeout`] setting. +A new default can be set using link:{neo4j-docs-base-uri}/operations-manual/current/configuration/configuration-settings/#config_dbms.cypher.transactions.default_subquery_retry_timeout[`dbms.cypher.transactions.default_subquery_retry_timeout`]. A maximum retry duration can be specified for individual queries as follows: `ON ERROR RETRY [FOR] SEC[OND[S]]`, where is an `INTEGER` or `FLOAT` value representing seconds that is greater than or equal to `0`. Decimal values are allowed, and the `` can be set with a parameter. From fff401dc749b65e927bae13c0ed6977469e810f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:58:05 +0100 Subject: [PATCH 09/12] fix on error continue example --- modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index 4af040609..bc63257bb 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -570,7 +570,7 @@ A new default can be set using link:{neo4j-docs-base-uri}/operations-manual/curr A maximum retry duration can be specified for individual queries as follows: `ON ERROR RETRY [FOR] SEC[OND[S]]`, where is an `INTEGER` or `FLOAT` value representing seconds that is greater than or equal to `0`. Decimal values are allowed, and the `` can be set with a parameter. -Note that this overrides the default value. +Note that this `` overrides the default value. The duration timer starts when the first retry is scheduled. As a result, regardless of the specified duration, a minimum of one retry will be attempted. @@ -877,7 +877,7 @@ CALL (row) { MERGE (m:Movie {movieId: row.movieId}) MERGE (y:Year {year: row.year}) MERGE (m)-[r:RELEASED_IN]->(y) -} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON CONTINUE REPORT STATUS as status +} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status WITH status WHERE status.errorMessage IS NOT NULL RETURN status.transactionId AS transaction, status.committed AS commitStatus, status.errorMessage AS errorMessage From c241c52b0d2ccb4f3a3853bb374ea5b048641dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 17 Mar 2025 10:17:18 +0100 Subject: [PATCH 10/12] add slotted runtime note --- modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index bc63257bb..e5cbf2a22 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -543,6 +543,9 @@ If the transaction still fails after the maximum duration, the failure is handle `ON ERROR RETRY` increases query robustness by handling transient errors without manual intervention. It is particularly suitable for xref:subqueries/subqueries-in-transactions.adoc#concurrent-transactions[concurrent transactions], reducing the likelihood of xref:subqueries/subqueries-in-transactions.adoc#deadlocks[deadlocks]. +[NOTE] +Queries using `ON ERROR RETRY` can currently only use the xref:planning-and-tuning/runtimes/concepts.adoc#runtimes-slotted-runtime[slotted runtime]. + .Basic retry with default duration ===== From ba05128830e46d00eae431529c6c5f392e121e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 17 Mar 2025 10:25:01 +0100 Subject: [PATCH 11/12] fix additions page --- ...tions-additions-removals-compatibility.adoc | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 0dae30619..497f94c08 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -205,22 +205,6 @@ RETURN count(p) AS count | Feature | Details -a| -label:functionality[] -label:new[] -[source, cypher, role=noheader] ----- -UNWIND range(1, 100) AS i -CALL (i) { - MERGE (u:User {id: i}) - ON CREATE SET u.created = timestamp() -} IN TRANSACTIONS ON ERROR RETRY 1 SECOND THEN FAIL ----- - -| New error handling option for `CALL { ... } IN TRANSACTIONS`: `ON ERROR RETRY`. -This option applies an exponential delay between retries for transaction batches failing due to transient errors, with an optional maximum retry duration, and handles failure based on a specified fallback error mode if the transaction doesn't succeed within the given time. -For more information, see xref:subqueries/subqueries-in-transactions#on-error-retry[`CALL` subqueries in transactions -> `ON ERROR RETRY`]. - a| label:functionality[] label:new[] @@ -335,7 +319,7 @@ CALL (i) { ---- | New error handling option for `CALL { ... } IN TRANSACTIONS`: `ON ERROR RETRY`. -This option applies an exponential delay between retries for transaction batches failing due to transient errors, with an optional maximum retry duration, and handles failure based on a specified fallback error mode if the transaction doesn't succeed within the given time. +This option applies an exponential delay between retries for transaction batches failing due to transient errors, with an optional maximum retry duration, and handles failure based on a specified fallback error mode if the transaction does not succeed within the given time. For more information, see xref:subqueries/subqueries-in-transactions#on-error-retry[`CALL` subqueries in transactions -> `ON ERROR RETRY`]. |=== From 88e246a3568fb518c2ec67b763bba0f51cb69ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 18 Mar 2025 09:45:02 +0100 Subject: [PATCH 12/12] post review corrections --- .../subqueries-in-transactions.adoc | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index e5cbf2a22..9a965302f 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -538,10 +538,11 @@ RETURN n.num === `ON ERROR RETRY` `ON ERROR RETRY` uses an exponential delay between retry attempts for transaction batches that fail due to transient errors (i.e. errors where retrying a transaction can be expected to give a different result), with an optional xref:subqueries/subqueries-in-transactions.adoc#specify-retry-duration[maximum retry duration]. -If the transaction still fails after the maximum duration, the failure is handled according to an optionally specified xref:subqueries/subqueries-in-transactions.adoc#fallback-error-handling[fallback error handling mode] (`THEN CONTINUE` (default), `THEN BREAK`, `THEN FAIL`). +If the transaction still fails after the maximum duration, the failure is handled according to an optionally specified xref:subqueries/subqueries-in-transactions.adoc#fallback-error-handling[fallback error handling mode] (`THEN CONTINUE`, `THEN BREAK`, `THEN FAIL` (default)). `ON ERROR RETRY` increases query robustness by handling transient errors without manual intervention. -It is particularly suitable for xref:subqueries/subqueries-in-transactions.adoc#concurrent-transactions[concurrent transactions], reducing the likelihood of xref:subqueries/subqueries-in-transactions.adoc#deadlocks[deadlocks]. +It is particularly suitable for xref:subqueries/subqueries-in-transactions.adoc#concurrent-transactions[concurrent transactions], reducing the likelihood of a query failing due to xref:subqueries/subqueries-in-transactions.adoc#deadlocks[deadlocks]. + [NOTE] Queries using `ON ERROR RETRY` can currently only use the xref:planning-and-tuning/runtimes/concepts.adoc#runtimes-slotted-runtime[slotted runtime]. @@ -551,7 +552,9 @@ Queries using `ON ERROR RETRY` can currently only use the xref:planning-and-tuni The below example demonstrates a basic retry scenario. If a transient error occurs during the creation of a `User` node, the transaction will be retried for the default maximum retry duration (`30` seconds). -If the retry succeeds, the query continues. If the retry fails after the default duration, it behaves like xref:subqueries/subqueries-in-transactions#on-error-continue[`ON ERROR CONTINUE`] (the default fallback). +If the retry succeeds, the query continues. + +If the retry fails after the default duration, the query fails because it behaves like xref:subqueries/subqueries-in-transactions#on-error-fail[`ON ERROR FAIL`] (the default fallback). .`ON ERROR RETRY` [source, cypher] @@ -623,21 +626,19 @@ CALL (i) { The fallback behavior specifies what will happen if a transaction has not succeeded within the time limit. Specifically: -* `ON ERROR RETRY ... THEN CONTINUE` (default): the query will ignore recoverable errors and continue with the execution of subsequent inner transactions. -The outer transaction will succeed, and `null` will be returned for any failed inner transactions. +* `ON ERROR RETRY ... THEN CONTINUE`: the query will ignore recoverable errors and continue with the execution of subsequent inner transactions. +The outer transaction succeeds, and `null` will be returned for any failed inner transactions. See xref:subqueries/subqueries-in-transactions.adoc#on-error-continue[`ON ERROR CONTINUE`] for more information about this behavior. -[NOTE] -Because `THEN CONTINUE` is the default fallback option it does not have to be specified. - * `ON ERROR RETRY ... THEN BREAK`: the query will ignore recoverable errors and stop the execution of subsequent inner transactions. -The out transaction succeeds, and `null` will be returned for the failed inner transaction and all subsequent ones. +The outer transaction succeeds, and `null` will be returned for the failed inner transaction and all subsequent ones. See xref:subqueries/subqueries-in-transactions.adoc#on-error-break[`ON ERROR BREAK`] for more information about this behavior. -* `ON ERROR RETRY ... THEN FAIL`: the query will acknowledge a recoverable error and stop the execution of subsequent inner transactions, causing the outer transaction to fail. -all subsequent ones. +* `ON ERROR RETRY ... THEN FAIL` (default): the query will acknowledge a recoverable error and stop the execution of subsequent inner transactions, causing the outer transaction to fail. See xref:subqueries/subqueries-in-transactions.adoc#on-error-fail[`ON ERROR FAIL`] for more information about this behavior. +[NOTE] +Because `THEN FAIL` is the default fallback option it does not have to be specified. // tag::tabs[] [.tabs] @@ -845,6 +846,11 @@ If so, the impacted transactions are always rolled back, and an error is thrown The latter is particularly suited for concurrent transactions, because it retries recoverable transient errors with exponential backoff between retries until the xref:subqueries/subqueries-in-transactions.adoc#specify-retry-duration[maximum retry duration] has been reached. +[NOTE] +Deadlock detection and transaction retries can be time-consuming. +When importing data that includes a significant number of relationships to be merged between the same nodes but processed in different batches, increasing the concurrency may not enhance performance. +On the contrary, it could slow down the import process. + .Dealing with deadlocks ===== @@ -872,7 +878,7 @@ ForsetiClient[transactionId=64, clientId=12] can't acquire ExclusiveLock{owner=F Client[63] waits for [ForsetiClient[transactionId=64, clientId=12]]] ---- -.Query using `ON ERROR RETRY` to ignore deadlocks and complete outer transaction +.Query using `ON ERROR CONTINUE` to ignore deadlocks and complete outer transaction [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row @@ -928,10 +934,9 @@ RETURN status.transactionId AS transaction, status.committed AS commitStatus, st These are transient errors, meaning that re-running the transactions may be successful. To retry the any failed inner transactions, use the error option `ON ERROR RETRY`, which retries any failing transactions until the maximum retry duration has been reached. -The following query uses `ON ERROR RETRY` to retry the above query for a maximum of `3` seconds. -Note that `ON ERROR RETRY` by default falls back to the error option `THEN CONTINUE`, which ensures that any deadlocks are bypassed and that subsequent inner transactions are executed. +The following query uses `ON ERROR RETRY ... THEN CONTINUE` to retry the above query for a maximum of `3` seconds and then continue the execution of subsequent inner transactions by ignoring any recoverable errors. -.Retrying any failed inner transactions with `ON ERROR RETRY` +.Query using `ON ERROR RETRY ... THEN CONTINUE` to retry deadlocked inner transactions and complete outer transaction [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row @@ -939,7 +944,7 @@ CALL (row) { MERGE (m:Movie {movieId: row.movieId}) MERGE (y:Year {year: row.year}) MERGE (m)-[r:RELEASED_IN]->(y) -} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR RETRY FOR 3 SECONDS REPORT STATUS AS status +} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR RETRY THEN CONTINUE FOR 3 SECONDS REPORT STATUS AS status RETURN status.transactionID as transaction, status.committed AS successfulTransaction ----