Home > Mobile >  Prevent aws lambda code execute for multiple time
Prevent aws lambda code execute for multiple time

Time:11-03

I have a very important webhook which call my lambda function. The issue is this webhook is hitting my lambda function thrice with same data. I don't want to process thrice. I want to exit if it's already being called. I tried to store the data (paid) in dynamo db and check if it's already present but that ain't working. it's like the db is not atomic.

I call below method before executing the code.

def check_duplicate_webhook(user_id, order_id):
    try:
        status = dynamodb_table_payment.get_item(Key={'user_id': user_id},
                                                 ProjectionExpression='payments.#order_id.#pay_status',
                                                 ExpressionAttributeNames={
                                                     "#order_id": order_id,
                                                     '#pay_status': "status"
                                                 })
        if "Item" in status and "payments" in status['Item']:
            check = status['Item']['payments'][order_id]

            if check == 'paid':
                return True

        return False
    except Exception as e:
        log(e)
        return False

Updating the database

 dynamodb_table_payment.update_item(Key={'user_id': user_id},
                                       UpdateExpression="SET payments.#order_id.#pay_status = :pay_status, "
                                                        "payments.#order_id.#update_date = :update_date, "
                                                        "payments.#order_id.reward = :reward_amount",
                                       ExpressionAttributeNames={
                                           "#order_id": attr['order_id'],
                                           '#pay_status': "status",
                                           '#update_date': 'updated_at'
                                       },
                                       ExpressionAttributeValues={
                                           ":pay_status": 'paid',
                                           ':update_date': int(time.time()),
                                           ':reward_amount': reward_amount
                                       })

CodePudding user response:

DynamoDB isn't atomic and if the three requests come very close together, it could happen that the read value isn't consistent. For financial transactions it is recommended to use DynamoDB transactions.

May I also suggest that you use Step Functions and decouple the triggering from the actual execution. The webhook will trigger a function that will register the payment for execution. A different function will execute it. You will need some orchestration in the future, if for not anything else, to implement a retry logic.

CodePudding user response:

You're separating the retrieve and update, which can cause a race condition. You should be able to switch to a put_item() with condition, which will only insert once (or optionally update if the criteria are met).

You could also use a FIFO SQS queue as an intermediary between the webhook and Lambda, and let it do the deduplication. But that's a more complex solution.

It also appears that you're storing all orders for a given customer in a single record in DynamoDB. This seems like a bad idea to me: first because you need more RCUs/WCUs to be able to retrieve larger records, second because you will eventually bump up against the size limit of a DynamoDB record, and third because it makes the update logic more complex. I think you would be better to manage orders separately, using a key of (user_id, order_id).

  • Related