Skip to main content

Overview

The MongoDB persistence adapter provides distributed locks, circuit breaker state management, caching, and checkpoint support using MongoDB as the backend. It leverages MongoDB’s native TTL indexes for automatic expiration and unique indexes for lock acquisition.

Installation

npm install @go-go-scope/persistence-mongodb mongodb

Features

  • Distributed Locks: TTL-based locks with automatic expiration via MongoDB TTL indexes
  • Circuit Breaker State: Persistent failure tracking with document storage
  • Caching: High-performance cache with native TTL support
  • Checkpoints: Full checkpoint support for task recovery
  • Idempotency: Prevent duplicate operations (via MongoDBIdempotencyAdapter)
  • Auto Indexing: Automatic index creation on connect
  • No Manual Cleanup: MongoDB TTL indexes handle expiration automatically

Basic Usage

import { MongoClient } from 'mongodb'
import { MongoDBAdapter } from '@go-go-scope/persistence-mongodb'
import { scope } from 'go-go-scope'

const client = new MongoClient(process.env.MONGODB_URL)
await client.connect()

const db = client.db('myapp')
const persistence = new MongoDBAdapter(db, { keyPrefix: 'myapp:' })

// Initialize indexes
await persistence.connect()

await using s = scope({ persistence })

Configuration

db
Db
required
MongoDB database instance
options
PersistenceAdapterOptions
Configuration options

Collections and Indexes

The adapter automatically creates the following collections and indexes on connect():

go_goscope_locks

  • Unique Index: { key: 1 }
  • TTL Index: { expiresAt: 1 } with expireAfterSeconds: 0

go_goscope_circuit

  • Unique Index: { key: 1 }

go_goscope_cache

  • Unique Index: { key: 1 }
  • TTL Index: { expiresAt: 1 } with expireAfterSeconds: 0

go_goscope_checkpoints

  • Unique Index: { taskId: 1, sequence: 1 }
  • Index: { taskId: 1 }

Lock Provider Methods

acquire(key, ttl, owner?)

Acquires a distributed lock using MongoDB’s unique index constraint. Automatically expires via TTL index.
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 (duplicate key error)

extend(key, ttl, owner)

Extends the TTL of an existing lock by updating the expiresAt field.
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. Automatically checks expiration (MongoDB 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 (stored as-is, no JSON serialization required)
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 in the cache.
key
string
required
Cache key
Returns: Promise<boolean>

clear()

Clears all cached values (respects key prefix if set).

keys(pattern?)

Lists all cache keys matching an optional pattern.
pattern
string
Optional regex pattern (e.g., 'user:.*')
Returns: Promise<string[]> - Array of matching keys

Circuit Breaker Methods

getState(key)

Retrieves circuit breaker state.
key
string
required
Circuit breaker identifier
Returns: Promise<CircuitBreakerPersistedState | null>

setState(key, state)

Updates circuit breaker state.
key
string
required
Circuit breaker identifier
state
CircuitBreakerPersistedState
required
New state (state, failureCount, lastFailureTime, lastSuccessTime)

recordFailure(key, maxFailures)

Records a failure and potentially opens the circuit.
key
string
required
Circuit breaker identifier
maxFailures
number
required
Maximum failures before opening circuit
Returns: Promise<number> - Current failure count

recordSuccess(key)

Records a success and closes the circuit.
key
string
required
Circuit breaker identifier

Connection Example

import { MongoClient } from 'mongodb'
import { MongoDBAdapter } from '@go-go-scope/persistence-mongodb'

const client = new MongoClient(process.env.MONGODB_URL, {
  maxPoolSize: 10,
  minPoolSize: 2,
})

await client.connect()

const db = client.db('myapp')
const persistence = new MongoDBAdapter(db, {
  keyPrefix: 'prod:',
})

// Initialize indexes
await persistence.connect()

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

// Cleanup
await persistence.disconnect()
await client.close()

MongoDB Atlas Example

import { MongoClient } from 'mongodb'
import { MongoDBAdapter } from '@go-go-scope/persistence-mongodb'

const uri = `mongodb+srv://${process.env.ATLAS_USER}:${process.env.ATLAS_PASSWORD}@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority`

const client = new MongoClient(uri)
await client.connect()

const db = client.db('production')
const persistence = new MongoDBAdapter(db)

await persistence.connect()

await using s = scope({ persistence })

Best Practices

Call connect() once at application startup to create indexes. The adapter uses createIndex() which is idempotent.
MongoDB TTL indexes are eventually consistent with a background process that runs every 60 seconds. For strict expiration guarantees, the adapter also checks expiration client-side.
Lock acquisition uses MongoDB’s unique index constraint for atomic operations. Duplicate key errors (code 11000) indicate the lock is already held.

Performance Considerations

  • TTL indexes automatically clean up expired documents
  • Unique indexes provide atomic lock acquisition
  • Connection pooling is handled by the MongoClient
  • Use compound indexes for checkpoint queries on large datasets
  • Consider sharding for very high lock contention scenarios

Document Structure Examples

Lock Document

{
  "key": "myapp:lock:resource:123",
  "owner": "1709482836123-abc123",
  "expiresAt": ISODate("2024-03-03T12:34:56.000Z"),
  "createdAt": ISODate("2024-03-03T12:34:26.000Z")
}

Circuit Breaker Document

{
  "key": "myapp:circuit:external-api",
  "state": "open",
  "failureCount": 5,
  "lastFailureTime": ISODate("2024-03-03T12:34:56.000Z"),
  "lastSuccessTime": ISODate("2024-03-03T12:30:00.000Z"),
  "updatedAt": ISODate("2024-03-03T12:34:56.000Z")
}

Cache Document

{
  "key": "myapp:cache:user:123",
  "value": { "name": "Alice", "role": "admin" },
  "expiresAt": ISODate("2024-03-03T12:39:56.000Z"),
  "updatedAt": ISODate("2024-03-03T12:34:56.000Z")
}

Redis Adapter

Redis-based persistence

DynamoDB Adapter

DynamoDB-based persistence

Build docs developers (and LLMs) love