neo4j / neo4j-dotnet-driver

Neo4j Bolt driver for .NET
Apache License 2.0
226 stars 69 forks source link

How to resolve deadlock caused by ExclusiveLock? Can give me some advice? #750

Closed 6ther closed 9 months ago

6ther commented 10 months ago

Neo4j.Driver.TransientException: ForsetiClient[transactionId=169062, clientId=2734] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=169064, clientId=2731]} on NODE(359109), because holders of that lock are waiting for ForsetiClient[transactionId=169062, clientId=2734].

thelonelyvulpes commented 10 months ago

Hi @6ther, I don't know how this didn't get picked up by my issues notifications, so apologies for the delayed response. here is an article about this exception for a bit of explanation: https://neo4j.com/developer/kb/explanation-of-error-deadlockdetectedexception-forseticlient-0-cant-acquire-exclusivelock/

This is a transient exception you can retry the transaction. Which method on the driver are you using to invoke the query/transaction?

6ther commented 9 months ago

Hi @6ther, I don't know how this didn't get picked up by my issues notifications, so apologies for the delayed response. here is an article about this exception for a bit of explanation: https://neo4j.com/developer/kb/explanation-of-error-deadlockdetectedexception-forseticlient-0-cant-acquire-exclusivelock/

This is a transient exception you can retry the transaction. Which method on the driver are you using to invoke the query/transaction?

like this: var session = _driver.Session(); using var tran = session.BeginTransaction(); tran.Run(query, parameters) tran.Commit();

6ther commented 9 months ago

Hi @6ther, I don't know how this didn't get picked up by my issues notifications, so apologies for the delayed response. here is an article about this exception for a bit of explanation: https://neo4j.com/developer/kb/explanation-of-error-deadlockdetectedexception-forseticlient-0-cant-acquire-exclusivelock/

This is a transient exception you can retry the transaction. Which method on the driver are you using to invoke the query/transaction?

like this: var session = _driver.Session(); using var tran = session.BeginTransaction(); tran.Run(query, parameters) tran.Commit();

6ther commented 9 months ago

first of all, thanks for your reply and an article about this exception.

thelonelyvulpes commented 9 months ago

@6ther You have a few options here:

  1. Use your own retry mechanism with your explicit transaction by catching TransientException, rollback that transaction and try it again.

    var retries = 0;
    while (true)
    {
    var tx = await session.BeginTransactionAsync();
    try
    {
        var cursor = await tx.RunAsync("UNWIND RANGE(1, 100) AS x RETURN x");
        var results = await cursor.ToListAsync();
        await tx.CommitAsync();
    }
    catch (TransientException)
    {
        await tx.RollbackAsync();
        retries++;
        if (retries > 5) throw;
        await Task.Delay(500);
    }
    }
  2. Use the "transaction functions" ExecuteReadAsync/ExecuteWriteAsync.

    var results = await session.ExecuteReadAsync(async tx =>
    {     
        var cursor = await tx.RunAsync("UNWIND RANGE(1, 100) AS x RETURN x");
        return cursor.ToListAsync();
    }
    );
  3. the driver [ExecutableQuery](), it works nicely for single query transactions.

    var results = await driver.ExecutableQuery("UNWIND RANGE(1, 100) AS x RETURN x")
    .WithConfig(new QueryConfig(RoutingControl.Readers, "neo4j"))
    .ExecuteAsync();
6ther commented 9 months ago

@6ther You have a few options here:

  1. Use your own retry mechanism with your explicit transaction by catching TransientException, rollback that transaction and try it again.
var retries = 0;
while (true)
{
    var tx = await session.BeginTransactionAsync();
    try
    {
        var cursor = await tx.RunAsync("UNWIND RANGE(1, 100) AS x RETURN x");
        var results = await cursor.ToListAsync();
        await tx.CommitAsync();
    }
    catch (TransientException)
    {
        await tx.RollbackAsync();
        retries++;
        if (retries > 5) throw;
        await Task.Delay(500);
    }
}
  1. Use the "transaction functions" ExecuteReadAsync/ExecuteWriteAsync.
