Database

AWS DynamoDB: NoSQL Database

Tables, queries, GSI/LSI indexes, capacity modes, DynamoDB Streams β€” panduan lengkap managed NoSQL database di AWS

1. Pengenalan DynamoDB

Amazon DynamoDB adalah layanan database NoSQL fully-managed dari AWS. DynamoDB dirancang untuk performa tinggi, skalabilitas, dan ketersediaan β€” tanpa perlu mengelola server, patching, atau konfigurasi infrastruktur.

DynamoDB digunakan oleh perusahaan besar seperti Amazon.com itu sendiri, Netflix, Lyft, Airbnb, dan Samsung untuk menangani traffic dalam skala miliaran request per hari.

Fitur Utama DynamoDB

Fitur Deskripsi
Fully ManagedTidak perlu server, patching, atau maintenance
ServerlessOtomatis scale up/down sesuai demand
Single-digit ms latencyKonsisten <10ms untuk read/write
Auto ScalingOtomatis menyesuaikan kapasitas
Global TablesMulti-region replication untuk latency rendah global
ACID TransactionTransaksi lintas tabel (sejak 2018)
DynamoDB StreamsCapture perubahan data real-time
Backup & RestorePoint-in-time recovery
Diagram: Arsitektur DynamoDB
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     AWS CLOUD                                    β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚  Client   │────▢│         DynamoDB Service          β”‚         β”‚
β”‚  β”‚ (App/SDK) β”‚     β”‚                                    β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚         β”‚
β”‚                    β”‚  β”‚ Partitionβ”‚  β”‚ Partitionβ”‚        β”‚         β”‚
β”‚                    β”‚  β”‚    1     β”‚  β”‚    2     β”‚        β”‚         β”‚
β”‚                    β”‚  β”‚(3 copies)β”‚  β”‚(3 copies)β”‚        β”‚         β”‚
β”‚                    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚         β”‚
β”‚                    β”‚                                    β”‚         β”‚
β”‚                    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚         β”‚
β”‚                    β”‚  β”‚ Partitionβ”‚  β”‚ Partitionβ”‚        β”‚         β”‚
β”‚                    β”‚  β”‚    3     β”‚  β”‚    4     β”‚        β”‚         β”‚
β”‚                    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚         β”‚
β”‚                    β”‚                                    β”‚         β”‚
β”‚                    β”‚  Storage: SSD-backed, replicated   β”‚         β”‚
β”‚                    β”‚  3x across 3 AZ for durability     β”‚         β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚                                                                 β”‚
β”‚  Features: Streams β†’ Lambda β†’ SNS/SQS                          β”‚
β”‚            DAX (in-memory cache)                                β”‚
β”‚            Global Tables (multi-region)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Setup AWS CLI & SDK

Bash β€” Setup AWS
# =============================================
# INSTALASI AWS CLI
# =============================================
# macOS
brew install awscli

# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Konfigurasi credentials
aws configure
# AWS Access Key ID: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region: ap-southeast-1 (Singapore)
# Default output format: json

# Cek konfigurasi
aws sts get-caller-identity

# =============================================
# PYTHON SDK (boto3)
# =============================================
pip install boto3

# =============================================
# LOCAL DynamoDB (untuk development)
# =============================================
# Download JAR
mkdir -p ~/dynamodb-local
cd ~/dynamodb-local
curl -O https://d1ni2b6xgvw0s0.cloudfront.net/v2.x/dynamodb_local_latest.tar.gz
tar xzf dynamodb_local_latest.tar.gz

# Jalankan
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb -port 8000

# Test dengan AWS CLI ke local
aws dynamodb list-tables --endpoint-url http://localhost:8000

2. Membuat & Mengelola Tabel

AWS CLI β€” Membuat Tabel DynamoDB
# =============================================
# MEMBUAT TABEL
# =============================================

# Tabel sederhana dengan partition key saja
aws dynamodb create-table \
    --table-name Products \
    --attribute-definitions \
        AttributeName=product_id,AttributeType=S \
    --key-schema \
        AttributeName=product_id,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST \
    --table-class STANDARD

