Tablestore supports the local transaction feature. A local transaction can be created based on a partition key value. You can read and write data within the transaction. You can commit the transaction to make changes permanently effective, or abort the transaction to invalidate changes.

When you use the local transaction feature, you can first create a local transaction based on the specified partition key value. Then, the server of Tablestore returns a transaction ID. You can use the transaction ID to read and write data within the range specified by the partition key value and commit the transaction to apply all data changes in the transaction. You can also use the transaction ID to abort the transaction so that all changes in the transaction are not applied to raw data.

The local transaction feature allows you to perform atomic operations to read or write a single row or multiple rows.
Note
  • Currently, a transaction is limited within one partition key.
  • For more information about partition keys, see Primary keys and attributes.
  • The local transaction feature is currently available for invitational preview and is disabled by default. To use this feature, submit a ticket or join the Tablestore group in DingTalk.

Related operations

  • StartLocalTransaction: creates a local transaction.
  • CommitTransaction: commits a transaction.
  • AbortTransaction: aborts a transaction.
  • Local transactions are supported by write operations such as PutRow, UpdateRow, DeleteRow, and BatchWriteRow.
  • Local transactions are supported by read operations such as GetRow and GetRange.

Limits

  • The transaction ID cannot be used to access data beyond the range specified by the partition key value that is used to create the transaction.
  • A local transaction can be used by only one request at a time. When the transaction is in use, other operations that use the transaction ID fail.
  • The partition key values of all write requests in the same transaction must be the same as the partition key value used to create the transaction. This limit does not apply to read requests.
  • If a BatchWriteRow request includes a transaction ID, all rows in the request can only be written to the table that matches the transaction ID.
  • Up to 4 MB data can be written to each transaction. The data volume of each transaction is calculated in the same way as a write request.
  • The maximum interval for reading or writing a transaction is 60 seconds. A transaction that has not been read or written for more than 60 seconds times out.
  • The maximum time-to-live (TTL) of a transaction is 60 seconds. A transaction that has not been committed or aborted for more than 60 seconds times out.

For more information about how to calculate the volume of written data, see Billing items and billing description.

Additional considerations

  • When a transaction is running, the corresponding partition key value is locked. Write requests with the same partition key value but without the transaction ID are rejected. The partition key value is unlocked when the transaction is committed or aborted, or when the transaction times out.
  • If you do not specify a version for a cell, the server of Tablestore assigns a version to the cell in the usual way when the cell is written into the transaction (rather than when the transaction is committed).
  • If a transaction is unused for a long time, the server of Tablestore determines that the transaction times out and aborts it.
  • If an uncommitted transaction becomes invalid, retry the operation within this transaction.
  • A transaction creation request may have already been executed by the server of Tablestore even if a timeout error is returned. In this case, you need to resend a transaction creation request.
  • A transaction remains valid even if a read or write request with the transaction ID is rejected. You can resend the request in the same way as a common request or abort the transaction.

