This runbook provides step-by-step procedures for common operational tasks in StellarStream.
For initial production setup, refer to the Deployment Guide.
- Reset SQLite Database
- Rotate JWT Secret
- Force Indexer Reconcile
- Requeue Dead-Letter Webhooks
- Archive Old Streams Manually
- Indexer Falls Behind
- Webhook Dead-Letter Spike
- SQLite WAL Size Growth
- Contract Invocation Timeout
Prerequisites:
- Access to the server's filesystem.
- Backend service stopped (recommended).
Steps:
- Stop the backend service.
- Navigate to the
backend/datadirectory. - Delete the database file:
rm backend/data/streams.db
- Restart the backend service.
Expected Output:
- Backend logs show:
Database initialized.andmigrate()running. - A new
streams.dbfile is created.
Prerequisites:
- Access to the backend environment variables or
.envfile.
Steps:
- Generate a new random secret:
openssl rand -hex 32
- Update the
JWT_SECRETvalue in your environment orbackend/.envfile. - Restart the backend service.
Expected Output:
- All existing user sessions are invalidated.
- Users will be prompted to re-connect their wallets and sign a new challenge.
Prerequisites:
- Access to the backend environment variables.
Steps:
- Identify the ledger sequence number you want to re-index from.
- Set the
INDEXER_START_LEDGERenvironment variable:# Example: Re-index from ledger 1234567 export INDEXER_START_LEDGER=1234567
- Restart the backend service.
Expected Output:
- Backend logs show:
INDEXER_START_LEDGER override active: starting from ledger 1234567. - The indexer will process events starting from that ledger, potentially updating local records.
Prerequisites:
- An admin JWT or access to the database.
- The ID of the dead-letter record.
Steps:
- Get the list of dead-letter webhooks:
curl -H "Authorization: Bearer <ADMIN_TOKEN>" http://localhost:3001/api/webhooks/dead-letters - Re-queue a specific webhook using its ID:
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" http://localhost:3001/api/webhooks/dead-letters/<ID>/requeue
Expected Output:
- JSON response:
{ "success": true, "message": "Webhook re-queued successfully" }. - The record is moved from
webhook_dead_lettersback towebhook_deliveries.
Prerequisites:
- Node.js environment on the server.
Steps: Currently, archiving is defined in the codebase but not exposed via a CLI or API. To trigger it manually, you can use a small script:
- Create a file
archive.js:const { initDb } = require('./dist/services/db'); const { archiveOldStreams } = require('./dist/services/streamStore'); async function run() { initDb(); const archived = await archiveOldStreams(); console.log(`Archived ${archived} streams.`); process.exit(0); } run();
- Run the script:
node archive.js
Expected Output:
- Console log showing the number of streams archived (completed > 30 days ago).
Symptoms:
- Stream statuses in the dashboard are stale (e.g., a completed stream still shows "active").
- Backend logs show
lastLedgerlagging behind the current Stellar network ledger sequence. - Alert:
indexer_lag_secondsexceeds threshold (configurable, default 300s).
Diagnosis:
- Check the current indexer cursor position:
sqlite3 backend/data/streams.db "SELECT key, value FROM indexer_cursor;" - Compare with the latest Stellar ledger sequence (using the RPC endpoint):
curl -s <STELLAR_RPC_URL> -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"getLatestLedger"}' | \ jq '.result.sequence'
- Check backend logs for indexer errors:
Or if running via PM2:
journalctl -u stellar-stream-backend --since "10 minutes ago" | grep -i indexer
pm2 logs stellar-stream-backend --lines 200 | grep -i indexer
Remediation:
- Network issue: Verify the backend can reach the Stellar RPC endpoint:
If unreachable, check firewall rules and RPC provider status.
curl -s --max-time 5 <STELLAR_RPC_URL>/health
- Backoff stuck: Restart the backend to reset the indexer's exponential backoff:
Or if using systemd:
pm2 restart stellar-stream-backend
systemctl restart stellar-stream-backend
- Force re-index from a specific ledger (use with caution—this may process duplicate events):
export INDEXER_START_LEDGER=<LEDGER_SEQUENCE> pm2 restart stellar-stream-backend
- Persistent lag: If the indexer consistently falls behind, reduce the polling interval by setting
INDEXER_POLL_INTERVAL_MSto a lower value (e.g.,5000for 5 seconds) in the backend.env.
Symptoms:
- Alert:
webhook_dead_letter_countexceeds threshold (default > 50). - Recipients report not receiving stream event notifications.
- Backend logs contain repeated
webhook delivery failedentries.
Diagnosis:
- Count dead-letter records:
sqlite3 backend/data/streams.db "SELECT COUNT(*) FROM webhook_dead_letters;" - List recent dead-letter entries with failure reasons:
sqlite3 backend/data/streams.db \ "SELECT id, stream_id, event_type, failure_reason, created_at \ FROM webhook_dead_letters ORDER BY created_at DESC LIMIT 20;"
- Check the webhook worker log for connectivity errors:
journalctl -u stellar-stream-backend --since "30 minutes ago" | grep -i "webhook\|dead.letter\|retry"
Remediation:
- Fix the receiver endpoint: If the downstream webhook receiver is down or returning errors, contact the receiver's operator. Verify the webhook endpoint is reachable:
curl -s -o /dev/null -w "%{http_code}" --max-time 5 <WEBHOOK_URL>
- Re-queue dead-letter webhooks after the receiver is healthy:
Or requeue individually via the admin API (see Requeue Dead-Letter Webhooks).
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \ http://localhost:3001/api/webhooks/dead-letters/requeue-all - Increase retry attempts if the receiver is slow but healthy: set
WEBHOOK_MAX_RETRIESin the backend.env(default: 3, max recommended: 6). - Inspect dead-letter payloads to rule out malformed data:
sqlite3 backend/data/streams.db \ "SELECT id, payload FROM webhook_dead_letters ORDER BY created_at DESC LIMIT 5;" | \ jq '.'
Symptoms:
- Disk usage on the backend server is growing unexpectedly.
- The
backend/data/directory contains astreams.db-walfile significantly larger thanstreams.db. - Alert: WAL file size exceeds 500 MB (configurable threshold).
Diagnosis:
- Check WAL and database file sizes:
ls -lh backend/data/streams.db* - Confirm WAL mode is active:
sqlite3 backend/data/streams.db "PRAGMA journal_mode;" - Check how many checkpoints are pending:
Output format:
sqlite3 backend/data/streams.db "PRAGMA wal_checkpoint;"busy,log,checkpointed. A largelogvalue (pages) indicates many uncheckpointed writes. - Monitor write-heavy workloads:
sqlite3 backend/data/streams.db \ "SELECT COUNT(*) FROM streams; SELECT COUNT(*) FROM stream_events; \ SELECT COUNT(*) FROM webhook_deliveries;"
Remediation:
- Force a WAL checkpoint to flush the WAL into the main database:
The WAL file should shrink or disappear after this.
sqlite3 backend/data/streams.db "PRAGMA wal_checkpoint(TRUNCATE);" - Schedule periodic checkpointing by adding the following pragmas to
db.tsafter WAL mode is enabled:These reduce WAL spooling and improve concurrency.PRAGMA synchronous=NORMAL; PRAGMA busy_timeout=5000; PRAGMA cache_size=-64000;
- Set
PRAGMA wal_autocheckpointto tune checkpoint frequency (default: 1000 pages). For write-heavy workloads, lower it:sqlite3 backend/data/streams.db "PRAGMA wal_autocheckpoint=500;" - Add a periodic cron job if manual checkpointing is required:
# Every hour, checkpoint the WAL 0 * * * * sqlite3 /path/to/streams.db "PRAGMA wal_checkpoint(TRUNCATE);"
- Verify recovery after remediation:
ls -lh backend/data/streams.db* sqlite3 backend/data/streams.db "PRAGMA wal_checkpoint;"
Symptoms:
- Stream creation, claim, or cancel operations fail with timeout errors.
- Backend logs contain
soroban_contracterrors:Contract invocation timed outorRPC call timed out. - Frontend shows "Transaction failed" with no detailed error message.
Diagnosis:
- Check backend logs for contract invocation errors:
journalctl -u stellar-stream-backend --since "1 hour ago" | grep -i "contract\|soroban\|timeout\|rpc"
- Verify the Soroban RPC endpoint is reachable and responsive:
curl -s --max-time 10 <STELLAR_RPC_URL> -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' | jq '.'
- Check the current network ledger status:
curl -s --max-time 10 <STELLAR_RPC_URL> -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"getLatestLedger"}' | \ jq '{sequence: .result.sequence, protocolVersion: .result.protocolVersion}'
- Verify the deployed contract ID matches what the backend expects:
grep SOROBAN_CONTRACT_ID backend/.env
Remediation:
- Increase RPC timeout in the backend
.env:(Default is typically 10000 ms. Increase in increments of 5000 ms.)SOROBAN_RPC_TIMEOUT_MS=30000
- Switch to a more reliable RPC provider if timeouts persist. Update
STELLAR_RPC_URLin.env. - Check rate limits — some RPC providers throttle high-volume requests. Reduce concurrent contract calls by lowering
SOROBAN_MAX_CONCURRENT_CALLS(default: 10). - Restart the backend to clear any stale RPC connections:
pm2 restart stellar-stream-backend
- Verify the contract is still deployed at the expected address:
curl -s --max-time 10 <STELLAR_RPC_URL> -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"getContractData","params":{"contractId":"<SOROBAN_CONTRACT_ID>","key":"..."}}' | \ jq '.result'
- Escalate to the Soroban/SDK team if the issue is on the Stellar network side (e.g., network congestion or protocol upgrade).