# Tabel dengan partition key + sort key
aws dynamodb create-table \
    --table-name Orders \
    --attribute-definitions \
        AttributeName=customer_id,AttributeType=S \
        AttributeName=order_date,AttributeType=S \
    --key-schema \
        AttributeName=customer_id,KeyType=HASH \
        AttributeName=order_date,KeyType=RANGE \
    --billing-mode PAY_PER_REQUEST

# Tabel dengan provisioned capacity
aws dynamodb create-table \
    --table-name SensorData \
    --attribute-definitions \
        AttributeName=sensor_id,AttributeType=S \
        AttributeName=timestamp,AttributeType=N \
    --key-schema \
        AttributeName=sensor_id,KeyType=HASH \
        AttributeName=timestamp,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=10


# =============================================
# KELOLA TABEL
# =============================================

# Cek semua tabel
aws dynamodb list-tables

# Describe tabel (detail)
aws dynamodb describe-table --table-name Products

# Update capacity
aws dynamodb update-table \
    --table-name SensorData \
    --provisioned-throughput \
        ReadCapacityUnits=20,WriteCapacityUnits=20

# Hapus tabel
aws dynamodb delete-table --table-name Products

# Backup
aws dynamodb create-backup \
    --table-name Orders \
    --backup-name orders-backup-2026-06

Konsep Kunci: Primary Key

Tipe Key Komponen Cocok Untuk
Partition Key (HASH)Satu atributData dengan key unik (user_id, product_id)
Composite Key (HASH + RANGE)Dua atributData dengan pola many-to-many (customer + date)

3. CRUD Operations

AWS CLI β€” CRUD Operations
# =============================================
# PUT ITEM (Insert/Update)
# =============================================
aws dynamodb put-item \
    --table-name Products \
    --item '{
        "product_id": {"S": "PROD-001"},
        "name": {"S": "Laptop ASUS ROG"},
        "price": {"N": "15000000"},
        "category": {"S": "Elektronik"},
        "stock": {"N": "50"},
        "tags": {"SS": ["gaming", "laptop", "asus"]},
        "in_stock": {"BOOL": true},
        "specs": {"M": {
            "cpu": {"S": "Intel i9"},
            "ram": {"S": "32GB"},
            "storage": {"S": "1TB SSD"}
        }}
    }'

# Tipe data DynamoDB:
# S  = String
# N  = Number (always string format!)
# B  = Binary
# BOOL = Boolean
# NULL = Null
# M  = Map (object/dict)
# L  = List (array)
# SS = String Set
# NS = Number Set
# BS = Binary Set


# =============================================
# GET ITEM (Read by key)
# =============================================
aws dynamodb get-item \
    --table-name Products \
    --key '{"product_id": {"S": "PROD-001"}}' \
    --projection-expression "product_id, #n, price" \
    --expression-attribute-names '{"#n": "name"}'


# =============================================
# UPDATE ITEM
# =============================================
aws dynamodb update-item \
    --table-name Products \
    --key '{"product_id": {"S": "PROD-001"}}' \
    --update-expression "SET price = :p, stock = stock - :s" \
    --expression-attribute-values '{
        ":p": {"N": "14500000"},
        ":s": {"N": "1"}
    }' \
    --return-values UPDATED_NEW

# Conditional update (hanya jika stok > 0)
aws dynamodb update-item \
    --table-name Products \
    --key '{"product_id": {"S": "PROD-001"}}' \
    --update-expression "SET stock = stock - :s" \
    --expression-attribute-values '{
        ":s": {"N": "1"},
        ":min_stock": {"N": "0"}
    }' \
    --condition-expression "stock > :min_stock" \
    --return-values UPDATED_NEW


# =============================================
# DELETE ITEM
# =============================================
aws dynamodb delete-item \
    --table-name Products \
    --key '{"product_id": {"S": "PROD-001"}}'

