DynamoDB Driver
AWS DynamoDB driver for Grove KV with TTL, Batch, CAS, and Transaction support.
The DynamoDB driver connects Grove KV to AWS DynamoDB using the AWS SDK for Go v2. It stores values as binary attributes and supports TTL via DynamoDB's native time-to-live feature.
Installation
go get github.com/xraph/grove/kv
go get github.com/xraph/grove/kv/drivers/dynamodriver
go get github.com/aws/aws-sdk-go-v2/configConnection
The DynamoDB driver requires a pre-configured *dynamodb.Client. The DSN passed to Open is used as the table name:
import (
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/xraph/grove/kv"
"github.com/xraph/grove/kv/drivers/dynamodriver"
)
// Load AWS config
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatal(err)
}
// Create the DynamoDB client
client := dynamodb.NewFromConfig(cfg)
// Create driver with options and open the store
// The DSN is the table name
store, err := kv.Open(
dynamodriver.New(client, dynamodriver.WithPKAttribute("pk")),
"my-kv-table",
)
if err != nil {
log.Fatal(err)
}
defer store.Close()Capabilities
| Capability | Supported |
|---|---|
| TTL | Yes (native DynamoDB TTL) |
| CAS (SetNX/SetXX) | Yes |
| Scan | No (use Query/ScanTable for advanced access) |
| Batch | Yes (BatchGetItem/BatchWriteItem) |
| PubSub | No |
| Transactions | Yes (TransactGet/TransactWrite) |
| Streams | No |
Table Schema Requirements
Your DynamoDB table must have the following attribute schema:
| Attribute | DynamoDB Type | Description |
|---|---|---|
pk | S (String) | Partition key -- stores the KV key |
val | B (Binary) | Stores the encoded value bytes |
ttl | N (Number) | Unix timestamp for DynamoDB TTL expiration |
The attribute names are configurable via options. Create your table with the partition key attribute:
aws dynamodb create-table \
--table-name my-kv-table \
--attribute-definitions AttributeName=pk,AttributeType=S \
--key-schema AttributeName=pk,KeyType=HASH \
--billing-mode PAY_PER_REQUESTEnable TTL on the table:
aws dynamodb update-time-to-live \
--table-name my-kv-table \
--time-to-live-specification "Enabled=true,AttributeName=ttl"TTL
DynamoDB TTL is handled natively. When you set a key with a TTL, the driver stores a Unix timestamp in the ttl attribute. DynamoDB automatically deletes expired items (typically within 48 hours of expiration). The driver also checks the TTL value on reads and returns kv.ErrNotFound for expired items immediately:
// Set with a 1-hour TTL
err := store.Set(ctx, "session:abc", data, 1*time.Hour)
// Check remaining TTL
remaining, err := store.TTL(ctx, "session:abc")
// Update TTL on an existing key
err = store.Expire(ctx, "session:abc", 2*time.Hour)Batch Operations
The driver supports MGet and MSet via DynamoDB's BatchGetItem and BatchWriteItem APIs. The driver automatically handles DynamoDB's per-request limits by chunking:
- BatchGetItem: max 100 keys per request
- BatchWriteItem: max 25 items per request
// Batch get
results := make(map[string]any)
err := store.MGet(ctx, []string{"key1", "key2", "key3"}, results)
// Batch set
err = store.MSet(ctx, map[string]any{
"key1": value1,
"key2": value2,
"key3": value3,
}, kv.WithTTL(1*time.Hour))Transactions
The driver provides DynamoDB's transactional APIs for atomic multi-item operations:
TransactGet
Read multiple items atomically:
ddb := dynamodriver.Unwrap(store)
items, err := ddb.TransactGet(ctx, []string{"account:1", "account:2"})
for _, item := range items {
// Process each item's attributes
}TransactWrite
Write, delete, update, or condition-check multiple items atomically:
ddb := dynamodriver.Unwrap(store)
err := ddb.TransactWrite(ctx, []dynamodriver.TransactWriteItem{
{
Put: &dynamodriver.TransactPut{
Item: map[string]types.AttributeValue{
"pk": &types.AttributeValueMemberS{Value: "order:123"},
"val": &types.AttributeValueMemberB{Value: orderBytes},
},
},
},
{
Delete: &dynamodriver.TransactDelete{
Key: map[string]types.AttributeValue{
"pk": &types.AttributeValueMemberS{Value: "cart:user1"},
},
},
},
{
ConditionCheck: &dynamodriver.TransactConditionCheck{
Key: map[string]types.AttributeValue{
"pk": &types.AttributeValueMemberS{Value: "inventory:sku-99"},
},
ConditionExpression: "quantity > :zero",
ExpressionValues: map[string]types.AttributeValue{
":zero": &types.AttributeValueMemberN{Value: "0"},
},
},
},
})Query and ScanTable
For access patterns that go beyond simple key-value lookups (e.g., querying GSIs or LSIs), use Query and ScanTable:
Query (GSI/LSI)
ddb := dynamodriver.Unwrap(store)
result, err := ddb.Query(ctx, dynamodriver.QueryInput{
IndexName: "gsi-status-created",
KeyConditionExpression: "#status = :status AND #created > :since",
ExpressionAttributeNames: map[string]string{
"#status": "status",
"#created": "created_at",
},
ExpressionValues: map[string]types.AttributeValue{
":status": &types.AttributeValueMemberS{Value: "active"},
":since": &types.AttributeValueMemberN{Value: "1700000000"},
},
Limit: aws.Int32(50),
})
for _, item := range result.Items {
// Process each item
}ScanTable
ddb := dynamodriver.Unwrap(store)
result, err := ddb.ScanTable(ctx, dynamodriver.ScanInput{
FilterExpression: "#type = :type",
ExpressionAttributeNames: map[string]string{
"#type": "entity_type",
},
ExpressionValues: map[string]types.AttributeValue{
":type": &types.AttributeValueMemberS{Value: "user"},
},
})UpdateBuilder
The UpdateBuilder provides a fluent API for building DynamoDB UpdateItem expressions:
ddb := dynamodriver.Unwrap(store)
err := ddb.Update("user:123").
Set("email", &types.AttributeValueMemberS{Value: "new@example.com"}).
Set("updated_at", &types.AttributeValueMemberN{Value: "1700000000"}).
Remove("temp_field").
Condition("attribute_exists(#a0)").
Exec(ctx)Options
Configure the driver when calling dynamodriver.New:
ddb := dynamodriver.New(client,
dynamodriver.WithPKAttribute("pk"),
dynamodriver.WithValueAttribute("val"),
dynamodriver.WithTTLAttribute("ttl"),
)| Option | Default | Description |
|---|---|---|
dynamodriver.WithPKAttribute(attr) | "pk" | Name of the partition key attribute |
dynamodriver.WithValueAttribute(attr) | "val" | Name of the attribute storing the value bytes |
dynamodriver.WithTTLAttribute(attr) | "ttl" | Name of the attribute storing the TTL Unix timestamp |
Unwrap for Native Client
Access the underlying DynamoDB client for operations not covered by the Grove KV interface:
// Get the DynamoDB driver
ddb := dynamodriver.Unwrap(store)
// Get the table name
tableName := ddb.Table()
// Get the AWS DynamoDB client
client := dynamodriver.UnwrapClient(store)