javaneo4jgraph-databases

Neo4j driver with transaction timeout: how to detect the timeout event?


I'm looking at recent versions of the Neo4j driver, which have a nice feature about transaction timeouts, ie, you can make a query to stop if it cannot return a result within a time constraint.

I don't have clear how the occurrence of such a time out event can be detected and recovered (in my case, I have a number of queries to collect potentially relevant data and I can afford to omit results from too slow queries). Is there a specific exception that is thrown? Or is there some other mechanism?


Solution

  • There are a few ways to set a timeout. Here's one way: by specifying a timeout when you call session.beginTransaction().

    Start by creating a TransactionConfig with a timeout value. The example below creates a config with a timeout of 5 seconds:

    TransactionConfig config = TransactionConfig.builder()
            .withTimeout(Duration.ofSeconds(5))
            .build();
    

    Then create a new transaction, and use the config you just made:

    try (Transaction transaction = neo4jDriver.session().beginTransaction(config)) {
        // do stuff
        transaction.commit();
    }
    

    If the timeout is exceeded, the transaction will abort and a org.neo4j.driver.exceptions.ClientException will be thrown.

    Putting it all together, it could look something like this:

    TransactionConfig config = TransactionConfig.builder()
            .withTimeout(Duration.ofSeconds(5))
            .build();
    
    try (Transaction transaction = neo4jDriver.session().beginTransaction(config)) {
        // do stuff
        transaction.commit();
    
    } catch (ClientException e) {
        // handle this
    }
    

    To determine specifically that the cause was from a timeout, you can:

    1. check code() on the ClientException, or
    2. inspect the text within getMessage() on a caught exception

    There are various ways to trigger a timeout – I was able to do this in the code example below by:

    Here's the example showing a comparison with code() on line 15 which compares against "Neo.ClientError.Transaction.TransactionTimedOutClientConfiguration":

     1  // set up driver, then create a session
     2  Driver neo4jDriver = GraphDatabase.driver(...);
     3  Session session = neo4jDriver.session();
     4  
     5  // set up config with timeout of 1 second
     6  TransactionConfig config = TransactionConfig.builder()
     7          .withTimeout(Duration.ofSeconds(1))
     8          .build();
     9  
    10  // call something slow on line 12, or set a breakpoint
    11  try (Transaction transaction = session.beginTransaction(config)) {
    12      callSlowThing(transaction);
    13  } catch (ClientException e) {
    14      // check "e.code()" for specifics
    15      if ("Neo.ClientError.Transaction.TransactionTimedOutClientConfiguration".equals(e.code())) {
    16          System.out.println("transaction timed out");
    17      }
    18  }
    

    It would be best if we didn't have to compare code() against a string literal, but I don't see that string literal anywhere in neo4j-java-driver--5.19.0.jar source, nor was I able to find any other obvious variations – it's probably generated on the server side, and thus not directly accessible in client code. While this technique does make it possible to isolate "TransactionTimedOutClientConfiguration", the usefulness might silently vanish if the server-generated message changes (server upgrade) thus breaking any timeout handling you've added.

    The other technique would be to check getMessage() for specific content. In local tests pointed at Neo4j Community 5.18.1, the entire message looks like below (check your own runtime environment to see what shows up for you).

    The transaction has been terminated. Retry your operation in a new transaction, and you should see a successful result. The transaction has not completed within the timeout specified at its start by the client. You may want to retry with a longer timeout.

    So perhaps you could combine these together by:

    1. checking the result of code(), and
    2. checking if getMessage() contains things like "transaction", "terminated", and "timeout"

    Note: the technique of using TransactionConfig works with Driver, but it is not possible (nor is there any equivalent I've found) to set a timeout when using OGM.