DynamoDB: No one (or no one) wanted to be a DBA, and SRE didn’t want to be a DBA. With cloud, DBAs are turning their backs on big data, not RDS. Other companies have “you write it, you deploy it, and you maintain it”, where a team is responsible for their own products, from UI to DB, and everything is full-stack. DynamoDB belongs to Developer and can be handled completely without the help of DBA or SRE. Transaction is not a deal breaker for DynamoDB anymore. Transaction is not a deal breaker for DynamoDB anymore, Transaction is not a deal breaker for DynamoDB anymore.

DynamoDB Transaction guarantees ACID. ACID means atomicity, consistency, isolation, and durability. We’re talking about atomicity, operating on multiple tables or rows at the same time, and the result is all or nothing, all success, or nothing at all.

Two Transactional API

  • TransactWriteItems

    • A transaction operates on a maximum of 25 unused items, each of which is less than 400 KB, and the total transaction size is less than 4 MB.
    • The same transaction can operate on the same item only once.
    • Cost double WCUs.
    • Client tokens can be used to keep idempotent (10 mins).
  • TransactReadItems

    • A transaction can operate a maximum of 25 unused GETS, and the total transaction size cannot exceed 4 MB.
    • The result of a synchronous read is returned.
    • Consume double RCUs.
    • Locks are not used for reading or writing, and a collision will cancel the transaction and require handling retry yourself. Note that the same capacity units are consumed even if the transaction is canceled.
  • Other:

    • Transaction reads and writes do not need to be locked. If a transaction conflict occurs, the transaction will be cancelled and retry will need to be handled. Note that the same capacity units are consumed even if the transaction is canceled.
    • Transaction must be in the same AWS Region
    • Transaction must be in the same AWS account
    • Transaction must be in DynamoDB

The instance test

Source: github.com/shubozhang/…

Create three tables

1.Table Account: unique accountId

@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName="Account")
@ToString
@Data
public class Account {
    private String accountId;
    
    @DynamoDBHashKey(attributeName = "AccountId")
    public String getAccountId(a) { return accountId; }
    
    public void setAccountId(String accountId) { this.accountId = accountId; }}Copy the code
  1. Table Room: Unique roomId, attribute Available only YES or NO
@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName="Room")
@ToString
@Data
public class Room {
    private String roomId;
    private String available;

    public Room(String roomId) { this.roomId = roomId; }
    
    @DynamoDBHashKey(attributeName = "RoomId")
    public String getRoomId(a) { return roomId; }
    public void setRoomId(String roomId) { this.roomId = roomId; }
    
    @DynamoDBAttribute(attributeName = "Available")
    public String getAvailable(a) { return available; }
    public void setAvailable(String available) { this.available = available; }}Copy the code
  1. Table Order: unique orderId, accountId must exist, roomId must exist, and corresponding room available must be before YES, after NO
@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName="Order")
@ToString
@Data
public class Order {
    private String orderId;
    private String accountId;
    private String roomId;

    public Order(String orderId) { this.orderId = orderId; }

    @DynamoDBHashKey(attributeName = "OrderId")
    public String getOrderId(a) { return orderId; }
    public void setOrderId(String orderId) { this.orderId = orderId; }

    @DynamoDBAttribute(attributeName = "AccountId")
    public String getAccountId(a) { return accountId; }
    public void setAccountId(String accountId) { this.accountId = accountId; }@DynamoDBAttribute(attributeName = "RoomId")
    public String getRoomId(a) { return roomId; }
    public void setRoomId(String roomId) { this.roomId = roomId; }}Copy the code
Write the Transaction
  1. Create an order
