Skip to main content

Overview

The DynamoDB persistence adapter provides distributed locks, circuit breaker state management, caching, and checkpoint support using AWS DynamoDB as the backend. It uses a single-table design with native TTL support for automatic expiration.

Installation

npm install @go-go-scope/persistence-dynamodb @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

Features

  • Distributed Locks: Atomic locks using conditional writes
  • Circuit Breaker State: Persistent failure tracking
  • Caching: Serverless cache with DynamoDB TTL
  • Checkpoints: Full checkpoint support for task recovery
  • Idempotency: Prevent duplicate operations (via DynamoDBIdempotencyAdapter)
  • Single-Table Design: All data in one table with partition/sort keys
  • Native TTL: Automatic expiration via DynamoDB TTL attribute
  • Serverless: No infrastructure to manage

Basic Usage

import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
import { DynamoDBAdapter } from '@go-go-scope/persistence-dynamodb'
import { scope } from 'go-go-scope'

const client = DynamoDBDocumentClient.from(new DynamoDBClient({}))
const persistence = new DynamoDBAdapter(client, 'go-goscope-prod', {
  keyPrefix: 'myapp:',
})

await using s = scope({ persistence })

Configuration

client
DynamoDBDocumentClient
required
DynamoDB Document Client from @aws-sdk/lib-dynamodb
tableName
string
required
Name of the DynamoDB table to use (table must be pre-created)
options
PersistenceAdapterOptions
Configuration options

Table Schema (Single-Table Design)

You must create the DynamoDB table with the following schema:
{
  TableName: 'go-goscope-prod',
  KeySchema: [
    { AttributeName: 'pk', KeyType: 'HASH' },  // Partition key
    { AttributeName: 'sk', KeyType: 'RANGE' }  // Sort key
  ],
  AttributeDefinitions: [
    { AttributeName: 'pk', AttributeType: 'S' },
    { AttributeName: 'sk', AttributeType: 'S' }
  ],
  TimeToLiveSpecification: {
    Enabled: true,
    AttributeName: 'expiresAt'
  },
  BillingMode: 'PAY_PER_REQUEST' // or PROVISIONED
}

Key Patterns

  • Locks: pk = LOCK#{prefix}lock:{key}, sk = METADATA
  • Circuit Breaker: pk = CB#{prefix}circuit:{key}, sk = METADATA
  • Cache: pk = CACHE#{prefix}cache:{key}, sk = METADATA
  • Checkpoints: pk = CHECKPOINT#{prefix}{taskId}:{sequence}, sk = METADATA

Optional Global Secondary Index

For efficient keys() queries on cache items, create a GSI:
{
  IndexName: 'sk-pk-index',
  KeySchema: [
    { AttributeName: 'sk', KeyType: 'HASH' },
    { AttributeName: 'pk', KeyType: 'RANGE' }
  ],
  Projection: { ProjectionType: 'ALL' },
  BillingMode: 'PAY_PER_REQUEST'
}

Lock Provider Methods

acquire(key, ttl, owner?)

Acquires a distributed lock using DynamoDB conditional writes. Automatically expires via TTL.
key
string
required
Lock identifier
ttl
number
required
Time-to-live in milliseconds
owner
string
Optional lock owner identifier (auto-generated if not provided)
Returns: Promise<LockHandle | null> - Lock handle or null if already locked (ConditionalCheckFailedException)

extend(key, ttl, owner)

Extends the TTL of an existing lock.
key
string
required
Lock identifier
ttl
number
required
New time-to-live in milliseconds
owner
string
required
Lock owner identifier
Returns: Promise<boolean> - True if extended successfully

forceRelease(key)

Forces release of a lock regardless of owner.
key
string
required
Lock identifier

Cache Provider Methods

get(key)

Retrieves a cached value. Checks expiration client-side (DynamoDB TTL is eventually consistent).
key
string
required
Cache key
Returns: Promise<T | null> - Cached value or null if not found/expired

set(key, value, ttl?)

Stores a value in the cache.
key
string
required
Cache key
value
T
required
Value to cache (DynamoDB supports complex objects)
ttl
number
Time-to-live in milliseconds (optional)

delete(key)

Removes a cached value.
key
string
required
Cache key

has(key)

Checks if a key exists and is not expired.
key
string
required
Cache key
Returns: Promise<boolean>

