AWS DynamoDB

Wednesday, Jun 25, 2025| Tags: aws, dynamodb

DISCLAIMER: Image is generated using ChatGPT.


1. Introduction

2. DynamoDB Structure

3. Setup LocalStack

4. Using CLI

5. Using Python

6. Using Perl


Introduction



AWS DynamoDB is a fully managed NoSQL database service provided by AWS.

It is designed for high availability, scalability and low-latency performance.

It is a popular choice for modern applications that require fast and flexible data storage.

Unlike traditional relational databases, DynamoDB is schemaless, meaning it does not require a fixed table structure.

DynamoDB can handle millions of requests per second by distributing data across multiple servers.

Horizontal scaling refers to adding more machines or nodes to a system to handle increased load, rather than upgrading a single machine, which is Vertical scaling.

AWS DynamoDB supports automatic horizontal scaling.


DynamoDB Structure



In DynamoDB, each table has a primary key that is composed of one or two parts: Partition Key (HASH key) and Sort Key (RANGE key)

There are two types of primary keys in DynamoDB.


1. Simple Primary Key


It consists of single attribute (partition key). It is used to distribute data across partitions.


2. Composite Primary Key


It consists of two attributes: Partition Key and Sort Key.


Partition Key determines the physical storage partition and Sort Key orders items within the same partition.

The key-schema is used to define the primary key (partition key and sort key).

The attribute-definitions is used to define the data types of the attributes in the table.

Optionally, we can create Global Secondary Indexes which helps to query the data using different attributes.


Setup LocalStack



As always being a Docker fan, I am using this docker compose configuration to start the LocalStack container.


$ cat docker-compose.yml
version: '3.8'

services:
  localstack:
    image: localstack/localstack
    container_name: localstack
    ports:
      - "4566:4566"
      - "4510-4559:4510-4559"
    environment:
      - LAMBDA_EXECUTOR=docker
      - HOST_TMP_FOLDER=/host_tmp
      - LOCALSTACK_S3_STRICT_ENCRYPTION=1
      - SERVICES=s3,ec2,lambda,sts,iam,apigateway,logs,kinesis,dynamodb
      - LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT=60
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - localstack_localstack_data:/var/lib/localstack
      - /tmp/localstack_host:/host_tmp

volumes:
  localstack_localstack_data:

Start the container, do this:


$ docker-compose up -d

Using CLI



For demo purpose, lets create table Users with attributes id, name, age and sex. We’ll make id as primary key.


Create Table


$ aws dynamodb create-table \
    --table-name Users \
    --billing-mode PAY_PER_REQUEST \
    --attribute-definitions \
        AttributeName=id,AttributeType=S \
        AttributeName=name,AttributeType=S \
        AttributeName=age,AttributeType=N \
        AttributeName=sex,AttributeType=S \
    --key-schema AttributeName=id,KeyType=HASH \
    --global-secondary-indexes \
        '[
            {
                "IndexName": "NameIndex",
                "KeySchema": [{"AttributeName": "name", "KeyType": "HASH"}],
                "Projection": {"ProjectionType": "ALL"}
            },
            {
                "IndexName": "AgeIndex",
                "KeySchema": [{"AttributeName": "age", "KeyType": "HASH"}],
                "Projection": {"ProjectionType": "ALL"}
            },
            {
                "IndexName": "SexIndex",
                "KeySchema": [{"AttributeName": "sex", "KeyType": "HASH"}],
                "Projection": {"ProjectionType": "ALL"}
            }
        ]'

List Tables


To list all the tables in DynamoDB, try this:


$ aws dynamodb list-tables --output text
TABLENAMES      Users

Describe Table


To describe table definition, try this:


$ aws dynamodb describe-table --table-name Users
{
    "Table": {
        "AttributeDefinitions": [
            {
                "AttributeName": "id",
                "AttributeType": "S"
            },
            {
                "AttributeName": "name",
                "AttributeType": "S"
            },
            {
                "AttributeName": "age",
                "AttributeType": "N"
            },
            {
                "AttributeName": "sex",
                "AttributeType": "S"
            }
        ],
        "TableName": "Users",
        "KeySchema": [
            {
                "AttributeName": "id",
                "KeyType": "HASH"
            }
        ],
        ...
        ...
        ...
    }
}