# Conditional delete
aws dynamodb delete-item \
    --table-name Products \
    --key '{"product_id": {"S": "PROD-001"}}' \
    --condition-expression "attribute_exists(product_id)"


# =============================================
# BATCH WRITE (multiple items)
# =============================================
aws dynamodb batch-write-item \
    --request-items '{
        "Products": [
            {
                "PutRequest": {
                    "Item": {
                        "product_id": {"S": "PROD-002"},
                        "name": {"S": "Keyboard Mechanical"},
                        "price": {"N": "850000"},
                        "category": {"S": "Aksesoris"}
                    }
                }
            },
            {
                "PutRequest": {
                    "Item": {
                        "product_id": {"S": "PROD-003"},
                        "name": {"S": "Mouse Logitech"},
                        "price": {"N": "350000"},
                        "category": {"S": "Aksesoris"}
                    }
                }
            }
        ]
    }'

4. Query vs Scan

Ini adalah konsep terpenting di DynamoDB. Query menggunakan index untuk pencarian efisien. Scan membaca seluruh tabel β€” sangat tidak efisien untuk tabel besar.

AWS CLI β€” Query & Scan
# =============================================
# QUERY (cepat β€” gunakan partition key!)
# =============================================

# Query orders berdasarkan customer_id (partition key)
aws dynamodb query \
    --table-name Orders \
    --key-condition-expression "customer_id = :cid" \
    --expression-attribute-values '{
        ":cid": {"S": "CUST-001"}
    }'

# Query dengan sort key range
aws dynamodb query \
    --table-name Orders \
    --key-condition-expression \
        "customer_id = :cid AND order_date BETWEEN :d1 AND :d2" \
    --expression-attribute-values '{
        ":cid": {"S": "CUST-001"},
        ":d1": {"S": "2026-01-01"},
        ":d2": {"S": "2026-06-30"}
    }'

# Query dengan filter expression (filter SETELAH query)
aws dynamodb query \
    --table-name Orders \
    --key-condition-expression "customer_id = :cid" \
    --filter-expression "total_amount > :min_amount AND #s = :status" \
    --expression-attribute-names '{"#s": "status"}' \
    --expression-attribute-values '{
        ":cid": {"S": "CUST-001"},
        ":min_amount": {"N": "100000"},
        ":status": {"S": "completed"}
    }'

# Query dengan limit dan pagination
aws dynamodb query \
    --table-name Orders \
    --key-condition-expression "customer_id = :cid" \
    --expression-attribute-values '{":cid": {"S": "CUST-001"}}' \
    --limit 10 \
    --scan-index-forward false  # descending


# =============================================
# SCAN (lambat β€” baca SELURUH tabel!)
# =============================================

# Scan semua data (HINDARI di tabel besar!)
aws dynamodb scan --table-name Products

# Scan dengan filter
aws dynamodb scan \
    --table-name Products \
    --filter-expression "category = :cat AND price > :min_price" \
    --expression-attribute-values '{
        ":cat": {"S": "Elektronik"},
        ":min_price": {"N": "1000000"}
    }'

# Scan dengan projection (ambil kolom tertentu)
aws dynamodb scan \
    --table-name Products \
    --projection-expression "product_id, #n, price" \
    --expression-attribute-names '{"#n": "name"}'


# =============================================
# PERBANDINGAN QUERY vs SCAN
# =============================================
# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
# β”‚ Aspek        β”‚ QUERY            β”‚ SCAN             β”‚
# β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
# β”‚ Kecepatan    β”‚ βœ… Sangat cepat  β”‚ ❌ Lambat        β”‚
# β”‚ Cost         β”‚ βœ… Murah (sedikitβ”‚ ❌ Mahal (banyak  β”‚
# β”‚              β”‚    RCU)          β”‚    RCU)          β”‚
# β”‚ Filter       β”‚ Hanya key kolom  β”‚ Semua kolom      β”‚
# β”‚ Skalabilitas β”‚ βœ… Konsisten     β”‚ ❌ Makin lambat  β”‚
# β”‚              β”‚                  β”‚    seiring data  β”‚
# β”‚ Kapan pakai  β”‚ Selalu!          β”‚ Terakhir kali    β”‚
# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
⚠️ Scan = Full Table Scan