clear()

Clears all cached values (scans and deletes in batches).

keys(pattern?)

Lists all cache keys matching an optional pattern (requires GSI).
pattern
string
Optional regex pattern
Returns: Promise<string[]> - Array of matching keys

Checkpoint Provider Methods

save(checkpoint)

Saves a checkpoint for task recovery.
checkpoint
Checkpoint<T>
required
Checkpoint data including taskId, sequence, progress, and data

load(checkpointId)

Loads a specific checkpoint by ID (uses Scan).
checkpointId
string
required
Checkpoint identifier
Returns: Promise<Checkpoint<T> | undefined>

loadLatest(taskId)

Loads the most recent checkpoint for a task (requires GSI).
taskId
string
required
Task identifier
Returns: Promise<Checkpoint<T> | undefined>

list(taskId)

Lists all checkpoints for a task (requires GSI).
taskId
string
required
Task identifier
Returns: Promise<Checkpoint<unknown>[]>

cleanup(taskId, keepCount)

Deletes old checkpoints, keeping only the most recent N.
taskId
string
required
Task identifier
keepCount
number
required
Number of checkpoints to keep

deleteAll(taskId)

Deletes all checkpoints for a task.
taskId
string
required
Task identifier

CloudFormation Template

Resources:
  GoGoScopeTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: go-goscope-prod
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
        - AttributeName: sk
          AttributeType: S
      KeySchema:
        - AttributeName: pk
          KeyType: HASH
        - AttributeName: sk
          KeyType: RANGE
      TimeToLiveSpecification:
        Enabled: true
        AttributeName: expiresAt
      GlobalSecondaryIndexes:
        - IndexName: sk-pk-index
          KeySchema:
            - AttributeName: sk
              KeyType: HASH
            - AttributeName: pk
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
      Tags:
        - Key: Application
          Value: go-go-scope

Connection Example

import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
import { DynamoDBAdapter } from '@go-go-scope/persistence-dynamodb'

const client = DynamoDBDocumentClient.from(
  new DynamoDBClient({
    region: process.env.AWS_REGION || 'us-east-1',
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    },
  })
)

const persistence = new DynamoDBAdapter(client, 'go-goscope-prod', {
  keyPrefix: 'myapp:',
})

// Use with scope
await using s = scope({ persistence })

Best Practices

Use on-demand billing mode for unpredictable workloads. Switch to provisioned capacity with auto-scaling for predictable high-throughput scenarios.
DynamoDB TTL deletion is eventually consistent and may take up to 48 hours. The adapter checks expiration client-side for immediate consistency.
Lock acquisition uses conditional writes (attribute_not_exists(pk) OR expiresAt < :now) which are atomic and strongly consistent.

Performance Considerations

  • Single-table design minimizes RCU/WCU consumption
  • TTL automatically deletes expired items (no cost)
  • Use on-demand billing for variable workloads
  • Consider provisioned capacity + auto-scaling for sustained high throughput
  • GSI required for keys() and checkpoint queries
  • Batch operations for checkpoint cleanup

Cost Optimization

  • Use TTL for automatic cleanup (no write cost)
  • Enable point-in-time recovery for production
  • Use DynamoDB Streams for audit logging (optional)
  • Monitor with CloudWatch metrics
  • Consider DynamoDB Global Tables for multi-region HA

Item Structure Examples

Lock Item

{
  "pk": "LOCK#myapp:lock:resource:123",
  "sk": "METADATA",
  "owner": "1709482836123-abc123",
  "expiresAt": 1709482866,
  "data": { "acquiredAt": 1709482836123 },
  "version": 1
}

Circuit Breaker Item

{
  "pk": "CB#myapp:circuit:external-api",
  "sk": "METADATA",
  "data": {
    "state": "open",
    "failureCount": 5,
    "lastFailureTime": 1709482836123,
    "lastSuccessTime": 1709482536123
  },
  "expiresAt": 1709486436,
  "version": 1
}

Cache Item

{
  "pk": "CACHE#myapp:cache:user:123",
  "sk": "METADATA",
  "data": { "name": "Alice", "role": "admin" },
  "expiresAt": 1709483136,
  "version": 1
}

MongoDB Adapter

MongoDB-based persistence

Redis Adapter

Redis-based persistence

Build docs developers (and LLMs) love