Add Item


Add items to the table. I am using the most basic form here.

We are adding three items as below:


$ aws dynamodb put-item \
  --table-name Users \
  --item '{
     "id": {"S": "1"},
     "name": {"S": "Joe"},
     "age": {"N": "23"},
     "sex": {"S": "m"}
   }'

$ aws dynamodb put-item \
  --table-name Users \
  --item '{
    "id": {"S": "2"},
    "name": {"S": "Kate"},
    "age": {"N": "25"},
    "sex": {"S": "f"}
  }'

$ aws dynamodb put-item \
  --table-name Users \
  --item '{
    "id": {"S": "3"},
    "name": {"S": "John"},
    "age": {"N": "28"},
    "sex": {"S": "m"}
  }'

Show Items


To list all the items in the table, try this:


$ aws dynamodb scan --table-name Users --no-paginate
{
    "Items": [
        {
            "name": {
                "S": "Joe"
            },
            "id": {
                "S": "1"
            },
            "age": {
                "N": "23"
            },
            "sex": {
                "S": "m"
            }
        },
        {
            "name": {
                "S": "John"
            },
            "id": {
                "S": "3"
            },
            "age": {
                "N": "28"
            },
            "sex": {
                "S": "m"
            }
        },
        {
            "name": {
                "S": "Kate"
            },
            "id": {
                "S": "2"
            },
            "age": {
                "N": "25"
            },
            "sex": {
                "S": "f"
            }
        }
    ],
    "Count": 3,
    "ScannedCount": 3
}

Query Table


Let’s query table using the primary key.


