diff --git a/antora.yml b/antora.yml index 52a8c041c..3c7aa133b 100644 --- a/antora.yml +++ b/antora.yml @@ -6,4 +6,4 @@ nav: - modules/ROOT/content-nav.adoc asciidoc: attributes: - neo4j-version: '2025.02' + neo4j-version: '2025.03' diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index a8e074cd8..a55a814c5 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -16,6 +16,34 @@ New features are added to the language continuously, and occasionally, some feat This section lists all of the features that have been removed, deprecated, added, or extended in different Cypher versions. Replacement syntax for deprecated and removed features are also indicated. +[[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 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`]. + +|=== + [[cypher-deprecations-additions-removals-2025.01]] == Neo4j 2025.01 diff --git a/modules/ROOT/pages/subqueries/call-subquery.adoc b/modules/ROOT/pages/subqueries/call-subquery.adoc index 30dbbb03a..5f862397c 100644 --- a/modules/ROOT/pages/subqueries/call-subquery.adoc +++ b/modules/ROOT/pages/subqueries/call-subquery.adoc @@ -50,6 +50,7 @@ The variables returned in a subquery are available to the outer scope of the enc In this example, the `CALL` subquery executes three times, one for each row that the xref:clauses/unwind.adoc[`UNWIND`] clause outputs. .Query +// tag::subqueries_call_subquery_basic_example[] [source, cypher] ---- UNWIND [0, 1, 2] AS x @@ -58,6 +59,8 @@ CALL () { } RETURN innerReturn ---- +// end::subqueries_call_subquery_basic_example[] + .Result [role="queryresult",options="header,footer",cols="m"] @@ -117,6 +120,7 @@ As a result, `CALL` subqueries can help maintain optimal performance and scalabi In this example, a `CALL` subquery is used to xref:functions/aggregating.adoc#functions-collect[`collect`] a `LIST` containing all players who play for a particular team. .Collect a list of all players playing for a particular team +// tag::subqueries_call_subquery_variable_scope[] [source, cypher] ---- MATCH (t:Team) @@ -126,6 +130,8 @@ CALL (t) { } RETURN t AS team, players ---- +// end::subqueries_call_subquery_variable_scope[] + .Result [source, role="queryresult",options="header,footer",cols="m,2m"] @@ -457,6 +463,7 @@ RETURN p.name AS playerName, team.name AS team Note that no results are returned for `Player C`, since they are not connected to any `Team` with a `PLAYS_FOR` relationship. .Query using regular `OPTIONAL CALL` +// tag::subqueries_call_subquery_optional_call[] [source, cypher] ---- MATCH (p:Player) @@ -466,10 +473,10 @@ OPTIONAL CALL (p) { } RETURN p.name AS playerName, team.name AS team ---- +// end::subqueries_call_subquery_optional_call[] Now all `Player` nodes, regardless of whether they have any `PLAYS_FOR` relationships connected to a `Team`, are returned. -.Result .Result [role="queryresult",options="header,footer",cols="2*m"] |=== @@ -572,6 +579,7 @@ Call subqueries can be used to further process the results of a xref:clauses/uni This example query finds the youngest and the oldest `Player` in the graph. .Find the oldest and youngest players +// tag::subqueries_call_subquery_union[] [source, cypher] ---- CALL () { @@ -587,6 +595,7 @@ UNION } RETURN p.name AS playerName, p.age AS age ---- +// end::subqueries_call_subquery_union[] .Result [role="queryresult",options="header,footer",cols="2* 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. +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`: 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. + +* `ON ERROR RETRY ... THEN BREAK`: the query will ignore recoverable errors and stop the execution of subsequent inner transactions. +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` (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] + +.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 @@ -517,7 +706,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) { @@ -527,7 +716,7 @@ CALL (i) { OF 1 ROW ON ERROR CONTINUE REPORT STATUS AS s -RETURN n.num, s; +RETURN n.num, s ---- .Result @@ -544,7 +733,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) { @@ -554,7 +743,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 @@ -581,7 +770,7 @@ CALL (i) { OF 1 ROW ON ERROR FAIL REPORT STATUS AS s -RETURN n.num, s.errorMessage; +RETURN n.num, s.errorMessage ---- .Error @@ -610,6 +799,8 @@ If a negative number is specified (which can only be done through a parameter), `CALL { ... } IN CONCURRENT TRANSACTIONS` is particularly suitable for importing data without dependencies. This example creates `Person` nodes from a unique `tmdbId` value assigned to each person row in the CSV file (444 in total) in 3 concurrent transactions. +.`CALL` subquery run in `CONCURRENT TRANSACTIONS` +// tag::subqueries_in_transactions_concurrent_transactions[] [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/persons.csv' AS row @@ -619,6 +810,7 @@ CALL (row) { } IN 3 CONCURRENT TRANSACTIONS OF 10 ROWS RETURN count(*) AS personNodes ---- +// end::subqueries_in_transactions_concurrent_transactions[] .Result [role="queryresult",options="header,footer",cols="m"] @@ -656,13 +848,24 @@ Use the xref:subqueries/subqueries-in-transactions.adoc#status-report[status rep When a write transaction occurs, Neo4j takes locks to preserve data consistency while updating. For example, when creating or deleting a relationship, a write lock is taken on both the specific relationship and its connected nodes. -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 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/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. + +[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 -==== +===== 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. @@ -688,9 +891,6 @@ 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 [source, cypher] ---- @@ -708,66 +908,189 @@ 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 ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| 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 ... 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. + +.Query using `ON ERROR RETRY ... THEN CONTINUE` to retry deadlocked inner transactions and complete outer transaction +// tag::subqueries_in_transactions_deadlock_example[] +[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 RETRY FOR 3 SECONDS THEN CONTINUE REPORT STATUS AS status +RETURN status.transactionID as transaction, status.committed AS successfulTransaction +---- +// end::subqueries_in_transactions_deadlock_example[] + +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 | ++-------------------------------------------------+ +---- + + +.Click to see an example of failed transactions being retried without using `ON ERROR RETRY` [%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: +==== .Query retrying failed transactions [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) + 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 WITH * WHERE status.committed = false CALL (row) { - MERGE (m:Movie {movieId: row.movieId}) - MERGE (y:Year {year: row.year}) - MERGE (m)-[r:RELEASED_IN]->(y) + 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 ---- -===== ==== +===== [[restrictions]] == Restrictions diff --git a/package-lock.json b/package-lock.json index 32a6cbab3..c848be04a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@antora/cli": "^3.1.10", "@antora/site-generator-default": "^3.1.10", "@neo4j-antora/antora-add-notes": "^0.3.2", - "@neo4j-antora/antora-modify-sitemaps": "^0.7.0", + "@neo4j-antora/antora-modify-sitemaps": "^0.7.1", "@neo4j-antora/antora-page-roles": "^0.3.1", "@neo4j-antora/antora-table-footnotes": "^0.3.3", "@neo4j-antora/mark-terms": "1.1.0", @@ -329,9 +329,9 @@ "license": "MIT" }, "node_modules/@neo4j-antora/antora-modify-sitemaps": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@neo4j-antora/antora-modify-sitemaps/-/antora-modify-sitemaps-0.7.0.tgz", - "integrity": "sha512-mx6KhD9rdxq/gMM8NxKOEkbaT8qG8U6VgEx2TPZ0yz4Udo3kYypiJ+hvQtKfcRz1WcU9hHzISV+gV3xvTab39A==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@neo4j-antora/antora-modify-sitemaps/-/antora-modify-sitemaps-0.7.1.tgz", + "integrity": "sha512-1XA4mfThu31PFEYvP8emBUruf7AT3C3/kjRv1yiB1NXcHESLM2mhpT64YwPNq6SRSGdWEGwQceSsM+CuHLZCwA==", "license": "MIT", "dependencies": { "semver": "^7.6.3" @@ -2780,9 +2780,9 @@ "integrity": "sha512-Jsv17dEBELSkqplEIZE9b5I2zjYPvoHi4momLRt1FfBRQnBTWbk4kkf2JQojRJ8mQEVscj2tApfTDOAUtAOSLA==" }, "@neo4j-antora/antora-modify-sitemaps": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@neo4j-antora/antora-modify-sitemaps/-/antora-modify-sitemaps-0.7.0.tgz", - "integrity": "sha512-mx6KhD9rdxq/gMM8NxKOEkbaT8qG8U6VgEx2TPZ0yz4Udo3kYypiJ+hvQtKfcRz1WcU9hHzISV+gV3xvTab39A==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@neo4j-antora/antora-modify-sitemaps/-/antora-modify-sitemaps-0.7.1.tgz", + "integrity": "sha512-1XA4mfThu31PFEYvP8emBUruf7AT3C3/kjRv1yiB1NXcHESLM2mhpT64YwPNq6SRSGdWEGwQceSsM+CuHLZCwA==", "requires": { "semver": "^7.6.3" } diff --git a/package.json b/package.json index b86fe81ff..3fb8a3361 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@antora/cli": "^3.1.10", "@antora/site-generator-default": "^3.1.10", "@neo4j-antora/antora-add-notes": "^0.3.2", - "@neo4j-antora/antora-modify-sitemaps": "^0.7.0", + "@neo4j-antora/antora-modify-sitemaps": "^0.7.1", "@neo4j-antora/antora-page-roles": "^0.3.1", "@neo4j-antora/antora-table-footnotes": "^0.3.3", "@neo4j-antora/mark-terms": "1.1.0",