Operational State
Mammoth stores operational memory in SQLite.
SQLite is used for:
- schema migration state
- checkpoints
- dead letters
- delivered-envelope ledger entries
Why SQLite?
Mammoth needs durable local state even when running as a small self-hosted service. SQLite provides an inspectable operational database without requiring another service dependency.
Tables
schema_migrations
Tracks applied operational database migrations.
CREATE TABLE schema_migrations (
version TEXT PRIMARY KEY,
applied_at TEXT NOT NULL
);
checkpoints
Stores source progress.
CREATE TABLE checkpoints (
id INTEGER PRIMARY KEY,
source_name TEXT NOT NULL,
slot_name TEXT NOT NULL,
publication_name TEXT NOT NULL,
last_lsn TEXT,
updated_at TEXT NOT NULL,
UNIQUE (source_name, slot_name)
);
The checkpoint table answers:
Through which source position does Mammoth have contiguous durable outcomes?
The shared progress coordinator writes this row only after every earlier work
item has a durable outcome. For PostgreSQL sources, Mammoth persists the
checkpoint before acknowledging the same watermark through pgoutput-client.
Individual destination workers never advance this table independently.
The stored PostgreSQL last_lsn is the transport watermark in standard
HEX/HEX form, not the decoder's normalized transaction commit_lsn.
dead_letters
Stores failed deliveries after retry exhaustion.
CREATE TABLE dead_letters (
id INTEGER PRIMARY KEY,
event_id TEXT NOT NULL,
source_name TEXT NOT NULL,
destination_name TEXT NOT NULL,
operation TEXT NOT NULL,
namespace TEXT,
entity TEXT,
source_position TEXT,
payload_json TEXT NOT NULL,
error_class TEXT,
error_message TEXT,
retry_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'pending',
failed_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
CHECK (status IN ('pending', 'resolved', 'ignored')),
CHECK (retry_count >= 0)
);
The dead-letter table answers:
What failed, where was it going, and why did it fail?
payload_json is the exact payload prepared for the destination. When a
payload policy is active, removed source values are not restored in this
operational store. Replay sends this stored JSON unchanged rather than applying
the current policy again.
delivered_envelopes
Stores successfully delivered event or transaction idempotency keys.
CREATE TABLE delivered_envelopes (
id INTEGER PRIMARY KEY,
idempotency_key TEXT NOT NULL,
source_name TEXT NOT NULL,
slot_name TEXT NOT NULL,
destination_name TEXT NOT NULL,
delivery_unit TEXT NOT NULL,
transaction_id TEXT,
source_position TEXT,
delivered_at TEXT NOT NULL,
UNIQUE (idempotency_key)
);
The delivered-envelope table answers:
Has this event or transaction already been delivered to this destination?
Mammoth uses this ledger to skip duplicate downstream delivery when upstream replication replays already-delivered work after a restart.
Inspect with sqlite3
Install SQLite locally if needed:
sudo apt update
sudo apt install sqlite3
List tables:
sqlite3 data/mammoth.db ".tables"
Show schema:
sqlite3 data/mammoth.db ".schema"
Inspect dead letters:
sqlite3 data/mammoth.db \
"SELECT event_id, destination_name, operation, namespace, entity, retry_count, status, error_class, error_message FROM dead_letters;"
Inspect and replay dead letters with Mammoth:
mammoth dead-letters list config/mammoth.yml
mammoth dead-letters show config/mammoth.yml 12
mammoth dead-letters replay config/mammoth.yml 12
Inspect delivered envelopes:
sqlite3 data/mammoth.db \
"SELECT idempotency_key, destination_name, delivery_unit, transaction_id, source_position, delivered_at FROM delivered_envelopes;"
Container volume inspection
If the Mammoth image does not include sqlite3, inspect the database using a temporary container that mounts the same volume:
docker run --rm -it \
-v failing_webhook_retry_mammoth_retry_data:/data \
alpine:3.20 \
sh -c "apk add --no-cache sqlite && sqlite3 /data/mammoth.db '.tables'"