Scenarios

  • Read and write (simple scenario)

    You can use either of the following methods to perform read, modify, and write operations:

    These methods have the following limits:

    • Conditional update can only be used to process one request at a time that involves a single row. It cannot be used to process requests that involve data across rows or requests for writing data multiple times.
    • An atomic counter can only be used to process one request at a time that involves a single row, and only supports the column value summation.

    You can create a local transaction to read, modify, and write back data within the range specified by a partition key value.

    • Use StartLocalTransaction to create a transaction based on the partition key value.
    • Use GetRow or GetRange to read data. The transaction ID must be included in the request.
    • Modify data on the client.
    • Use PutRow, UpdateRow, DeleteRow, or BatchWriteRow to write back the modified data. The transaction ID must be included in the request.
    • Use CommitTransaction to commit the transaction.
  • Email (complex scenario)

    You can create a local transaction to perform atomic operations on emails of the same user.

    To properly use the local transaction feature, include a primary table and two index tables in a physical table. The primary key columns are as follows.

    Table UserID Type IndexField MailID
    Primary table User ID "Main" "N/A" Email ID
    Folder table User ID "Folder" $Folder Email ID
    SendTime table User ID "SendTime" $SendTime Email ID

    The primary table is differentiated from the index tables through the Type column. Index rows use the IndexField column to store fields of different meanings. This column is not included in the primary table.

    You can create a local transaction to complete the following operations:

    • List the latest 100 emails that a user has sent by following these steps:
      • Use the user ID to create a local transaction and obtain the transaction ID.
      • Use the transaction ID to call GetRange in the SendTime table and retrieve 100 emails.
      • Use the transaction ID to call BatchGetRow in the primary table and retrieve the details of the 100 emails.
      • Commit or abort the transaction. The commit and abort operations have the same effect because no writes are performed in this transaction.
    • Transfer all emails in a directory to another directory by following these steps:
      • Use the user ID to create a local transaction and obtain the transaction ID.
      • Use the transaction ID to call GetRange in the Folder table and retrieve several emails.
      • Use the transaction ID to call BatchWriteRow in the Folder table. The write operation is performed on two rows each time when an email is transferred. Specifically, a row is deleted from the old Folder table and a row is added to the new Folder table.
      • Commit the transaction.
    • Count the number of read emails and the number of unread emails in a directory by following these steps (not the optimal solution):
      • Use the user ID to create a local transaction and obtain the transaction ID.
      • Use the transaction ID to call GetRange in the Folder table and retrieve several emails.
      • Use the transaction ID to call BatchGetRow in the primary table and obtain the read status of each email.
      • Commit or abort the transaction.

    In this scenario, you can add more index tables to accelerate some common operations. The local transaction feature solves the status inconsistency issue between the primary table and index tables and simplifies development. For example, when you count the number of emails, many emails are read, resulting in a large overhead. To reduce overhead and accelerate query, use a new index table to store the number of read emails and the number of unread emails.

Billing items

  • Each of the StartLocalTransaction, CommitTransaction, and AbortTransaction requests consume 1 write capacity unit.
  • Other read and write requests are billed in the same way as common read and write requests.

For more information about billing, see Billing items and description.

Sample code

  • Create a local transaction.

    You can call startLocalTransaction of AsyncClient or SyncClient to create a local transaction and obtain the transaction ID. The parameter is a value of the partition key.

    PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
    primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("txnKey"));
    PrimaryKey primaryKey = primaryKeyBuilder.build();
    StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
    String txnId = client.startLocalTransaction(request).getTransactionID();
  • Read and write data in a transaction.
    You can read and write data in a transaction in a way similar to reading and writing data normally. You only need to enter the transaction ID.
    • Write data to a row.
      PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
      primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("txnKey"));
      primaryKeyBuilder.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong("userId"));
      PrimaryKey primaryKey = primaryKeyBuilder.build();
      RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey);
      rowPutChange.addColumn(new Column("Col", ColumnValue.fromLong(columnValue)));
      
      PutRowRequest request = new PutRowRequest(rowPutChange);
      request.setTransactionId(txnId);
      client.putRow(request);
    • Read data from the specified row.
      PrimaryKeyBuilder primaryKeyBuilder;
      primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
      primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("txnKey"));
      primaryKeyBuilder.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong("userId"));
      PrimaryKey primaryKey = primaryKeyBuilder.build();
      SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(tableName, primaryKey);
      criteria.setMaxVersions(1); // Set the latest version to be read.
      
      GetRowRequest  request = new GetRowRequest(criteria);
      request.setTransactionId(txnId);
      GetRowResponse getRowResponse = client.getRow(request);
  • Commit a transaction.
    CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
    client.commitTransaction(commitRequest);
  • Abort a transaction.
    AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
    client.abortTransaction(abortRequest);

Error codes

  • OTSRowOperationConflict: the specified partition key value is already used by an existing local transaction.
  • OTSSessionNotExist: the transaction with the specified transaction ID does not exist, the specified transaction is invalid, or the specified transaction has timed out.
  • OTSSessionBusy: the last request of the transaction has not been completed.
  • OTSOutOfTransactionDataSizeLimit: the volume of data in a transaction exceeds the maximum volume.
  • OTSDataOutOfRange: data is operated beyond the range specified by the partition key value used to create the transaction.