DynamoDB Cache Extension
DynamoDB Cache Extension
The DynamoDB Cache Extension enables Lucee applications to use Amazon DynamoDB as a distributed cache provider and NoSQL data store. This powerful combination provides a highly scalable, fully managed solution with automatic table creation, TTL support, seamless integration with Lucee's cache functions, and direct DynamoDB operations via the DynamoDBCommand() function.
Overview
DynamoDB is Amazon's fully managed NoSQL database service that provides fast and predictable performance with seamless scalability. By using DynamoDB as a cache backend, your Lucee applications can benefit from:
- Dual access mode — use standard Lucee cache functions or native DynamoDB commands
- Distributed caching across multiple application servers
- Automatic scalability to handle varying loads
- Built-in replication for high availability
- Pay-per-use billing with on-demand capacity mode
- Automatic table creation — no manual setup required
- Dynamic Primary Key discovery — auto-detects existing table partition keys
- TTL support for automatic cache expiration
- Session and scope storage — offload session or client data to a durable cloud layer
- Maven-based dependency management (no OSGi complexity)
- AWS SDK v2 — built on the latest asynchronous-capable Java SDK
Requirements
- Lucee 7.0 or higher (requires Maven-based extension support)
- AWS Account with DynamoDB access (or DynamoDB Local for development/testing)
- AWS Credentials with appropriate DynamoDB permissions (not required when running on EC2/ECS with IAM roles)
Installation
Via Lucee Admin
- Navigate to Extension > Applications in your Lucee Administrator
- Search for "DynamoDB"
- Click Install on the DynamoDB Cache extension
- Restart Lucee if prompted
Via Application.cfc
Configure the cache at the application level in your Application.cfc:
this.name = "myApp";
this.caches = {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"storage": false,
"custom": {
"tableName": "my_cache_table",
"accessKeyId": "${AWS_ACCESS_KEY_ID}",
"secretkey": "${AWS_SECRET_ACCESS_KEY}",
"primaryKey": "pk",
"region": "us-east-1",
"liveTimeout": 3600000,
"log": "application"
},
"default": ""
}
};
Via .CFConfig.json
For server-wide configuration, add the cache definition to your .CFConfig.json file located at {lucee-server}/context/.CFConfig.json:
{
"caches": {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"custom": {
"tableName": "prod_cache",
"accessKeyId": "${AWS_ACCESS_KEY_ID}",
"secretkey": "${AWS_SECRET_ACCESS_KEY}",
"primaryKey": "pk",
"region": "us-east-1",
"liveTimeout": 5000,
"log": "dynamodb"
},
"readOnly": false,
"storage": false
}
}
}
Note: The ${VARIABLE_NAME} syntax allows you to reference environment variables, making it easy to keep credentials secure and environment-specific without hardcoding them in configuration files.
Configuration Method Comparison
| Method | Scope | Use Case |
|---|---|---|
| Lucee Admin | Server/Web | GUI-based configuration, quick setup |
| Application.cfc | Application | Application-specific cache, portable with code |
| .CFConfig.json | Server | Server-wide default, infrastructure-as-code, environment variables |
Configuration Options
Required Settings
| Parameter | Type | Description |
|---|---|---|
tableName |
String | The name of the DynamoDB table to use for caching. The table will be created automatically if it doesn't exist. |
Authentication Settings
| Parameter | Type | Required | Description |
|---|---|---|---|
accessKeyId |
String | No* | AWS Access Key ID for authentication. Leave empty when running on EC2/ECS with IAM roles. For DynamoDB Local testing, you can use any dummy value like 'dummy'. |
secretkey |
String | No* | AWS Secret Access Key for authentication. Leave empty when running on EC2/ECS with IAM roles. For DynamoDB Local testing, you can use any dummy value like 'dummy'. |
*Not required when running on EC2/ECS with IAM roles, but required for local development or external access.
Custom Endpoint Settings
| Parameter | Type | Required | Description |
|---|---|---|---|
region |
String | No | AWS region where the DynamoDB table is located (e.g., 'us-east-1', 'eu-west-1'). If not specified, the default region from your AWS configuration will be used. |
host |
String | No | Custom endpoint URL for DynamoDB. Use 'http://localhost:8000' for DynamoDB Local testing. Leave empty to use the standard AWS DynamoDB endpoints. |
Primary Key Settings
| Parameter | Type | Default | Description |
|---|---|---|---|
primaryKey |
String | "cacheKey" |
The name of the partition key attribute for the DynamoDB table. If the table already exists, the extension auto-detects the existing partition key name, so this setting is only used when creating a new table. |
General Settings
| Parameter | Type | Default | Description |
|---|---|---|---|
liveTimeout |
Number | 3600000 | Connection timeout in milliseconds for DynamoDB operations. |
log |
String | "application" |
Name of the log file where cache operations and errors will be recorded. Set to 'application' to use the default application log, or specify a custom log name (e.g., 'dynamodb-cache'). |
storage |
Boolean | false |
When set to true, allows this cache to be used as storage for Lucee's session or client scopes. See Session & Scope Storage. |
Configuration Examples
Production Configuration (AWS)
this.caches = {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"storage": false,
"custom": {
"tableName": "prod_cache",
"accessKeyId": getSystemSetting("AWS_ACCESS_KEY_ID"),
"secretkey": getSystemSetting("AWS_SECRET_ACCESS_KEY"),
"primaryKey": "pk",
"region": "us-east-1",
"liveTimeout": 5000,
"log": "dynamodb"
},
"default": ""
}
};
Development Configuration (DynamoDB Local)
this.caches = {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"storage": false,
"custom": {
"tableName": "dev_cache",
"accessKeyId": "dummy",
"secretkey": "dummy",
"primaryKey": "pk",
"region": "us-east-1",
"host": "http://localhost:8000",
"liveTimeout": 3600000,
"log": "application"
},
"default": ""
}
};
EC2/ECS Configuration (IAM Roles)
When running on EC2 or ECS with IAM roles, you don't need to provide credentials:
this.caches = {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"storage": false,
"custom": {
"tableName": "app_cache",
"region": "us-east-1",
"log": "dynamodb"
},
"default": ""
}
};
Access Models
The extension provides two ways to interact with your data, depending on your use case.
1. Lucee Cache Interface (High-Level)
This treats DynamoDB as a standard Lucee cache. It is ideal for query caching, session storage, or storing complex CFML objects. Once configured, you can use all standard Lucee cache functions with your DynamoDB cache.
2. Native Command API (Direct Access)
For advanced NoSQL patterns, the extension provides the DynamoDBCommand() function. This allows you to perform operations that the standard cache interface cannot express, such as atomic increments, complex queries, scans with filters, and direct item manipulation. See DynamoDBCommand() Reference for full details.
Usage — Lucee Cache Interface
Basic Operations
Store Data in Cache
// Simple string value
cachePut(id="user_session_123", value="active", cacheName="dynamo");
// Complex data structures
var userData = {
"name": "John Doe",
"email": "john@example.com",
"preferences": {
"theme": "dark",
"notifications": true
}
};
cachePut(id="user_profile_456", value=userData, cacheName="dynamo");
// With expiration (5 minutes)
cachePut(
id="temporary_token",
value="abc123xyz",
timeSpan=createTimeSpan(0,0,5,0),
cacheName="dynamo"
);
Retrieve Data from Cache
// Get value (throws error if not found)
var sessionData = cacheGet(id="user_session_123", cacheName="dynamo");
// Get value with default
var userData = cacheGet(
id="user_profile_456",
throwWhenNotExist=false,
cacheName="dynamo"
);
// Check if exists before getting
if(cacheIdExists(id="user_session_123", cacheName="dynamo")) {
var session = cacheGet(id="user_session_123", cacheName="dynamo");
}
Delete from Cache
// Delete single item
cacheDelete(id="user_session_123", cacheName="dynamo");
// Clear all items matching a pattern
cacheClear(filter="user_session_*", cacheName="dynamo");
// Clear entire cache
cacheClear(cacheName="dynamo");
Advanced Operations
Working with Multiple Items
// Get all cache IDs
var allIds = cacheGetAllIds(cacheName="dynamo");
// Get IDs matching a pattern
var userSessions = cacheGetAllIds(filter="user_session_*", cacheName="dynamo");
// Get all values matching a pattern
var allUserData = cacheGetAll(filter="user_*", cacheName="dynamo");
// Returns a struct: { "user_123": {...}, "user_456": {...}, ... }
// Count cache items
var totalItems = cacheCount(cacheName="dynamo");
Cache Metadata
// Get metadata for a cache entry
var metadata = cacheGetMetadata(id="user_profile_456", cacheName="dynamo");
// Metadata includes:
// - hitCount: number of times accessed
// - timeToLive: remaining time before expiration
// - lastAccessed: last access timestamp
// - lastModified: last modification timestamp
// - created: creation timestamp
Data Type Support
The DynamoDB cache extension supports all native Lucee data types:
// Strings
cachePut(id="key1", value="Simple string", cacheName="dynamo");
// Numbers
cachePut(id="key2", value=12345.67, cacheName="dynamo");
// Booleans
cachePut(id="key3", value=true, cacheName="dynamo");
// Dates
cachePut(id="key4", value=now(), cacheName="dynamo");
// Arrays
cachePut(id="key5", value=[1, "two", 3.0, true], cacheName="dynamo");
// Structs
cachePut(id="key6", value={
"name": "Product",
"price": 99.99,
"inStock": true,
"tags": ["featured", "sale"]
}, cacheName="dynamo");
// Queries
var qryData = queryNew("id,name", "integer,varchar", [[1,"Alice"],[2,"Bob"]]);
cachePut(id="key7", value=qryData, cacheName="dynamo");
DynamoDBCommand() Reference
The DynamoDBCommand() function provides direct access to DynamoDB operations, going beyond what the standard cache interface offers. It reuses the connection and table configuration from your cache definition.
Syntax
result = DynamoDBCommand(action, data [, cacheName]);
| Argument | Type | Required | Description |
|---|---|---|---|
action |
String | Yes | The DynamoDB operation to perform. See supported actions below. |
data |
Struct | Yes | The operation parameters (keys, expressions, values, etc.). |
cacheName |
String | No | The name of the DynamoDB cache connection to use. If omitted, the default cache is used. |
Supported Actions
putItem
Writes a complete item to the table. The struct you pass becomes the item; include the partition key attribute.
// Insert a new item
DynamoDBCommand("putItem", {
"pk": "user_001",
"name": "Urs",
"age": 30,
"active": true
}, "dynamo");
// With ReturnValues to get the previous item
previous = DynamoDBCommand("putItem", {
"pk": "user_001",
"name": "Urs",
"age": 31,
"ReturnValues": "ALL_OLD"
}, "dynamo");
getItem
Retrieves a single item by its primary key. Returns a struct, or null if the item doesn't exist.
// Get an item by primary key
user = DynamoDBCommand("getItem", {
"pk": "user_001"
}, "dynamo");
updateItem
Updates specific attributes of an existing item without replacing the entire item. Supports atomic operations like incrementing counters.
// Atomic increment — no read-modify-write cycle needed
result = DynamoDBCommand("updateItem", {
"key": { "pk": "user_001" },
"UpdateExpression": "SET age = age + :inc",
"ExpressionAttributeValues": { ":inc": 1 },
"ReturnValues": "UPDATED_NEW"
}, "dynamo");
// Update multiple attributes
result = DynamoDBCommand("updateItem", {
"key": { "pk": "user_001" },
"UpdateExpression": "SET #n = :name, active = :status",
"ExpressionAttributeNames": { "#n": "name" },
"ExpressionAttributeValues": { ":name": "Urs Updated", ":status": true },
"ReturnValues": "UPDATED_NEW"
}, "dynamo");
Supported parameters for updateItem:
| Parameter | Type | Description |
|---|---|---|
key |
Struct | The primary key of the item to update (required). |
UpdateExpression |
String | A DynamoDB update expression (e.g., "SET age = age + :inc"). |
ExpressionAttributeValues |
Struct | Placeholder values used in the expression. |
ExpressionAttributeNames |
Struct | Placeholder names for reserved words in the expression. |
ReturnValues |
String | What to return: "NONE", "UPDATED_OLD", "UPDATED_NEW", "ALL_OLD", "ALL_NEW". |
deleteItem
Deletes an item by its primary key. Optionally returns the deleted item.
// Simple delete
DynamoDBCommand("deleteItem", {
"pk": "user_001"
}, "dynamo");
// Delete and return the old item
deleted = DynamoDBCommand("deleteItem", {
"pk": "user_001",
"ReturnValues": "ALL_OLD"
}, "dynamo");
query
Retrieves items that match a key condition. Use this when you know the partition key and optionally want to filter by sort key. Returns an array of structs.
// Query by partition key
results = DynamoDBCommand("query", {
"KeyConditionExpression": "pk = :userId",
"ExpressionAttributeValues": { ":userId": "user_001" }
}, "dynamo");
// Query with filter expression
results = DynamoDBCommand("query", {
"KeyConditionExpression": "pk = :userId",
"FilterExpression": "age > :minAge",
"ExpressionAttributeValues": {
":userId": "user_001",
":minAge": 18
}
}, "dynamo");
// Query a Global Secondary Index
results = DynamoDBCommand("query", {
"IndexName": "email-index",
"KeyConditionExpression": "email = :email",
"ExpressionAttributeValues": { ":email": "urs@example.com" }
}, "dynamo");
Supported parameters for query:
| Parameter | Type | Description |
|---|---|---|
KeyConditionExpression |
String | The key condition (required). |
ExpressionAttributeValues |
Struct | Placeholder values used in expressions. |
FilterExpression |
String | Additional server-side filtering applied after the key condition. |
IndexName |
String | Name of a Global or Local Secondary Index to query. |
scan
Reads every item in the table (or index), optionally applying a filter. Returns an array of structs. Use sparingly on large tables.
// Scan entire table
allItems = DynamoDBCommand("scan", {}, "dynamo");
// Scan with filter
activeUsers = DynamoDBCommand("scan", {
"FilterExpression": "active = :status",
"ExpressionAttributeValues": { ":status": true }
}, "dynamo");
// Scan with limit
sample = DynamoDBCommand("scan", {
"Limit": 10
}, "dynamo");
// Scan a Global Secondary Index
results = DynamoDBCommand("scan", {
"IndexName": "status-index",
"FilterExpression": "age > :minAge",
"ExpressionAttributeValues": { ":minAge": 25 }
}, "dynamo");
Supported parameters for scan:
| Parameter | Type | Description |
|---|---|---|
FilterExpression |
String | Server-side filter applied to results. |
ExpressionAttributeValues |
Struct | Placeholder values used in the filter. |
IndexName |
String | Name of a Global or Local Secondary Index to scan. |
Limit |
Number | Maximum number of items to evaluate (before filtering). |
Time-To-Live (TTL) and Expiration
The DynamoDB extension fully supports cache expiration using DynamoDB's native TTL feature.
Setting Expiration
// Expire after 1 hour
cachePut(
id="session_token",
value="xyz789",
timeSpan=createTimeSpan(0,1,0,0),
cacheName="dynamo"
);
// Expire at specific time
var expirationDate = dateAdd("h", 2, now());
cachePut(
id="scheduled_job",
value=jobData,
until=expirationDate,
cacheName="dynamo"
);
// Idle timeout (expires if not accessed)
cachePut(
id="idle_session",
value=sessionData,
idleTime=1800000, // 30 minutes in milliseconds
cacheName="dynamo"
);
TTL Behavior
When using cachePut() with a timeSpan, the extension automatically calculates the epoch timestamp and stores it. If TTL is enabled on your DynamoDB table (on the attribute named timeToLive), AWS will automatically expire items at no extra cost.
- TTL is automatically enabled when the table is created by the extension
- Expired items are automatically deleted by DynamoDB (typically within 48 hours of expiration)
- The cache checks for expiration on read operations
- Expired items won't be returned by cache operations even if not yet deleted by DynamoDB
Primary Key Discovery
The extension is designed to be "plug-and-play" with existing DynamoDB tables. During initialization, it describes the target table to discover the partition key name:
- If the table exists, the extension adopts the existing partition key name automatically — no configuration needed.
- If the table does not exist, the extension creates one using the
primaryKeysetting from your configuration (defaulting tocacheKey).
This means you can point the extension at any existing DynamoDB table regardless of its key schema, and both the cache interface and DynamoDBCommand() will work correctly.
Session & Scope Storage
DynamoDB can be used as a durable, distributed storage backend for Lucee's session and client scopes. This is especially useful in clustered environments where sessions need to be shared across multiple application servers.
Configuration
To enable scope storage, set storage to true in your cache definition and then reference the cache name in your Application.cfc:
component {
this.name = "myApp";
this.caches["dynamo"] = {
class: "org.lucee.extension.aws.dynamodb.DynamoDBCache",
maven: "org.lucee:dynamodb:1.0.0.7-RC",
storage: true,
custom: {
"tableName": "session-table",
"region": "us-east-1"
}
};
// Use DynamoDB for session storage
this.sessionStorage = "dynamo";
// Or for client storage
// this.clientStorage = "dynamo";
}
Use Cases
API Rate Limiting
function checkRateLimit(apiKey, maxRequests=100, timeWindow=3600) {
var cacheKey = "ratelimit_" & hash(apiKey);
var requests = cacheGet(id=cacheKey, throwWhenNotExist=false, cacheName="dynamo");
if(isNull(requests)) {
cachePut(
id=cacheKey,
value=1,
timeSpan=createTimeSpan(0,0,0,timeWindow),
cacheName="dynamo"
);
return true;
}
if(requests >= maxRequests) {
return false;
}
cachePut(
id=cacheKey,
value=requests + 1,
timeSpan=createTimeSpan(0,0,0,timeWindow),
cacheName="dynamo"
);
return true;
}
Query Result Caching
function getCachedQueryResults(sql, cacheMinutes=15) {
var cacheKey = "query_" & hash(sql);
var results = cacheGet(
id=cacheKey,
throwWhenNotExist=false,
cacheName="dynamo"
);
if(!isNull(results)) {
return results;
}
results = queryExecute(sql);
cachePut(
id=cacheKey,
value=results,
timeSpan=createTimeSpan(0,0,cacheMinutes,0),
cacheName="dynamo"
);
return results;
}
Atomic Counters with DynamoDBCommand
// Increment a page view counter atomically — no read-modify-write needed
function incrementPageView(pageId) {
return DynamoDBCommand("updateItem", {
"key": { "pk": "pageviews_" & pageId },
"UpdateExpression": "SET hits = if_not_exists(hits, :zero) + :inc",
"ExpressionAttributeValues": { ":inc": 1, ":zero": 0 },
"ReturnValues": "UPDATED_NEW"
}, "dynamo");
}
Distributed Lock Implementation
function acquireLock(resourceName, timeout=30) {
var lockKey = "lock_" & resourceName;
var lockValue = createUUID();
try {
cachePut(
id=lockKey,
value=lockValue,
timeSpan=createTimeSpan(0,0,0,timeout),
cacheName="dynamo"
);
return lockValue;
} catch(any e) {
return null;
}
}
function releaseLock(resourceName, lockValue) {
var lockKey = "lock_" & resourceName;
var currentValue = cacheGet(
id=lockKey,
throwWhenNotExist=false,
cacheName="dynamo"
);
if(!isNull(currentValue) && currentValue == lockValue) {
cacheDelete(id=lockKey, cacheName="dynamo");
return true;
}
return false;
}
Local Development with DynamoDB Local
For local development and testing, you can use DynamoDB Local instead of AWS DynamoDB.
Installing DynamoDB Local
Using Docker
docker run -p 8000:8000 amazon/dynamodb-local:latest
Using Java (Manual Installation)
-
Download DynamoDB Local from AWS Developer Guide
-
Extract and run:
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
Local Configuration
this.caches = {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"storage": false,
"custom": {
"tableName": "local_cache",
"accessKeyId": "dummy",
"secretkey": "dummy",
"region": "us-east-1",
"host": "http://localhost:8000",
"liveTimeout": 3600000,
"log": "application"
}
}
};
Environment-Based Configuration
// Detect environment
var isDevelopment = (server.coldfusion.productname contains "Lucee") &&
(cgi.server_name contains "localhost");
var cacheConfig = {
"tableName": isDevelopment ? "dev_cache" : "prod_cache",
"region": "us-east-1",
"log": "dynamodb"
};
if(isDevelopment) {
cacheConfig.host = "http://localhost:8000";
cacheConfig.accessKeyId = "dummy";
cacheConfig.secretkey = "dummy";
} else {
if(len(getSystemSetting("AWS_ACCESS_KEY_ID", ""))) {
cacheConfig.accessKeyId = getSystemSetting("AWS_ACCESS_KEY_ID");
cacheConfig.secretkey = getSystemSetting("AWS_SECRET_ACCESS_KEY");
}
}
this.caches = {
"dynamo": {
"class": "org.lucee.extension.aws.dynamodb.DynamoDBCache",
"maven": "org.lucee:dynamodb:1.0.0.7-RC",
"storage": false,
"custom": cacheConfig
}
};
Performance Considerations
Best Practices
- Use Appropriate Key Names: Create meaningful, namespaced keys to avoid collisions
// Good
cachePut(id="user:123:profile", value=data, cacheName="dynamo");
// Less ideal
cachePut(id="123", value=data, cacheName="dynamo");
- Implement Cache Warming: Pre-populate frequently accessed data
function warmCache() {
var criticalData = queryExecute("SELECT * FROM frequently_accessed");
loop query=criticalData {
cachePut(
id="warm_" & criticalData.id,
value=criticalData,
cacheName="dynamo"
);
}
}
- Use Batch Operations: Leverage
cacheGetAll()for multiple items
// Efficient: single query
var userData = cacheGetAll(filter="user_*", cacheName="dynamo");
// Less efficient: multiple queries
for(var id in userIDs) {
var data = cacheGet(id="user_" & id, cacheName="dynamo");
}
- Set Appropriate TTL: Balance between freshness and performance
// Frequently changing data: short TTL
cachePut(id="stock_price", value=price, timeSpan=createTimeSpan(0,0,1,0), cacheName="dynamo");
// Rarely changing data: longer TTL
cachePut(id="config_settings", value=config, timeSpan=createTimeSpan(1,0,0,0), cacheName="dynamo");
- Prefer
queryoverscan: When usingDynamoDBCommand(), usequerywith a key condition whenever possible.scanreads the entire table and should be used sparingly on large tables.
Monitoring and Optimization
- Enable Logging: Monitor cache operations
custom: {
"log": "dynamodb",
// ... other settings
}
- Check Cache Metrics: Use
cacheGetMetadata()to analyze usage patterns
var metadata = cacheGetMetadata(id="popular_item", cacheName="dynamo");
if(metadata.hitCount > 1000) {
// Consider longer TTL for popular items
}
- Monitor DynamoDB Metrics: Use AWS CloudWatch to track read/write capacity usage, throttled requests, item count, table size, and average item size.
Security Best Practices
Credentials Management
- Use IAM Roles when running on AWS infrastructure
- Use Environment Variables for credentials in non-AWS environments
- Never hardcode credentials in your application code
- Rotate credentials regularly
Access Control
- Use least privilege principle for DynamoDB permissions
- Enable encryption at rest in DynamoDB table settings
- Use VPC endpoints for private network access
- Enable CloudTrail logging for audit trails
Recommended IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:GetItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:DescribeTable",
"dynamodb:DescribeTimeToLive"
],
"Resource": "arn:aws:dynamodb:*:*:table/your-cache-table-name"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:CreateTable",
"dynamodb:UpdateTimeToLive"
],
"Resource": "arn:aws:dynamodb:*:*:table/your-cache-table-name",
"Condition": {
"StringLike": {
"dynamodb:tableName": "your-cache-table-name"
}
}
}
]
}
Troubleshooting
Common Issues
Connection Errors
Error: Unable to connect to DynamoDB
Solution: Verify your credentials and region settings. Check if the host parameter is set correctly for DynamoDB Local.
Table Not Found
Error: ResourceNotFoundException: Cannot do operations on a non-existent table
Solution: The extension automatically creates tables. If this error occurs, verify the IAM permissions include CreateTable action.
400 Bad Request (Required keys missing)
Solution: When using DynamoDBCommand(), ensure the keys you pass match the case and name of the partition key in your table. The extension auto-detects the key name, but the data you pass must use the correct attribute name.
Throttling
Error: ProvisionedThroughputExceededException
Solution: For production tables, consider switching to on-demand billing mode or increasing provisioned capacity.
Invalid Security Token
Error: The security token included in the request is invalid
Solution: For AWS, verify your credentials are correct and not expired. For DynamoDB Local, ensure the host parameter is set to http://localhost:8000.
Debug Logging
Enable detailed logging to troubleshoot issues:
custom: {
"log": "dynamodb-debug",
// ... other settings
}
Then check your Lucee logs at: [lucee-config]/logs/dynamodb-debug.log
Migration from Other Cache Providers
From EhCache
// Old EhCache configuration
this.cache.connections["ehcache"] = {
class: "lucee.runtime.cache.eh.EHCache",
storage: false
};
// New DynamoDB configuration
this.cache.connections["dynamo"] = {
class: "org.lucee.extension.aws.dynamodb.DynamoDBCache",
maven: "org.lucee:dynamodb:1.0.0.7-RC",
storage: false,
custom: {
"tableName": "app_cache",
"region": "us-east-1"
}
};
// Application code remains the same — just update the cacheName
cachePut(id="key", value="value", cacheName="dynamo");
From Redis
The API is identical since both implement the Lucee cache interface. Simply change the cache configuration.
Comparison with Other Cache Providers
| Feature | DynamoDB | Redis | EhCache |
|---|---|---|---|
| Distributed | ✅ Yes | ✅ Yes | ❌ No |
| Managed Service | ✅ Yes | ⚠️ Optional | ❌ No |
| Auto-scaling | ✅ Yes | ❌ No | ❌ No |
| TTL Support | ✅ Native | ✅ Native | ✅ Yes |
| Native NoSQL Commands | ✅ Yes | ⚠️ Limited | ❌ No |
| Cost | 💰 Usage-based | 💰 Instance-based | ✅ Free |
| Setup Complexity | ✅ Minimal | ⚠️ Moderate | ✅ Minimal |
Maven-Based Extension Benefits
Unlike older OSGi-based extensions, the DynamoDB extension leverages Lucee 7's Maven support, providing:
- Automatic dependency resolution: No need to bundle all JAR files
- Easier updates: Maven handles dependency versioning
- Smaller extension size: Dependencies downloaded on-demand
- Better compatibility: Reduced version conflicts
- Simplified development: Standard Maven project structure
Additional Resources
- AWS DynamoDB Documentation
- DynamoDB Best Practices
- Lucee Cache Functions
- DynamoDB Local Documentation
Support and Contribution
- GitHub: lucee/extension-dynamodb
- Issues: Report bugs or request features on Jira
- Community: Join the Lucee community forums for support
- Extensions: Visit the Lucee Download page for updates