Scan di DynamoDB sama buruknya dengan SELECT * FROM tabel tanpa WHERE di SQL. Untuk tabel dengan jutaan item, scan bisa memakan waktu menit dan biaya RCU yang sangat besar. Selalu usahakan menggunakan Query dengan partition key.

5. GSI & LSI β€” Secondary Indexes

DynamoDB memiliki dua jenis secondary index: GSI (Global Secondary Index) dan LSI (Local Secondary Index). GSI adalah yang paling sering digunakan.

AWS CLI β€” Secondary Indexes
# =============================================
# GSI (Global Secondary Index)
# =============================================
# GSI = "virtual table" dengan partition key BERBEDA dari tabel utama
# Berguna untuk query berdasarkan kolom selain primary key

# Tambah GSI ke tabel yang sudah ada
aws dynamodb update-table \
    --table-name Products \
    --attribute-definitions \
        AttributeName=category,AttributeType=S \
        AttributeName=price,AttributeType=N \
    --global-secondary-index-updates '[
        {
            "Create": {
                "IndexName": "GSI-Category-Price",
                "KeySchema": [
                    {"AttributeName": "category", "KeyType": "HASH"},
                    {"AttributeName": "price", "KeyType": "RANGE"}
                ],
                "Projection": {
                    "ProjectionType": "INCLUDE",
                    "NonKeyAttributes": ["name", "stock"]
                }
            }
        }
    ]' --billing-mode PAY_PER_REQUEST

# Query GSI (seperti query tabel biasa)
aws dynamodb query \
    --table-name Products \
    --index-name "GSI-Category-Price" \
    --key-condition-expression "category = :cat AND price BETWEEN :p1 AND :p2" \
    --expression-attribute-values '{
        ":cat": {"S": "Elektronik"},
        ":p1": {"N": "500000"},
        ":p2": {"N": "5000000"}
    }'


# =============================================
# Membuat tabel BARU dengan GSI
# =============================================
aws dynamodb create-table \
    --table-name Orders \
    --attribute-definitions \
        AttributeName=customer_id,AttributeType=S \
        AttributeName=order_id,AttributeType=S \
        AttributeName=status,AttributeType=S \
        AttributeName=order_date,AttributeType=S \
    --key-schema \
        AttributeName=customer_id,KeyType=HASH \
        AttributeName=order_id,KeyType=RANGE \
    --global-secondary-index '[
        {
            "IndexName": "GSI-Status-Date",
            "KeySchema": [
                {"AttributeName": "status", "KeyType": "HASH"},
                {"AttributeName": "order_date", "KeyType": "RANGE"}
            ],
            "Projection": {"ProjectionType": "ALL"}
        }
    ]' \
    --billing-mode PAY_PER_REQUEST


# =============================================
# LSI (Local Secondary Index)
# =============================================
# LSI = partition key SAMA, sort key BERBEDA
# Harus dibuat saat tabel dibuat (tidak bisa ditambah nanti)
# Batasan: max 5 LSI per tabel, 10GB per partition

aws dynamodb create-table \
    --table-name Orders \
    --attribute-definitions \
        AttributeName=customer_id,AttributeType=S \
        AttributeName=order_date,AttributeType=S \
        AttributeName=total_amount,AttributeType=N \
    --key-schema \
        AttributeName=customer_id,KeyType=HASH \
        AttributeName=order_date,KeyType=RANGE \
    --local-secondary-indexes '[
        {
            "IndexName": "LSI-Customer-Amount",
            "KeySchema": [
                {"AttributeName": "customer_id", "KeyType": "HASH"},
                {"AttributeName": "total_amount", "KeyType": "RANGE"}
            ],
            "Projection": {"ProjectionType": "ALL"}
        }
    ]' \
    --billing-mode PAY_PER_REQUEST

