Troubleshooting
This guide records common issues encountered while running Mammoth locally, with Docker Compose, or with Kubernetes.
secret "postgres-secrets" not found
Symptom:
CreateContainerConfigError
Error: secret "postgres-secrets" not found
Cause:
The Helm chart references a PostgreSQL password Secret that does not exist.
Fix:
kubectl create secret generic postgres-secrets \
--from-literal=password=postgres
kubectl rollout restart deploy/mammoth
If your chart values use a different secret name or key, inspect:
helm get values mammoth --all
helm get manifest mammoth | grep -A5 -B5 postgres-secrets
could not translate host name "postgres-service.internal"
Symptom:
PostgreSQL CDC source failed: could not translate host name "postgres-service.internal" to address: Name or service not known
Cause:
The default Postgres host is not resolvable inside your Kubernetes cluster.
Fix:
Point Mammoth to a real Kubernetes Service:
helm upgrade mammoth ./charts/mammoth \
--set postgres.host=postgres-service \
--set postgres.port=5432
Mammoth pod is running but no webhook arrives
Possible causes:
- no publication exists
- the table is not part of the publication
- webhook URL is not reachable from inside the cluster
- Mammoth started before the publication was created and needs a restart
- destination returns an error and the event is dead-lettered
Check publications:
kubectl exec deploy/postgres -- psql -U postgres -d mammoth_demo -c "SELECT * FROM pg_publication;"
Create a table and publication:
kubectl exec deploy/postgres -- psql -U postgres -d mammoth_demo -c \
"CREATE TABLE IF NOT EXISTS orders (id bigserial PRIMARY KEY, status text NOT NULL, total_cents integer NOT NULL);"
kubectl exec deploy/postgres -- psql -U postgres -d mammoth_demo -c \
"CREATE PUBLICATION mammoth_publication FOR TABLE orders;"
Restart Mammoth and insert a row:
kubectl rollout restart deploy/mammoth
kubectl exec deploy/postgres -- psql -U postgres -d mammoth_demo -c \
"INSERT INTO orders (status, total_cents) VALUES ('created', 8888);"
Replica identity preflight fails
Mammoth lists every published UPDATE/DELETE table without usable old-row
identity. Inspect the configured publications and table identity:
SELECT
publication.pubname,
publication.pubupdate,
publication.pubdelete,
publication_table.schemaname,
publication_table.tablename,
relation.relreplident
FROM pg_publication_tables AS publication_table
JOIN pg_publication AS publication USING (pubname)
JOIN pg_namespace AS namespace
ON namespace.nspname = publication_table.schemaname
JOIN pg_class AS relation
ON relation.relnamespace = namespace.oid
AND relation.relname = publication_table.tablename
WHERE publication.pubname = 'mammoth_publication';
Add a primary key, select an eligible unique index with REPLICA IDENTITY USING INDEX, use REPLICA IDENTITY FULL, or remove UPDATE/DELETE from the
publication when it is intentionally insert-only. FULL is valid but has WAL
volume and row-matching costs.
Replication slot is active but nothing is delivered
Check slot movement:
SELECT
slot_name,
active,
restart_lsn,
confirmed_flush_lsn
FROM pg_replication_slots;
If confirmed_flush_lsn moves, Mammoth is consuming the stream. The issue is likely downstream delivery configuration.
Check webhook URL and Mammoth logs:
kubectl logs deploy/mammoth --tail=200
helm get values mammoth --all
PostgreSQL slot preflight fails
Mammoth fails closed when the configured slot is missing, active elsewhere, lost, invalidated, incompatible, or unable to serve the durable checkpoint. Inspect the complete slot state:
SELECT
slot_name,
plugin,
slot_type,
database,
active,
restart_lsn,
confirmed_flush_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_wal_bytes,
wal_status,
safe_wal_size,
inactive_since,
conflicting,
invalidation_reason,
catalog_xmin
FROM pg_replication_slots
WHERE slot_name = 'mammoth_prod';
The available columns vary by PostgreSQL version. Remove fields that the running server does not expose.
Do not solve a missing or invalidated slot by recreating it while retaining
Mammoth's old checkpoint. The lost interval requires external backfill or
reconciliation. After that process, establish new safe operational state and
restart Mammoth. For a first-time deployment with no checkpoint,
auto_create_slot: true may create the missing slot.
The same state is available through /readyz and the
mammoth_postgres_slot_* Prometheus gauges. If
mammoth_postgres_slot_inspection_up is 0, check PostgreSQL connectivity and
catalog permissions. If retained WAL grows continuously, investigate stalled
delivery or acknowledgement before PostgreSQL storage is exhausted.
Retained WAL keeps growing
Check Mammoth readiness, pending dead letters, destination latency, and the gap
between restart_lsn and confirmed_flush_lsn. A slow or failing destination
can hold the contiguous watermark behind later completed work.
Alert before retained WAL reaches the environment's disk or recovery budget.
max_slot_wal_keep_size and, where supported,
idle_replication_slot_timeout can protect PostgreSQL, but crossing those
guardrails may invalidate the slot. Increasing a guardrail buys investigation
time; it does not repair stalled delivery or acknowledgement. Disk-capacity and
catalog_xmin age monitoring belong in the PostgreSQL infrastructure stack.
Webhook payloads changed after a schema migration
PostgreSQL logical replication does not deliver DDL. Mammoth may receive new relation metadata and emit a changed row shape, but it does not migrate or version webhook consumers.
Deploy consumers that accept both shapes before applying additive database changes. For renames, removals, type changes, or replica-identity changes, coordinate a compatibility window and re-run Mammoth startup preflight. If a consumer rejects the new shape, correct the consumer or routing policy before replaying the resulting dead letters.
Sequence values diverge in a downstream database
Logical replication delivers generated values stored in rows but does not copy the sequence's current state. Webhook consumers normally need no action. A downstream database intended to become writable must synchronize sequences separately before cutover.
A destination reports a duplicate-key or semantic conflict
Mammoth is not a PostgreSQL subscriber applying SQL, so PostgreSQL subscription conflict-repair procedures do not apply. Destinations must use Mammoth idempotency keys and their own conflict policy. Mammoth retries delivery and dead-letters exhausted failures; after fixing the destination condition, replay the affected dead letter explicitly.
Docker Compose example shows duplicate dead-letter rows
Cause:
The Docker volume was reused across multiple runs with the same sample event_id.
Fix:
Reset the example volume:
docker compose down -v
Then rerun the example.
sqlite3 is not available in the container
The runtime image may not include sqlite3. Keep the runtime image lean and inspect the database with a temporary helper container.
Example:
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'"
zsh asks to correct exec to exe
When running commands like:
kubectl exec deploy/postgres -- psql -U postgres -d mammoth_demo -c "SELECT 1;"
zsh may ask:
zsh: correct 'exec' to 'exe' [nyae]?
Answer n or disable correction for the command. The kubectl exec command is correct.