Search code examples
amazon-web-servicesamazon-dynamodbdynamodb-queries

Enforcing uniqueness with conditional writes in DynamoDB


I am new to DynamoDB, i have a table: post_reaction (it stores user's reaction on a post)

PK: postId

SK: reactionType#timestamp#userId

userId: userId
PK SK userId
123 1#1676444573#324 324

Since my SK is non-deterministic (because of timestamp), is there any way to avoid duplicates? Same user cannot give same reactionType on the same Post

I have tried using ConditionExpression attribute_not_exists, but not working


Solution

  • The conditions for conditional item changes are only enforced on the item being changed and not across other items as that wouldn't scale well. An exception are transactions, which are a different topic.

    I assume you have the timestamp for sorting purposes or making the record unique.

    What you want to achieve can be done by using a global secondary index, I'm going to call it GSI1 with GSI1PK as its partition and GSI1SK as its sort key:

    PK SK GSI1PK GSI1SK DATA
    POST#123 USER#123#REACTION#1 POST#123 REACTION#1#TS#20230215 reactionType: 1
    POST#123 USER#123#REACTION#2 POST#123 REACTION#2#TS#20230213 reactionType: 2
    POST#123 USER#456#REACTION#2 POST#123 REACTION#2#TS#20230214 reactionType: 2

    Your business rule that you need to enforce appears to be:

    A user can only react to a post with a given reaction type exactly once.

    Given this table structure you try to put the item into the base table if it doesn't exist. This will enforce your constraint as the combination of partition and sort key needs to be unique in the base table.

    You can use the global secondary index to get all reactions for a post. Partition and Sort Key combinations in GSIs don't need to be unique, so you can query GSI1 with the Post Id and get all the reactions. You could still append a timestamp to GSI1Sk if you need sorting.

    The drawback is higher costs because of the GSI.


    And alternative (simpler) setup looks like this and omits the GSI at the cost of not being able to sort reactions by timestamps:

    PK SK DATA
    POST#123 REACTION#1#USER#123 reactionType: 1
    POST#123 REACTION#1#USER#456 reactionType: 1
    POST#123 REACTION#2#USER#123 reactionType: 2

    You can again do your conditional PutItem requests as above to enforce the constraint.

    To get all reactions for a post you do: Query: PK = Post#<id> & SK starts_with(REACTION#)

    To get all reactions of type x for a post you do: Query: PK = Post#<id> & SK starts_with(REACTION#<x>#)