Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DynamoDBMapper save item only if unique

I'm trying to keep items in my table unique based on a combination of two different columns.

I have an instanceId and imageId column (along with others) and based on a couple of posts on Stackoverflow and AWS Forums the below should work?

public void saveUnique(Server server) {
    DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression();
    Map<String, ExpectedAttributeValue> expectedAttributes =
        ImmutableMap.<String, ExpectedAttributeValue>builder()
            .put("instanceId", new ExpectedAttributeValue(false))
            .put("imageId", new ExpectedAttributeValue(false))
            .build();
    saveExpression.setExpected(expectedAttributes);
    saveExpression.setConditionalOperator(ConditionalOperator.AND);
    try {
        mapper.save(server, saveExpression);
    } catch (ConditionalCheckFailedException e) {
        //Handle conditional check
    }
}

However every time I try and save a duplicate item (same instanceId and imageId) it's successfully being saved into the database.

Am I missing anything else here?

EDIT R.E notionquest answer

Updating to the answer below.

I have a job that runs once a minute polling an API. The response from the API is represented as a Server POJO. The Server has an attribute named instanceId.

I want to make sure that if a Server with that instanceId is already in the database, don't save it.

The Server object has another attribute of id which is set as the table primary key.

public void saveUnique(Server server) {
    DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression();

    Map<String, ExpectedAttributeValue> expected = new HashMap<>();
    expected.put("instanceId", new ExpectedAttributeValue(new AttributeValue(server.getInstanceId())).withComparisonOperator(ComparisonOperator.NE));
    saveExpression.setExpected(expected);

    try {
        mapper.save(server, saveExpression);
    } catch (ConditionalCheckFailedException e) {
        LOGGER.info("Skipped saving as not unique...");
    }
}

This code will save the Server object over and over again with the exception never being thrown.

The Server POJO

@DynamoDBTable(tableName = "Servers")
public class Server {

    @Id
    private String id = UUID.randomUUID().toString();

    @DynamoDBTypeConvertedJson
    private Task task;

    @DynamoDBAttribute(attributeName = "instanceId")
    private String instanceId;

    public Server() {
    }

    @DynamoDBHashKey
    public String getId() {
        return id;
    }

    // other standard getters and setters
}
like image 948
Chris Turner Avatar asked Mar 16 '17 01:03

Chris Turner


1 Answers

2019 update

In 2019, looks like nothing has changed here since the time when the question was asked. Providing uniqueness for a non-primary-key field is still tricky in DynamoDB. Here is an article posted on Amazon recently: https://aws.amazon.com/blogs/database/simulating-amazon-dynamodb-unique-constraints-using-transactions/

Basically, instead of using an additional table as described below, the author proposes creating auxiliary records in the same table.

For example, on the below picture, for ensuring that instance_id=2c5d0cc8d900 is a unique value, you have to add a record with an artificial primary key value "instance_id#2c5d0cc8d900". If the insert succeeds, you can insert the main record itself.

+--------------------------+-------------------------+--------+----------------
|           id             |       instance_id       | attr1  | other fields... 
|                          |                         |        |
|      (primary key)       |     (a non-key field,   |        |
|                          |     must be unique)     |        |
+--------------------------+-------------------------+--------+----------------
| instance_id#2c5d0cc8d900 |                         |        |
| a9fd702a                 | 2c5d0cc8d900            | qwerty | ...

While this approach may work fine, I personally still prefer to use a separate table like described below in my original answer. Because, when reading data from a table that contains such auxiliary records, you have to care about filtering them from actual ones.


Original answer

If I correctly understood the question, you'd like to ensure uniqueness for a field which is not the hash key.

(I'm not sure why you do not use instanceId as the hash key for Servers table, I guess you have a reason for that).

My answer: looks like you can't do that without using an auxiliary table.

Here is your existing Servers table:

+----------------------------------------------+
|                 Servers                      |   
+----------------------------------------------+
| * id            the hash key                 |
| * instanceId    non-key field, must be unique|
|                                              |
| * ...                                        |
| * other fields                               |
| * ...                                        | 
+----------------------------------------------+

I would create an additional table with instanceId as the hash key:

+----------------------------------------------+
|                 Instance                     |   
+----------------------------------------------+
| * instanceId    the hash key                 |
+----------------------------------------------+

Having such a table, before saving a record into Servers, you have to ensure first that instanceId value is unique by adding a record (putItem) into Instance, providing a ConditionExpression like attribute_not_exists(instanceId).

And only if the put operation completes without an ConditionalCheckFailedException error, you can proceed with adding the record into Servers.

If you'd like to ensure uniqueness of records in Servers based on combination of two fields, instanceId and imageId, instead of just instanceId use concatenated values of these fields as a single field in your aux table:

+----------------------------------------------+
|               Instance_Image                 |   
+----------------------------------------------+
| * instanceId_imageId   the hash key          |
+----------------------------------------------+
like image 115
xtx Avatar answered Sep 26 '22 02:09

xtx