🌺 AWS CDK 101 - 🚂 Dynamodb stream triggering batch deleteItem on dynamodb table
Tags: aws, serverless, typescript, awscdk
Published: 2022-05-15 19:28:07

:beginner: Beginners new to AWS CDK, please do look at my previous articles one by one in this series.

If in case missed my previous article, do find it with the below links.

:repeat: Original previous post at :link: [Dev Post](https://devpost.hashnode.dev/aws-cdk-101-using-batched-dynamodb-stream-to-delete-item-on-another-dynamodb-table)

:repeat: Reposted previous post at :link: [dev to @aravindvcyber](https://dev.to/aravindvcyber/aws-cdk-101-using-batched-dynamodb-stream-to-delete-item-on-another-dynamodb-table-4oag)

In this article, let us add refine my previous article which demonstrated making use of a batch of dynamodb stream to delete items from another table. Here we convert the simple deleteItem action into a batchWrite action, which has great advantages.

## Benefits in this approach :sweat_drops:

* As earlier discussed in the previous article we are trying to make this scavenging setup as efficient as possible.
* This integration is fully asynchronous and does not block the existing process flow, and it uses the least amount of API calls and resources as discussed below.
* Involving `batchWrite` makes us make delete operation on a maximum chunk size of 25. 
* Since only one request is used in place of 25 calls, write request throttling scenarios can also be avoided when we have smaller provisioned capacities.
* By then you also have to check the `UnprocessedItems` to retry any failed keys if it is returned in the worst cases.
* In a summary including the previous article, we are now able to perform a batch get stream object with a size of 100 max which in turn performs a batch write of 25 max at a time.
* Fewer handler invocations and fewer dynamodb API calls.

![new relic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66i54dbmw5ttec8wq9i9.png)

## Planning and Construction :rowboat:

As already mentioned we will optimize the previous `deleteItem` helper function and convert our stream invocation into a batchWrite action on dynamodb table with chunks of 25 max.

Starting with refractor my previous lambda a bit to achieve the desired effects optimizing goals.

Here you may find that we are targeting an event name to be `INSERT`, likewise we can have finer control over our desired outcome during these stream invocations as shown below.

## Imports necessary 💮

The below imports are using in this article.

```ts
import { DynamoDBStreams } from "aws-sdk";
import {
  DeleteItemInput,
  BatchWriteItemInput,
  WriteRequest,
} from "aws-sdk/clients/dynamodb";
```

## New handler function logic :anchor:

Here we have optimized the lambda handler as follows, this involves creating a `keyMap` from the stream data, and further slicing this into chunks of a maximum of 25 to perform `batchWrite` operation invoking our helper method.

```ts
exports.created = async function (event: any) {
  console.log("Received stream:", JSON.stringify(event, undefined, 2));

  const keyMap: any[] = [];
  event.Records.map((Record: DynamoDBStreams.Record) => {
    console.log(JSON.stringify(Record, undefined, 2));
    if (Record.eventName === "INSERT") {
      keyMap.push(Record.dynamodb?.Keys);
    }
  });

  const chunkList = [...chunks(keyMap, 25)];
  await Promise.all(
    chunkList.map(async (chunk: any[]) => {
      const results = await batchDeleteDbItems(chunk);
      Object.entries(results).forEach((entry) => {
        console.log(JSON.stringify(entry, undefined, 2));
      });
    })
  );
};
```

## Helper function dynamodb deleteItem :bouquet:

Simple helper function to perform deleteItem from a dynamodb table.

Here we use the key list to generate a collection of `WriteRequest` of `Delete` action as follows.

```ts
const batchDeleteDbItems: any = async (keys: any) => {
  console.log("Deleting: ", { keys });

  const writeItems: WriteRequest[] = [];
  keys.map((key: any) => {
    const writeItem: WriteRequest = {
      DeleteRequest: {
        Key: {
          ...key,
        },
      },
    };
    writeItems.push(writeItem);
  });
  const params: BatchWriteItemInput = {
    RequestItems: {
      stgMessagesTable: writeItems,
    },
    ReturnConsumedCapacity: "TOTAL",
    ReturnItemCollectionMetrics: "SIZE",
  };

  console.log("deleteItem: ", JSON.stringify(params, undefined, 2));

  return await dynamo.batchWriteItem(params).promise();
};

```

## Minor changes to the dynamodb table definition :mandarin:

I have highlighted the necessary changes, we need to perform dynamodb stream generation for our table.

Most importantly, I have requested only keys, which will have all the necessary data we need here.

Besides in this article, we are trying to optimize as much as we could for the least footprint.

```ts
const messages = new dynamodb.Table(this, "MessagesTable", {
      tableName: process.env.messagesTable,
      sortKey: { name: "createdAt", type: dynamodb.AttributeType.NUMBER },
      partitionKey: { name: "messageId", type: dynamodb.AttributeType.STRING },
      encryption: dynamodb.TableEncryption.AWS_MANAGED,
      readCapacity: 5,
      writeCapacity: 5,
      stream: dynamodb.StreamViewType.KEYS_ONLY
});
```

![dynamodb stream](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08jngeb4292jo7hjqyjm.png)

## Sample dynamodb stream with one record 🥣

I have shared the dynamodb stream object used as payload to invoke our handler lambda below.

```json
{
  "eventID": "961320567faf6d890d6498a08fd1f34c",
  "eventName": "INSERT",
  "eventVersion": "1.1",
  "eventSource": "aws:dynamodb",
  "awsRegion": "ap-south-1",
  "dynamodb": {
    "ApproximateCreationDateTime": 1652637075,
    "Keys": {
      "createdAt": {
        "N": "1652637067000"
      },
      "messageId": {
        "S": "615f1e20-8261-4492-bb69-c281d3ea3382"
      }
    },
    "SequenceNumber": "36634400000000018872472773",
    "SizeBytes": 61,
    "StreamViewType": "KEYS_ONLY"
  },
  "eventSourceARN": "arn:aws:dynamodb:ap-south-1:57*****7078*****55:table/MessagesTable/stream/2022-05-15T17:48:27.902"
}
```

## Console log during execution :popcorn:

Finally post-execution, we could find the above JSON payload we have received in the `event` object and which is then used to batch delete from our staging table. You may find the results below in cloud watch logs.

Here a simple k6 load test is performed for a period of 15s with 10 targets and we can get 27 requests of which 25 are successful. the 2 failed because of the rate limit in our API gateway.

![15s 10 targets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e93jfm4rkiupxqb1wsje.png)

### Cloudwatch logs with 25 records of keys :fountain:

![25 records](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eurmm05a6dgb1bbai1ft.png)

### BatchWrite result :fountain:

![Write units](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ycg3vevoru6k384wtyo2.png)
 

## Visualizing latency involved :helicopter:

Here we take into account 25 successful puts, 25 get requests and a single batch deletes from separate handler functions. Though each request is of a different type, the common factor involved in the usage of dynamodb API calls across the network is where there are other overheads like latency.

![multi xray](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rn1c40bzd4p3y3kgb16k.png)

PutItem Requests

![put Requests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7220iuu1d4y6bnshqhje.png)

![entry to stg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ogo58x5ng5bsjsnrd6u.png)

GetItem Requests

![get Requests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fmc527zlnbqhrdjs2ggp.png)
 
![get Item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lmopj9oz39qr8k7i3dyq.png)

Batch DeleteItem Requests
 
![single xray](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k59mh2684prba9efzf62.png)

![graph](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6c2y5nenk407255iuo14.png)
 
Hence `batchWrite` or `batchGet` is much more efficient by avoiding the latency, and hence we have tried batchWrite for deletion of unwanted data.
As for us, I learn that 25 is the max limit for the batch writes request collection.

Also, it is essential to check`UnprocessedItems` in the result of batchWrite operation can be inspected and it is a best practice to check and retry the failed once, when we have some exceptions due to capacity or other failures due to message size.

In the next article, we will demonstrate how we will use a similar approach to delete Object from S3, which we have previously created.

We will be adding more connections to our stack and making it more usable in the upcoming articles by creating new constructs, so do consider following and subscribing to my newsletter.

⏭ We have our next article in serverless, do check out

https://dev.to/aravindvcyber/aws-cdk-101-dynamodb-streams-triggering-batch-deleteobjects-s3-2abn

🎉 Thanks for supporting! 🙏

Would be great if you like to [☕ Buy Me a Coffee](https://www.buymeacoffee.com/AravindVCyber), to help boost my efforts.

<a href="https://www.buymeacoffee.com/AravindVCyber"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=AravindVCyber&button_colour=BD5FFF&font_colour=ffffff&font_family=Cookie&outline_colour=000000&coffee_colour=FFDD00" /></a>

<a href='https://ko-fi.com/X8X0CITDJ' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

:repeat: Original post at 🔗 [Dev Post](https://devpost.hashnode.dev/aws-cdk-101-dynamodb-streams-triggering-batch-deleteitem-on-dynamodb-table)

:repeat: Reposted at 🔗 [dev to @aravindvcyber](https://dev.to/aravindvcyber/aws-cdk-101-using-batched-dynamodb-stream-to-delete-item-on-another-dynamodb-table-4oag)

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">🌺 AWS CDK 101 - 🚂 Dynamodb streams triggering batch deleteItem on another dynamodb table <a href="https://twitter.com/hashnode?ref_src=twsrc%5Etfw">@hashnode</a><br><br>Checkout more at my page<a href="https://t.co/CuYxnKr0Ig">https://t.co/CuYxnKr0Ig</a><a href="https://twitter.com/hashtag/TheHashnodeWriteathon?src=hash&amp;ref_src=twsrc%5Etfw">#TheHashnodeWriteathon</a><a href="https://twitter.com/hashtag/aws?src=hash&amp;ref_src=twsrc%5Etfw">#aws</a> <a href="https://twitter.com/hashtag/awscdk?src=hash&amp;ref_src=twsrc%5Etfw">#awscdk</a> <a href="https://twitter.com/hashtag/dynamodb?src=hash&amp;ref_src=twsrc%5Etfw">#dynamodb</a> <a href="https://twitter.com/hashtag/typescript?src=hash&amp;ref_src=twsrc%5Etfw">#typescript</a> <a href="https://twitter.com/hashtag/thwcloud?src=hash&amp;ref_src=twsrc%5Etfw">#thwcloud</a>-computing <a href="https://t.co/b3sOi0UmBu">https://t.co/b3sOi0UmBu</a></p>&mdash; Aravind V (@Aravind_V7) <a href="https://twitter.com/Aravind_V7/status/1525929803437973504?ref_src=twsrc%5Etfw">May 15, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>