$ aws dynamodb query \
    --table-name Users \
    --key-condition-expression "id = :id" \
    --expression-attribute-values '{":id": {"S": "2"}}'
{
    "Items": [
        {
            "name": {
                "S": "Kate"
            },
            "id": {
                "S": "2"
            },
            "age": {
                "N": "25"
            },
            "sex": {
                "S": "f"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

Update Item


Update an item using primary key.


$ aws dynamodb update-item \
  --table-name Users \
  --key '{"id": {"S": "2"}}' \
  --update-expression "SET age = :age" \
  --expression-attribute-values '{":age": {"N": "26"}}' \
  --return-values UPDATED_NEW
{
    "Attributes": {
        "age": {
            "N": "26"
        }
    }
}

Delete Table


To delete the table, just do this:


$ aws dynamodb delete-table --table-name Users

Using Python



Here we will re-create the same table and play with it as a standalone script.

Standard package import as needed and created client.

Also defined table name.


import boto3
from botocore.exceptions import ClientError

dynamodb = boto3.client('dynamodb', endpoint_url='http://localhost:4566', region_name='eu-west-1')
table_name = 'Users'

Declare function to delete the table if exists, so that we can run it multiple times.

I found something new in Python called waiter.


def delete_table_if_exists(table_name):
    try:
        dynamodb.describe_table(TableName=table_name)
        response = dynamodb.delete_table(TableName=table_name)

        # create waiter of type 'table_not_exists'
        waiter = dynamodb.get_waiter('table_not_exists')
        # ask the waiter to wait until the given table reached the status
        waiter.wait(TableName=table_name)

        print(f"Table {table_name} deleted successfully.")

    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceNotFoundException':
            print(f"Table {table_name} does not exist - nothing to delete")
        else:
            print(f"Error deleting table {table_name}: {e}")
    except Exception as e:
        print(f"Unexpected error deleting table {table_name}: {e}")

Time to define method to create the table as below:


def create_table(table_name):
    try:
        response = dynamodb.create_table(
            TableName=table_name,
            KeySchema=[
                {
                    'AttributeName': 'id',
                    'KeyType': 'HASH'
                }
            ],
            AttributeDefinitions=[
                {'AttributeName': 'id', 'AttributeType': 'S'},
                {'AttributeName': 'name', 'AttributeType': 'S'},
                {'AttributeName': 'age', 'AttributeType': 'N'},
                {'AttributeName': 'sex', 'AttributeType': 'S'}
            ],
            GlobalSecondaryIndexes=[
                {
                    'IndexName': 'NameIndex',
                    'KeySchema': [
                        {'AttributeName': 'name', 'KeyType': 'HASH'}
                    ],
                    'Projection': {
                        'ProjectionType': 'ALL'
                    },
                    'ProvisionedThroughput': {
                        'ReadCapacityUnits': 5,
                        'WriteCapacityUnits': 5
                    }
                },
                {
                    'IndexName': 'AgeIndex',
                    'KeySchema': [
                        {'AttributeName': 'age', 'KeyType': 'HASH'}
                    ],
                    'Projection': {
                        'ProjectionType': 'ALL'
                    },
                    'ProvisionedThroughput': {
                        'ReadCapacityUnits': 5,
                        'WriteCapacityUnits': 5
                    }
                },
                {
                    'IndexName': 'SexIndex',
                    'KeySchema': [
                        {'AttributeName': 'sex', 'KeyType': 'HASH'}
                    ],
                    'Projection': {
                        'ProjectionType': 'ALL'
                    },
                    'ProvisionedThroughput': {
                        'ReadCapacityUnits': 5,
                        'WriteCapacityUnits': 5
                    }
                }
            ],
            ProvisionedThroughput={
                'ReadCapacityUnits': 5,
                'WriteCapacityUnits': 5
            }
        )
        print(f"Table {table_name} created successfully.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceInUseException':
            print(f"Table {table_name} already exists.")
        else:
            print(f"Error creating table: {e}")

So far so good, lets define function to add three items as below:


def insert_items(table_name):
    items = [
        {
            'PutRequest': {
                'Item': {
                    'id': {'S': '1'},
                    'name': {'S': 'Joe'},
                    'age': {'N': '23'},
                    'sex': {'S': 'm'}
                }
            }
        },
        {
            'PutRequest': {
                'Item': {
                    'id': {'S': '2'},
                    'name': {'S': 'Kate'},
                    'age': {'N': '25'},
                    'sex': {'S': 'f'}
                }
            }
        },
        {
            'PutRequest': {
                'Item': {
                    'id': {'S': '3'},
                    'name': {'S': 'John'},
                    'age': {'N': '28'},
                    'sex': {'S': 'f'}
                }
            }
        }
    ]

    try:
        response = dynamodb.batch_write_item(RequestItems={table_name: items})
        print(f"Items inserted successfully.")
    except ClientError as e:
        print(f"Error inserting items: {e}")

Now we need function to query items.


def query_items(table_name, id):
    try:
        response = dynamodb.scan(
            TableName=table_name,
            FilterExpression='id = :id',
            ExpressionAttributeValues={
                ':id': {'S': id}
            }
        )

        print(f"Items where id = {id}:")
        for item in response['Items']:
            print(item)
    except ClientError as e:
        print(f"Error querying items: {e}")

Here is the function to update an item.

def update_age(table_name, id, age):
    try:
        response = dynamodb.update_item(
            TableName=table_name,
            Key={
                'id': {'S': id }
            },
            UpdateExpression='SET age = :age',
            ExpressionAttributeValues={
                ':age': {'N': str(age)}
            },
            ReturnValues='UPDATED_NEW'
        )
        print(f"Item with id {id} updated successfully.")
        print(f"Updated attributes: {response['Attributes']}")
    except ClientError as e:
        print(f"Error updating item with id {id}: {e}")

How about delete an item?


def delete_item(table_name, id):
    try:
        response = dynamodb.delete_item(
            TableName=table_name,
            Key={
                'id': {'S': id}
            }
        )
        print(f"Item with id {id} deleted successfully.")
    except ClientError as e:
        print(f"Error deleting item with id {id}: {e}")

Finally function to list the table contents.


def list_table_contents(table_name):
    try:
        response = dynamodb.scan(TableName=table_name)
        print(f"Items in {table_name}:")
        for item in response['Items']:
            print(item)
    except ClientError as e:
        print(f"Error listing table contents: {e}")

We have covered almost all basic operations, let’s get the hand dirty.


if __name__ == "__main__":
    delete_table_if_exists(table_name)
    create_table(table_name)
    insert_items(table_name)
    list_table_contents(table_name)
    query_items(table_name, '2')
    delete_item(table_name, '1')
    list_table_contents(table_name)
    update_age(table_name, '2', 26)
    list_table_contents(table_name)

Time to run the script: aws-dynamodb.py


$ py aws-dynamodb.py
Table Users deleted successfully.
Table Users created successfully.
Items inserted successfully.
Items in Users:
{'name': {'S': 'Joe'}, 'id': {'S': '1'}, 'age': {'N': '23'}, 'sex': {'S': 'm'}}
{'name': {'S': 'John'}, 'id': {'S': '3'}, 'age': {'N': '28'}, 'sex': {'S': 'f'}}
{'name': {'S': 'Kate'}, 'id': {'S': '2'}, 'age': {'N': '25'}, 'sex': {'S': 'f'}}
Items where id = 2:
{'name': {'S': 'Kate'}, 'id': {'S': '2'}, 'age': {'N': '25'}, 'sex': {'S': 'f'}}
Item with id 1 deleted successfully.
Items in Users:
{'name': {'S': 'John'}, 'id': {'S': '3'}, 'age': {'N': '28'}, 'sex': {'S': 'f'}}
{'name': {'S': 'Kate'}, 'id': {'S': '2'}, 'age': {'N': '25'}, 'sex': {'S': 'f'}}
Item with id 2 updated successfully.
Updated attributes: {'age': {'N': '26'}}
Items in Users:
{'name': {'S': 'John'}, 'id': {'S': '3'}, 'age': {'N': '28'}, 'sex': {'S': 'f'}}
{'name': {'S': 'Kate'}, 'id': {'S': '2'}, 'age': {'N': '26'}, 'sex': {'S': 'f'}}

Using Perl



With the help of CPAN module Paws, let’s prepare the ground as below:


use v5.30;
use Try::Tiny;
use Data::Dumper;

my $dynamodb = Paws->service('DynamoDB',
    region   => 'eu-west-1',
    endpoint => 'http://localhost:4566'
);

my $table_name = 'Users';

No waiter in Perl, unfortunately.


sub delete_table_if_exists {
    my ($table_name) = @_;

    try {
        $dynamodb->DeleteTable(TableName => $table_name);
        say "Table $table_name deletion requested.";
        # LocalStack deletes immediately - no need to wait
    } catch {
        if ($_->isa('Paws::DynamoDB::Errors::ResourceNotFoundException')) {
            say "Table $table_name already doesn't exist.";
        } else {
            die "Error deleting table: $_\n";
        }
    };
}

Here is function to create table:


sub create_table {
    my ($table_name) = @_;

    try {
        my $response = $dynamodb->CreateTable(
            TableName => $table_name,
            KeySchema => [
                {
                    AttributeName => 'id',
                    KeyType       => 'HASH',
                },
            ],
            AttributeDefinitions => [
                { AttributeName => 'id',   AttributeType => 'S' },
                { AttributeName => 'name', AttributeType => 'S' },
                { AttributeName => 'age',  AttributeType => 'N' },
                { AttributeName => 'sex',  AttributeType => 'S' },
            ],
            GlobalSecondaryIndexes => [
                {
                    IndexName => 'NameIndex',
                    KeySchema => [
                        {
                            AttributeName => 'name',
                            KeyType       => 'HASH',
                        },
                    ],
                    Projection => {
                        ProjectionType => 'ALL',
                    },
                    ProvisionedThroughput => {
                        ReadCapacityUnits  => 5,
                        WriteCapacityUnits => 5,
                    },
                },
                {
                    IndexName => 'AgeIndex',
                    KeySchema => [
                        {
                            AttributeName => 'age',
                            KeyType       => 'HASH',
                        },
                    ],
                    Projection => {
                        ProjectionType => 'ALL',
                    },
                    ProvisionedThroughput => {
                        ReadCapacityUnits  => 5,
                        WriteCapacityUnits => 5,
                    },
                },
                {
                    IndexName => 'SexIndex',
                    KeySchema => [
                        {
                            AttributeName => 'sex',
                            KeyType       => 'HASH',
                        },
                    ],
                    Projection => {
                        ProjectionType => 'ALL',
                    },
                    ProvisionedThroughput => {
                        ReadCapacityUnits  => 5,
                        WriteCapacityUnits => 5,
                    },
                },
            ],
            ProvisionedThroughput => {
                ReadCapacityUnits  => 5,
                WriteCapacityUnits => 5,
            },
        );

        say "Table $table_name created successfully.";
    } catch {
        if ($_->isa('Paws::DynamoDB::Errors::ResourceInUseException')) {
            say "Table $table_name already exists.";
        } else {
            die "Error creating table: $_\n";
        }
    };
}

The function to insert items.


sub insert_items {
    my ($table_name) = @_;

    my @items = (
        {
            'PutRequest' => {
                'Item'   => {
                    'id'   => {'S' => '1'  },
                    'name' => {'S' => 'Joe'},
                    'age'  => {'N' => '23' },
                    'sex'  => {'S' => 'm'  }
                }
            }
        },
        {
            'PutRequest' => {
                'Item'   => {
                    'id'   => {'S' => '2'   },
                    'name' => {'S' => 'Kate'},
                    'age'  => {'N' => '25'  },
                    'sex'  => {'S' => 'f'   }
                }
            }
        },
        {
            'PutRequest' => {
                'Item'   => {
                    'id'   => {'S' => '3'   },
                    'name' => {'S' => 'John'},
                    'age'  => {'N' => '28'  },
                    'sex'  => {'S' => 'm'   }
                }
            }
        }
    );

    try {
        my $response = $dynamodb->BatchWriteItem(
            RequestItems => {
                $table_name => \@items
            }
        );
        say "Items inserted successfully.";
    } catch {
        die "Error inserting items: $_\n";
    };
}

Query item using primary key.


sub query_items {
    my ($table_name, $id) = @_;

    try {
        my $response = $dynamodb->Scan(
            TableName => $table_name,
            FilterExpression => 'id = :id',
            ExpressionAttributeValues => {
                ':id' => { S => $id }
            }
        );

        say "Items where id = $id:";
        foreach my $item (@{$response->Items}) {
            say Dumper($item);
        }
    } catch {
        die "Error querying items: $_\n";
    };
}

Update item by primary key.


sub update_age {
    my ($table_name, $id, $new_age) = @_;

    try {
        my $response = $dynamodb->UpdateItem(
            TableName => $table_name,
            Key => {
                id => { S => $id }
            },
            UpdateExpression => 'SET age = :age',
            ExpressionAttributeValues => {
                ':age' => { N => $new_age }
            },
            ReturnValues => 'UPDATED_NEW'
        );
        say "Item with id $id updated successfully.";
        say "Updated attributes: " . Dumper($response->Attributes);
    } catch {
        die "Error updating item with id $id: $_\n";
    };
}

Delete an item using primary key.


sub delete_item {
    my ($table_name, $id) = @_;

    try {
        $dynamodb->DeleteItem(
            TableName => $table_name,
            Key => {
                id => { S => $id }
            }
        );
        say "Item with id $id deleted successfully.";
    } catch {
        die "Error deleting item with id $id: $_\n";
    };
}

Finally list table contents.


sub list_table_contents {
    my ($table_name) = @_;

    try {
        my $response = $dynamodb->Scan(TableName => $table_name);
        say "Items in $table_name:";
        foreach my $item (@{$response->Items}) {
            say Dumper($item);
        }
    } catch {
        die "Error listing table contents: $_\n";
    };
}

Now time for quick operations as below:


delete_table_if_exists($table_name);
create_table($table_name);
insert_items($table_name);
list_table_contents($table_name);
query_items($table_name, '2');
delete_item($table_name, '1');
list_table_contents($table_name);
update_age($table_name, '2', 26);
list_table_contents($table_name);

Let’s run the script now: aws-dynamodb.pl


$ perl aws-dynamodb.pl
Table Users deletion requested.
Table Users created successfully.
Items inserted successfully.
Items in Users:
$VAR1 = bless( {
                 'Map' => {
                            'name' => bless( {
                                               'L' => [],
                                               'S' => 'Joe'
                                             }, 'Paws::DynamoDB::AttributeValue' ),
                            'age' => bless( {
                                              'N' => '23',
                                              'L' => []
                                            }, 'Paws::DynamoDB::AttributeValue' ),
                            'id' => bless( {
                                             'S' => '1',
                                             'L' => []
                                           }, 'Paws::DynamoDB::AttributeValue' ),
                            'sex' => bless( {
                                              'L' => [],
                                              'S' => 'm'
                                            }, 'Paws::DynamoDB::AttributeValue' )
                          }
               }, 'Paws::DynamoDB::AttributeMap' );
...
...
...
...



Happy Hacking !!!

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me