var results = await session.ExecuteReadAsync(async tx =>
    {     
        var cursor = await tx.RunAsync("UNWIND RANGE(1, 100) AS x RETURN x");
        return cursor.ToListAsync();
    }
);
  1. the driver ExecutableQuery, it works nicely for single query transactions.
var results = await driver.ExecutableQuery("UNWIND RANGE(1, 100) AS x RETURN x")
    .WithConfig(new QueryConfig(RoutingControl.Readers, "neo4j"))
    .ExecuteAsync();

Thanks for your advice, and i still have some questions. When I set configuration like this: IDriver _driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password), (b) => { b.WithDefaultReadBufferSize(250000) .WithDefaultWriteBufferSize(125000) .WithMaxReadBufferSize(1250000) .WithMaxWriteBufferSize(1250000) **.WithMaxTransactionRetryTime(TimeSpan.FromSeconds(30));** }); _driver.Session().BeginTransaction(t => **t.WithTimeout(TimeSpan.FromSeconds(30))**); Can the transaction retry?

thelonelyvulpes commented 9 months ago

Can the tranaction retry?

_driver.Session().BeginTransaction(t => **t.WithTimeout(TimeSpan.FromSeconds(30)));

the driver can not do any retry mechanisms automatically for you when you use this method.

With regards to:

.WithMaxTransactionRetryTime(TimeSpan.FromSeconds(30))

This config only has an effect on ISession.ExecuteRead, ISession.ExecuteWrite, and IDriver.ExecutableQuery.

If you would like to make use of the ISession interface and have retries on your transactions you must use ExecuteRead and ExecuteWrite. Here is an example:

using var driver = GraphDatabase.Driver(
    uri,
    AuthTokens.Basic(user, password),
    cfg => cfg.WithDefaultReadBufferSize(250000)
        .WithDefaultWriteBufferSize(125000)
        .WithMaxReadBufferSize(1250000)
        .WithMaxWriteBufferSize(1250000)
        .WithMaxTransactionRetryTime(TimeSpan.FromSeconds(30)));

using var session = driver.Session(x => x.WithDatabase("neo4j"));
var results = session.ExecuteRead(
    tx => tx.Run(query, parameters).ToList(),
    cfg => cfg.WithTimeout(TimeSpan.FromSeconds(30)));
6ther commented 9 months ago

Can the tranaction retry?

_driver.Session().BeginTransaction(t => **t.WithTimeout(TimeSpan.FromSeconds(30)));

the driver can not do any retry mechanisms automatically for you when you use this method.

With regards to:

.WithMaxTransactionRetryTime(TimeSpan.FromSeconds(30))

This config only has an effect on ISession.ExecuteRead, ISession.ExecuteWrite, and IDriver.ExecutableQuery.

If you would like to make use of the ISession interface and have retries on your transactions you must use ExecuteRead and ExecuteWrite. Here is an example:

using var driver = GraphDatabase.Driver(
    uri,
    AuthTokens.Basic(user, password),
    cfg => cfg.WithDefaultReadBufferSize(250000)
        .WithDefaultWriteBufferSize(125000)
        .WithMaxReadBufferSize(1250000)
        .WithMaxWriteBufferSize(1250000)
        .WithMaxTransactionRetryTime(TimeSpan.FromSeconds(30)));

using var session = driver.Session(x => x.WithDatabase("neo4j"));
var results = session.ExecuteRead(
    tx => tx.Run(query, parameters).ToList(),
    cfg => cfg.WithTimeout(TimeSpan.FromSeconds(30)));

I understand. Thanks for your answer and thanks a lot.

thelonelyvulpes commented 9 months ago

@6ther you are welcome, can I close this issue?

6ther commented 9 months ago

@6ther you are welcome, can I close this issue?

Yes.