GSI vs LSI

Aspek GSI LSI
Partition KeyBisa berbeda dari tabelHarus sama dengan tabel
Sort KeyBisa berbedaHarus berbeda dari tabel
Kapan dibuatKapan sajaHanya saat buat tabel
CapacityCapacity terpisahBerbagi capacity tabel
ConsistencyEventually consistentStrongly consistent
UkuranTidak terbatasMax 10GB per partition
Rekomendasiβœ… Gunakan ini (default)Hanya jika butuh strong consistency
πŸ’‘ Projection Types

ALL: Semua atribut tabel di-copy ke GSI (besar tapi lengkap). KEYS_ONLY: Hanya key attributes (kecil, hemat cost). INCLUDE: Key + atribut tertentu (balance). Pilih yang paling sesuai dengan query Anda.

6. Capacity Modes β€” On-Demand vs Provisioned

AWS CLI β€” Capacity Management
# =============================================
# ON-DEMAND MODE
# =============================================
# Bayar per request (pay-per-request)
# Cocok untuk: workload unpredictable, baru mulai, development

aws dynamodb update-table \
    --table-name Products \
    --billing-mode PAY_PER_REQUEST


# =============================================
# PROVISIONED MODE
# =============================================
# Bayar per kapasitas yang dialokasikan (RCU/WCU)
# Cocok untuk: workload predictable, cost optimization

aws dynamodb update-table \
    --table-name Products \
    --billing-mode PROVISIONED \
    --provisioned-throughput \
        ReadCapacityUnits=25,WriteCapacityUnits=10


# =============================================
# AUTO SCALING (Provisioned mode)
# =============================================
# Otomatis sesuaikan RCU/WCU berdasarkan utilisasi

# Register scalable target
aws application-autoscaling register-scalable-target \
    --service-namespace dynamodb \
    --resource-id table/Products \
    --scalable-dynamodb/table:ReadCapacityUnits \
    --min-capacity 5 \
    --max-capacity 100

# Create scaling policy
aws application-autoscaling put-scaling-policy \
    --service-namespace dynamodb \
    --scalable-dynamodb/table:ReadCapacityUnits \
    --resource-id table/Products \
    --policy-name ReadAutoScaling \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration '{
        "TargetValue": 70.0,
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "DynamoDBReadCapacityUtilization"
        },
        "ScaleInCooldown": 60,
        "ScaleOutCooldown": 60
    }'

Understanding RCU & WCU

Unit Apa Detail
1 RCU1 strongly consistent read/detikItem hingga 4KB
1 RCU2 eventually consistent reads/detikItem hingga 4KB
1 WCU1 write/detikItem hingga 1KB
1 WCU1 read/detik (transaction)2x WCU

7. DynamoDB Streams & Triggers

AWS CLI β€” DynamoDB Streams
# =============================================
# AKTIFKAN STREAMS
# =============================================
aws dynamodb update-table \
    --table-name Orders \
    --stream-specification \
        StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

# StreamViewType options:
# KEYS_ONLY         β€” hanya key attributes
# NEW_IMAGE         β€” data baru saja
# OLD_IMAGE         β€” data lama saja
# NEW_AND_OLD_IMAGES β€” data baru + lama (paling lengkap)

# Cek stream
aws dynamodb describe-table --table-name Orders \
    --query 'Table.LatestStreamArn'

# Read stream records
aws dynamodb get-records \
    --shard-iterator "ITERATOR_STRING"


# =============================================
# TRIGGER: Lambda function saat data berubah
# =============================================
# Skenario: Kirim notifikasi saat order dibuat

# Lambda function (Python):
"""
import json

def lambda_handler(event, context):
    for record in event['Records']:
        if record['eventName'] == 'INSERT':
            new_image = record['dynamodb']['NewImage']
            customer_id = new_image['customer_id']['S']
            total = new_image['total']['N']
            print(f"New order from {customer_id}: Rp{total}")

            # Kirim notifikasi via SNS, email, dll.
            # send_notification(customer_id, total)
    return {'statusCode': 200}
"""

