google-cloud-platformgoogle-cloud-spanner

AbortedException in Empty Commits of Google Cloud Spanner


I'm currently developing a Java program using Google Cloud Spanner, utilizing batch inserts within manual transactions via a multithreaded approach. Each thread in the application is responsible for generating and accumulating its own mutation array. As part of the process, we add an insert mutation to the local array and perform empty commits. However, I've encountered an issue where AbortedException arises during these empty commits, and not specifically when the batch size is met for a commit to the database. The transactions are handled using TransactionManager and TransactionContext. I am looking for insights into why this exception occurs specifically at the point of empty commits. Thank you so much!

This is a link to the repo of a simplified version of the application.

Basically, the application utilizes a batch processing approach in the SpannerClient class for handling database insert operations. In this process, each thread creates mutations for insert operations, which are stored in a bufferedMutations ArrayList<Mutation>. These mutations are collected until they reach a set batch size. Upon reaching this limit, the batch is sent to the Google Cloud Spanner database in a single transaction.

Some basic errors occur are (more detail printed out when applciation is run locally) two types:

---------------------------------------------------------------------------------------------------------------
Thread-138 in SpannerClient.commit() method- AbortedException occurred.
Current size of mutation buffers array: 94.
Stack Trace:com.google.cloud.spanner.AbortedException: ABORTED: io.grpc.StatusRuntimeException: ABORTED: Database schema has changed



---------------------------------------------------------------------------------------------------------------
Thread-0 in SpannerClient.commit() method- AbortedException occurred.
Current size of mutation buffers array: 77.
Stack Trace:com.google.cloud.spanner.AbortedException: ABORTED: io.grpc.StatusRuntimeException: ABORTED: Transaction was aborted.
retry_delay {
  nanos: 38655229
}

Solution

  • Cloud Spanner can abort a read/write transaction for multiple reasons. In the examples given above, one of the aborts was for example caused by a schema change that went into effect during your read/write transaction. But read/write transactions can also be aborted for other reasons, and there is no way to guarantee up front that a given read/write transaction will not be aborted.

    It is important to realize that part of the API contract for read/write transactions in Cloud Spanner is that they can always be aborted. You therefore always need to put your read/write transactions in a retry loop. This is also shown in the example for how to use TransactionManager:

    long singerId = my_singer_id;
    try (TransactionManager manager = dbClient.transactionManager()) {
      TransactionContext transaction = manager.begin();
      while (true) {
        String column = "FirstName";
        Struct row = transaction.readRow("Singers", Key.of(singerId), Collections.singleton(column));
        String name = row.getString(column);
        transaction.buffer(Mutation.newUpdateBuilder("Singers").set(column).to(name.toUpperCase()).build());
        try {
          manager.commit();
          break;
        } catch (AbortedException e) {
          Thread.sleep(e.getRetryDelayInMillis());
          transaction = manager.resetForRetry();
        }
      }
    }
    

    (Source: https://github.com/googleapis/java-spanner/blob/f689f742d8754134523ed0394b9c1b8256adcae2/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClient.java#L429)