UnconditionedLife / smum

SMUM Checkin for Santa Maria Urban Ministries (San Jose, California)
https://www.santamariasj.org
3 stars 0 forks source link

[Bug] New client ID is not incrementing as expected - need better solution #188

Closed UnconditionedLife closed 2 months ago

kjain14 commented 6 months ago

Using Atomic Counters:

DynamoDB supports atomic counters, which can be used to increment a numeric attribute in an item. This method can be useful if you have a high-write throughput requirement.

Define a Numeric Attribute: In your table, define an attribute that will act as the counter.

Increment the Attribute: Use the UpdateItem API with the ADD operator to atomically increment the attribute.

Example:

import boto3

dynamodb = boto3.resource('dynamodb')

table = dynamodb.Table('YourTable')

def increment_counter(item_id, counter_attribute='Counter'):
    response = table.update_item(
        Key={'PrimaryKey': item_id},
        UpdateExpression='ADD #ctr :inc',
        ExpressionAttributeNames={'#ctr': counter_attribute},
        ExpressionAttributeValues={':inc': 1},
        ReturnValues='UPDATED_NEW'
    )
    return response['Attributes'][counter_attribute]

# Usage
item_id = 'your-item-id'
new_counter_value = increment_counter(item_id)
print(f"New Counter Value: {new_counter_value}")
scott-r commented 6 months ago

Atomic counters are also described in the DynamoDB Developer's Guide (v. 2012-08-10), p. 349:

You can use the UpdateItem operation to implement an atomic counter—a numeric attribute that is incremented, unconditionally, without interfering with other write requests. (All write requests are applied in the order in which they were received.) With an atomic counter, the updates are not idempotent. In other words, the numeric value increments each time you call UpdateItem. You might use an atomic counter to track the number of visitors to a website. In this case, your application would increment a numeric value, regardless of its current value. If an UpdateItem operation fails, the application could simply retry the operation. This would risk updating the counter twice, but you could probably tolerate a slight overcounting or undercounting of website visitors. An atomic counter would not be appropriate where overcounting or undercounting can't be tolerated (for example, in a banking application). In this case, it is safer to use a conditional update instead of an atomic counter.

UnconditionedLife commented 5 months ago

Step 1: Create a DynamoDB Table

Create a DynamoDB table named Counters with a partition key counter_name of type String.

{
    "TableName": "Counters",
    "KeySchema": [
        { "AttributeName": "counter_name", "KeyType": "HASH" }
    ],
    "AttributeDefinitions": [
        { "AttributeName": "counter_name", "AttributeType": "S" }
    ],
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 5,
        "WriteCapacityUnits": 5
    }
}

Step 2: Create an API Gateway Endpoint

  1. Create a New API: Go to the API Gateway console and create a new REST API.

  2. Create a New Resource: Create a new resource (e.g., /increment).

  3. Create a New Method: Create a new POST method for the /increment resource.

  4. Set Up the Integration: Integrate the POST method directly with DynamoDB.

    • Integration type: AWS Service
    • AWS Region: Your region
    • AWS Service: DynamoDB
    • HTTP method: POST
    • Action Type: Use action name
    • Action: UpdateItem
  5. Configure the Integration Request:

    • In the Integration Request section, add the necessary mapping templates to format the request to DynamoDB.

Mapping Template for Integration Request:

Create a mapping template for application/json to format the request payload to DynamoDB's UpdateItem action.

{
    "TableName": "Counters",
    "Key": {
        "counter_name": {
            "S": "my_counter"
        }
    },
    "UpdateExpression": "ADD #val :inc",
    "ExpressionAttributeNames": {
        "#val": "counter_value"
    },
    "ExpressionAttributeValues": {
        ":inc": {
            "N": "1"
        }
    },
    "ReturnValues": "UPDATED_NEW"
}
  1. Deploy the API: Deploy the API to a stage (e.g., prod).

Step 3: Test the API

You can test the API by making a POST request to the endpoint you created.

Example using curl:

curl -X POST https://your-api-id.execute-api.your-region.amazonaws.com/prod/increment

The response should be the new incremented value.

Summary

By following these steps, you have created a system where:

This setup ensures that the increment operation is atomic, leveraging DynamoDB's capabilities and making the counter accessible via an API Gateway endpoint.