# Buat event source mapping di AWS Console atau CLI:
# aws lambda create-event-source-mapping \
#     --event-source-arn arn:aws:dynamodb:region:account:table/Orders/stream/xxx \
#     --function-name order-processor \
#     --starting-position LATEST

8. TTL & DAX Cache

AWS CLI β€” TTL & DAX
# =============================================
# TTL (Time To Live)
# =============================================
# Aktifkan TTL pada kolom "expires_at" (Unix timestamp)
aws dynamodb update-table \
    --table-name SessionData \
    --attribute-definitions AttributeName=expires_at,AttributeType=N \
    --time-to-live-specification \
        Enabled=true,AttributeName=expires_at

# Set item dengan TTL (30 hari dari sekarang)
# expires_at = current Unix timestamp + 2592000 detik
import time
# expires_at: int(time.time()) + 2592000

# =============================================
# DAX (DynamoDB Accelerator) β€” In-Memory Cache
# =============================================
# DAX = cache in-memory yang kompatibel dengan DynamoDB API
# Latency: DynamoDB ~5ms β†’ DAX ~microseconds!

# Buat DAX cluster via Console atau CLI
# Cocok untuk workload read-heavy yang butuh latency sangat rendah

# Python dengan DAX:
# pip install amazon-dax-client

"""
import amazondax
import boto3

# Tanpa DAX:
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Products')
response = table.get_item(Key={'product_id': 'PROD-001'})

# Dengan DAX:
dax_client = amazondax.AmazonDaxClient('dax://my-cluster.xxxx.dax-clusters.REGION.amazonaws.com')
table = dax_client.Table('Products')
response = table.get_item(Key={'product_id': 'PROD-001'})  # ~ΞΌs!
"""

9. Python SDK (boto3)

Python β€” boto3 DynamoDB
import boto3
from boto3.dynamodb.conditions import Key, Attr
from decimal import Decimal
import uuid
from datetime import datetime

# =============================================
# KONEKSI
# =============================================
# Production:
dynamodb = boto3.resource('dynamodb', region_name='ap-southeast-1')

# Local development:
# dynamodb = boto3.resource('dynamodb', endpoint_url='http://localhost:8000')

table = dynamodb.Table('Products')


# =============================================
# CREATE TABLE
# =============================================
def create_products_table():
    dynamodb.create_table(
        TableName='Products',
        KeySchema=[
            {'AttributeName': 'product_id', 'KeyType': 'HASH'},
        ],
        AttributeDefinitions=[
            {'AttributeName': 'product_id', 'AttributeType': 'S'},
        ],
        BillingMode='PAY_PER_REQUEST'
    )


# =============================================
# PUT ITEM
# =============================================
def add_product(name, price, category, stock):
    item = {
        'product_id': str(uuid.uuid4()),
        'name': name,
        'price': Decimal(str(price)),  # Harus Decimal, bukan float!
        'category': category,
        'stock': stock,
        'created_at': datetime.now().isoformat(),
    }
    response = table.put_item(Item=item)
    return response

add_product('Laptop ASUS', 15000000, 'Elektronik', 50)
add_product('Keyboard Mech', 850000, 'Aksesoris', 200)


# =============================================
# GET ITEM
# =============================================
def get_product(product_id):
    response = table.get_item(
        Key={'product_id': product_id},
        ProjectionExpression='#n, price, category',
        ExpressionAttributeNames={'#n': 'name'}
    )
    return response.get('Item')


# =============================================
# QUERY
# =============================================
# Query GSI
def get_products_by_category(category, min_price=None):
    params = {
        'IndexName': 'GSI-Category-Price',
        'KeyConditionExpression': Key('category').eq(category),
    }
    if min_price:
        params['KeyConditionExpression'] = (
            Key('category').eq(category) & Key('price').gte(min_price)
        )
    response = table.query(**params)
    return response['Items']


