Optimized bulk export tool for moving large DynamoDB tables (millions of records) to Amazon Athena. Uses parallel scanning with batching for maximum throughput and minimal API overhead.
- Parallel Scanning: Splits table scan across multiple segments for high throughput
- Batch Processing: Groups records to reduce S3 API calls
- Type Conversion: Handles all DynamoDB data types (including Maps, Lists, Sets)
- Flexible Output: JSON or CSV format with optional gzip compression
- Athena Integration: Auto-generates DDL from sampled data
- Performance: Designed for tables with 4.5M+ records
| DynamoDB Type | Example | JSON/CSV Output |
|---|---|---|
| String (S) | {"name": {"S": "John"}} |
"name": "John" |
| Number (N) | {"age": {"N": "30"}} |
"age": 30 |
| Binary (B) | {"data": {"B": "base64..."}} |
"data": "base64..." |
| String Set (SS) | {"tags": {"SS": ["a", "b"]}} |
"tags": ["a", "b"] |
| Number Set (NS) | {"nums": {"NS": ["1", "2"]}} |
"nums": [1, 2] |
| Binary Set (BS) | {"files": {"BS": [...]}} |
"files": [...] |
| Map (M) | {"attrs": {"M": {"key": {"S": "val"}}}} |
"attrs": {"key": "val"} |
| List (L) | {"items": {"L": [{"S": "a"}]}} |
"items": ["a"] |
| Boolean (BOOL) | {"active": {"BOOL": true}} |
"active": true |
| Null (NULL) | {"deleted": {"NULL": true}} |
"deleted": true |
pip install boto3Copy config.yaml and update with your settings:
dynamodb:
table_name: "your-table-name"
region: "us-east-1"
s3:
bucket: "your-s3-bucket"
prefix: "dynamodb-export"
export:
format: "json" # or "csv"
batch_size: 500
segment_workers: 20
compression: true
athena:
database: "default"
table_name: "your-table-name"
sample_size: 1000# Edit the main() function in export_dynamodb.py with your config
python export_dynamodb.pyOr import and use programmatically:
from export_dynamodb import ExportConfig, export_dynamodb_to_s3
config = ExportConfig(
table_name="your-table-name",
s3_bucket="your-s3-bucket",
s3_prefix="dynamodb-export",
output_format="json",
region="us-east-1",
batch_size=500,
segment_workers=20,
compression=True
)
export_dynamodb_to_s3(config)# Update config in generate_athena_ddl.py main() function
python generate_athena_ddl.pyThis will:
- Sample records from S3
- Infer schema from data
- Generate CREATE TABLE DDL
- Save to
athena_ddl.sql
Execute the generated DDL in Athena console or using CLI:
aws athena start-query-execution \
--query-string "$(cat athena_ddl.sql)" \
--query-execution-context Database=default \
--result-configuration OutputLocation=s3://your-bucket/query-results/Key Parameters:
-
segment_workers: Number of parallel scan segments
- Start with 10-20
- Maximum: table's provisioned RCUs / 5
- More segments = faster but higher RCU cost
-
batch_size: Records per batch
- Range: 100-1000
- Larger batches reduce S3 API calls
- Too large = memory pressure
-
Compression: gzip compression
- Always enable for production
- Reduces S3 storage by ~60-80%
- Athena handles gzip natively
Based on 4.5M records:
| segment_workers | batch_size | Est. Time | RCU/s |
|---|---|---|---|
| 10 | 500 | ~30 min | 50 |
| 20 | 500 | ~15 min | 100 |
| 40 | 500 | ~8 min | 200 |
Pros:
- Preserves nested structures (Maps, Lists)
- Schema flexibility
- Better for complex data
Cons:
- Larger file size
- Slower to parse
Recommended: Tables with Maps, Lists, or variable schemas
Pros:
- Smaller file size
- Faster to parse
- Better for flat data
Cons:
- Nested structures serialized as JSON strings
- Requires consistent columns
Recommended: Tables with simple, flat schemas
After creating the table:
-- Basic query
SELECT * FROM your_table LIMIT 100;
-- Filter on nested fields (for JSON exports)
SELECT * FROM your_table
WHERE nested_field->>'key' = 'value';
-- Aggregate
SELECT COUNT(*) FROM your_table;Error: ProvisionedThroughputExceededException
Solutions:
- Reduce
segment_workers - Increase table's RCUs temporarily
- Use on-demand capacity mode
Error: MemoryError
Solutions:
- Reduce
batch_size - Reduce
segment_workers
Problem: Athena DDL doesn't match expected schema
Solutions:
- Increase
sample_sizein config - Manually edit generated DDL
- Run export with smaller sample to inspect data
- Read Units: ~0.5 RCU per record scanned
- 4.5M records: ~2.25M RCUs
- On-demand: ~$0.56 per million RCUs
- Estimated: ~$1.25
- Uncompressed: ~5-10 GB for 4.5M records
- Compressed (gzip): ~1-2 GB
- Cost: ~$0.023/GB/month
- Estimated: ~$0.05/month
- Data scanned: ~$5.00 per TB
- Typical queries: Minimal cost with filtering
- Python 3.7+
- boto3
- AWS credentials with permissions:
dynamodb:Scans3:PutObjects3:GetObjects3:ListBucket
MIT