Private String createOrder(Order Order) {// 1. Check the accountId DynamoDBTransactionWriteExpression checkAccount = new DynamoDBTransactionWriteExpression () .withConditionExpression("attribute_exists(AccountId)"); / / 2. Check whether Room is available. The status must be YES before the operation. Map<String, AttributeValue> roomUpdateValues = new HashMap<>(); roomUpdateValues.put(":pre_status", new AttributeValue("YES")); DynamoDBTransactionWriteExpression checkRoom = new DynamoDBTransactionWriteExpression() .withExpressionAttributeValues(roomUpdateValues) .withConditionExpression("Available = :pre_status"); / / 3. Check for repeat order DynamoDBTransactionWriteExpression checkOrder = new DynamoDBTransactionWriteExpression () .withConditionExpression("attribute_not_exists(OrderId)"); TransactionWriteRequest transactionWriteRequest = new TransactionWriteRequest(); transactionWriteRequest.addConditionCheck(new Account(order.getAccountId()), checkAccount); / / update the Room state, after the scheduled into NO transactionWriteRequest. AddUpdate (new Room (order. GetRoomId (), "NO"), the checkRoom); transactionWriteRequest.addPut(order, checkOrder); / / 4. Write the Transaction return executeTransactionWrite (transactionWriteRequest); }Copy the code
  1. Execute Transaction Write
private static String executeTransactionWrite(TransactionWriteRequest transactionWriteRequest) { try { mapper.transactionWrite(transactionWriteRequest); } catch (DynamoDBMappingException ddbme) { return ("Client side error in Mapper, fix before retrying. Error: " + ddbme.getMessage()); } catch (ResourceNotFoundException rnfe) { return ("One of the tables was not found, verify table exists before retrying. Error: " + rnfe.getMessage()); } catch (InternalServerErrorException ise) { return ("Internal Server Error, generally safe to retry with back-off. Error: " + ise.getMessage()); } the catch (TransactionCanceledException tce) {/ / key test this exception, Return ("Transaction Canceled, implies a client issue, fix before retrying. Error: " + tce.getMessage()); } catch (Exception ex) { return ("An exception occurred, investigate and configure retry strategy. Error: " + ex.getMessage()); } return "SUCCESS"; }Copy the code
Read the Transaction
  1. Read an order: Read the order and related room and account information
private List<Object> readOrder(Order order) { TransactionLoadRequest transactionLoadRequest = new TransactionLoadRequest(); / / return object order: the order, the room, and the account transactionLoadRequest. AddLoad (order); transactionLoadRequest.addLoad(new Room(order.getRoomId())); transactionLoadRequest.addLoad(new Account(order.getAccountId())); return executeTransactionLoad(transactionLoadRequest); }Copy the code
  1. Execute Transaction Load
    private static List<Object> executeTransactionLoad(TransactionLoadRequest transactionLoadRequest) {
        List<Object> loadedObjects = new ArrayList<Object>();
        try {
            loadedObjects = mapper.transactionLoad(transactionLoadRequest);
        } catch (DynamoDBMappingException ddbme) {
            System.err.println("Client side error in Mapper, fix before retrying. Error: " + ddbme.getMessage());
        } catch (ResourceNotFoundException rnfe) {
            System.err.println("One of the tables was not found, verify table exists before retrying. Error: " + rnfe.getMessage());
        } catch (InternalServerErrorException ise) {
            System.err.println("Internal Server Error, generally safe to retry with back-off. Error: " + ise.getMessage());
        } catch (TransactionCanceledException tce) {
            System.err.println("Transaction Canceled, implies a client issue, fix before retrying. Error: " + tce.getMessage());
        } catch (Exception ex) {
            System.err.println("An exception occurred, investigate and configure retry strategy. Error: " + ex.getMessage());
        }
        return loadedObjects;
    }
Copy the code
Prepare test data
// Insert 5 accounts, A1... A5 private static void insertAccounts(Integer count) { AccountDao accountDao = new AccountDao(); for (int i = 0; i < count; i++) { accountDao.saveItem(new Account("A"+i)); }} // Insert 5 room, R1... R5, initial state YES private static void insertRooms(Integer count) {RoomDao RoomDao = new RoomDao(); for (int i = 0; i < count; i++) { roomDao.saveItem(new Room("R"+i, "YES")); }}Copy the code
Test 1: Complete the order
@test public void testCreateOrder() {// Create order order order = new order (); order.setOrderId("1"); order.setAccountId("A1"); order.setRoomId("R1"); // createOrder createOrder(order); // readOrder List<Object> objects = readOrder(order); Order resOrder = (Order) objects.get(0); Room resRoom = (Room) objects.get(1); Account resAccount = (Account) objects.get(2); AssertThat (resOrder).isequalto (order); assertThat(resAccount).isEqualTo(new Account(order.getAccountId())); assertTrue(resRoom.getRoomId().equals(order.getRoomId()) && resRoom.getAvailable().equals("NO")); // clean up deleteOrder(order); }Copy the code
Test 2: Invalid Account
@Test public void testInvalidAccountException() { Order order = new Order(); order.setOrderId("2"); order.setAccountId("A"); order.setRoomId("R3"); String res = createOrder(order); assertTrue(res.contains("Transaction Canceled, implies a client issue, fix before retrying. Error: ")); ConditionCheck accountId assertTrue(res.contains("[ConditionalCheckFailed, None, None]")); }Copy the code
Test 3: Invalid room
@Test public void testInvalidRoomException() { Order order = new Order(); order.setOrderId("2"); order.setAccountId("A2"); order.setRoomId("R"); String res = createOrder(order); assertTrue(res.contains("Transaction Canceled, implies a client issue, fix before retrying. Error: ")); ConditionCheck is Room assertTrue(res.contains("[None, ConditionalCheckFailed, None]")); }Copy the code
Test 4: Repeat room
@Test public void testDupRoomException() { Order order = new Order(); order.setOrderId("2"); order.setAccountId("A2"); order.setRoomId("R2"); String res = createOrder(order); assertTrue(res.equals("SUCCESS")); Order order2 = new Order(); order2.setOrderId("3"); order2.setAccountId("A3"); order2.setRoomId("R2"); String res2 = createOrder(order2); assertTrue(res2.contains("Transaction Canceled, implies a client issue, fix before retrying. Error: ")); AssertTrue (res2.contains("[None, ConditionalCheckFailed, None]")); // clean up deleteOrder(order); }Copy the code
Test 5: Repeat order
@Test public void testDupOrderException() { Order order = new Order(); order.setOrderId("2"); order.setAccountId("A2"); order.setRoomId("R2"); String res = createOrder(order); assertTrue(res.equals("SUCCESS")); Order order2 = new Order(); order2.setOrderId("2"); order2.setAccountId("A3"); order2.setRoomId("R3"); String res2 = createOrder(order2); assertTrue(res2.contains("Transaction Canceled, implies a client issue, fix before retrying. Error: ")); AssertTrue (res2.contains("[None, None, ConditionalCheckFailed]")); // clean up deleteOrder(order); }Copy the code
Test six: None
@Test public void testAllInvalidException() { Order order = new Order(); order.setOrderId("2"); order.setAccountId("A2"); order.setRoomId("R2"); String res = createOrder(order); assertTrue(res.equals("SUCCESS")); Order order2 = new Order(); order2.setOrderId("2"); order2.setAccountId("NONE"); order2.setRoomId("NONE"); String res2 = createOrder(order2); assertTrue(res2.contains("Transaction Canceled, implies a client issue, fix before retrying. Error: ")); AssertTrue (res2.contains("[ConditionalCheckFailed, ConditionalCheckFailed, ConditionalCheckFailed, ConditionalCheckFailed]")); // clean up deleteOrder(order); }Copy the code

More examples

  • Docs.aws.amazon.com/amazondynam…