# =============================================
# SCAN dengan filter
# =============================================
def search_products(keyword):
    response = table.scan(
        FilterExpression=Attr('name').contains(keyword)
    )
    return response['Items']


# =============================================
# UPDATE
# =============================================
def update_stock(product_id, quantity):
    response = table.update_item(
        Key={'product_id': product_id},
        UpdateExpression='SET stock = stock - :qty',
        ExpressionAttributeValues={':qty': quantity},
        ConditionExpression='stock >= :qty',
        ReturnValues='UPDATED_NEW'
    )
    return response['Attributes']


# =============================================
# DELETE
# =============================================
def delete_product(product_id):
    table.delete_item(Key={'product_id': product_id})


# =============================================
# BATCH WRITE
# =============================================
def batch_write_products(items):
    with table.batch_writer() as batch:
        for item in items:
            item['product_id'] = str(uuid.uuid4())
            item['price'] = Decimal(str(item['price']))
            batch.put_item(Item=item)

# batch_write_products([
#     {'name': 'Mouse', 'price': 350000, 'category': 'Aksesoris', 'stock': 100},
#     {'name': 'Monitor', 'price': 3500000, 'category': 'Elektronik', 'stock': 30},
# ])


# =============================================
# PAGINATION
# =============================================
def get_all_products():
    items = []
    params = {}
    while True:
        response = table.scan(**params)
        items.extend(response['Items'])
        if 'LastEvaluatedKey' not in response:
            break
        params['ExclusiveStartKey'] = response['LastEvaluatedKey']
    return items

10. Best Practices & Anti-Patterns

Best Practices

Praktik Detail
Gunakan Query, bukan ScanSelalu filter dengan partition key
Design for access patternsRancang tabel berdasarkan query, bukan entitas
Distribusi partition keyHindari hot partition β€” pilih key yang merata
Gunakan GSI untuk query alternatifSatu GSI = satu access pattern tambahan
Batch reads/writesBatchWriteItem max 25 item, BatchGetItem max 100 item
Projection efficiencyAmbil hanya kolom yang dibutuhkan
TTL untuk data sementaraSession, cache, temporary data β€” otomatis dihapus

Anti-Patterns

Anti-Pattern Kenapa Buruk Solusi
Scan tanpa filterBaca semua data = mahal & lambatGunakan Query + partition key
Hot partitionSatu partition kebanjiran requestRandomize/shard partition key
Too many GSISetiap GSI = biaya write tambahanMaksimal 5 GSI, design dengan hati-hati
Large items (>400KB)Batas per item 400KBSimpan di S3, simpan pointer di DynamoDB
Float untuk uangPrecision errorSelalu gunakan Decimal

11. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut:

Pertanyaan 1: Apa perbedaan utama Query dan Scan di DynamoDB?

a) Query hanya untuk tabel kecil, Scan untuk tabel besar
b) Query menggunakan partition key (cepat), Scan membaca seluruh tabel (lambat)
c) Query hanya untuk GSI, Scan untuk tabel utama
d) Tidak ada perbedaan

Pertanyaan 2: Kapan GSI bisa dibuat?

a) Hanya saat membuat tabel
b) Kapan saja, termasuk setelah tabel ada
c) Hanya jika tabel kosong
d) Hanya dengan AWS Support

Pertanyaan 3: Mode kapasitas apa yang cocok untuk workload unpredictable?

a) Provisioned dengan Auto Scaling
b) On-Demand (Pay-per-request)
c) Provisioned tanpa Auto Scaling
d) Reserved Capacity

Pertanyaan 4: Berapa batas ukuran satu item di DynamoDB?

a) 64 KB
b) 400 KB
c) 1 MB
d) Tidak ada batas

Pertanyaan 5: Mengapa harus menggunakan Decimal, bukan float, untuk harga di Python boto3?

a) Float terlalu lambat untuk DynamoDB
b) DynamoDB tidak mendukung tipe float
c) Float punya precision error yang bisa merugikan perhitungan uang
d) Decimal lebih hemat storage
πŸ” Zoom
100%
🎨 Tema