Files
nick-doc/08 - Operations/Database Operations.md
2026-05-23 20:35:34 +03:30

9.9 KiB

title, tags
title tags
Database Operations
operations

Database Operations

Day-to-day operations for the two stateful services: MongoDB 8.2 (primary data store) and Redis 8 (cache, rate-limit counters, ephemeral session data).

For schema details see Data Models. For backup procedures and disaster recovery see Backup & Recovery.


1. MongoDB

1.1 Connection

Env URI in compose Auth
Dev mongodb://mongodb:27017 none
Prod mongodb://mongodb:27017 (private network) or with creds via .env typically none on the private network, but enable --auth if exposed

The DB name comes from DB_NAME (e.g. marketplace). See Environment Variables#database.

Connect from a shell inside the host:

# Dev
docker exec -it nickdev-mongodb mongosh

# Prod
docker exec -it nickapp-mongodb mongosh
> use marketplace
> show collections

If auth is enabled:

docker exec -it nickapp-mongodb mongosh \
  -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" \
  --authenticationDatabase admin

1.2 Init scripts (mongo-init/)

The production compose bind-mounts ./mongo-init into /docker-entrypoint-initdb.d. Mongo runs *.js and *.sh from this folder only on a fresh datadir (first boot of a new volume). Use this to:

  • Create application users (db.createUser({...}))
  • Bootstrap collections + indexes that must exist before the app starts

Example mongo-init/01-create-user.js:

db = db.getSiblingDB('marketplace');
db.createUser({
  user: 'marketplace_app',
  pwd: process.env.MARKETPLACE_APP_PWD,
  roles: [{ role: 'readWrite', db: 'marketplace' }],
});

[!warning] These scripts do not run when you restart an existing container. To force re-init, drop the mongodb_data volume — which destroys all data. Plan accordingly.

1.3 Indexes

Indexes are declared in Mongoose schemas under backend/src/models/. The app calls Model.createIndexes() on connection (via the model's syncIndexes/ensureIndexes lifecycle). Highlights:

Collection Key indexes
users email (unique), googleId (sparse), role, createdAt
addresses userId + compound for primary lookup
purchaserequests buyerId, status, createdAt, text index on title+description
selleroffers requestId, sellerId, status
payments providerPaymentId (unique sparse), userId, status, createdAt, transactionHash
chats participants (array), updatedAt
notifications userId + read, createdAt
tempverifications TTL on expiresAt (auto-deletes expired OTPs)

To verify a specific collection:

db.payments.getIndexes()

To add a new index without code-gen — preferred path is to declare it in the Mongoose schema and ship a deploy. For emergency hotfixes:

db.payments.createIndex({ providerPaymentId: 1 }, { unique: true, sparse: true });

1.4 TTL indexes

Currently used on tempverifications.expiresAt (5-minute auto-purge of email OTPs / passkey challenges). Mongo's TTL monitor runs every 60 seconds — purge isn't immediate.

If you add more TTL indexes:

db.notifications.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 90 });  // 90 days

1.5 Backup with mongodump

# Connect into the container, dump locally, copy out
docker exec nickapp-mongodb sh -c \
  "mongodump --db=marketplace --archive=/tmp/marketplace-$(date +%F).archive --gzip"
docker cp nickapp-mongodb:/tmp/marketplace-$(date +%F).archive ./backups/

# Or stream directly to host
docker exec nickapp-mongodb \
  mongodump --db=marketplace --archive --gzip \
  > ./backups/marketplace-$(date +%F).gz

For full details (retention, RTO/RPO, offsite copies) see Backup & Recovery.

1.6 Restore

# Restore an archive to an empty database
docker exec -i nickapp-mongodb \
  mongorestore --archive --gzip --drop \
  < ./backups/marketplace-2026-05-20.gz

--drop drops each collection before restoring. Omit it to merge.

[!warning] Restoring is destructive to current data. Always practise on a staging clone first.

1.7 Migrations

There is no formal migration framework. Two patterns are used:

  • Mongoose schema changes are forward-compatible (new optional fields default to undefined). Older documents will still load.
  • Data backfills are one-shot scripts in backend/src/scripts/ (e.g. migrateUserPoints.ts, fix-transaction-hashes.js, fix-dispute-sellers.js).

Pattern for a new migration:

  1. Add a src/seeds/migrate<Thing>.ts script that is idempotent (use $exists: false guards).
  2. Run on staging, confirm.
  3. Take a backup (Backup & Recovery).
  4. Run in production: docker exec -it nickapp-backend node dist/seeds/migrate<Thing>.js.
  5. Commit the script (it serves as a record of what changed).

1.8 Common admin queries

// Count by collection
db.users.countDocuments({ role: 'buyer' })

// Disk usage per collection
db.runCommand({ collStats: 'payments', scale: 1024*1024 }).size

// Slow queries
db.setProfilingLevel(1, { slowms: 200 })   // log queries > 200ms
db.system.profile.find().sort({ ts: -1 }).limit(10)

// Lock contention
db.serverStatus().locks

1.9 Seeding production safely

Seed scripts are designed to be idempotent for categories but destructive for users/addresses. Don't run seed:all in production.

Safe in production:

docker exec -it nickapp-backend node dist/seeds/seedCategories.js
docker exec -it nickapp-backend node dist/seeds/seedLevels.js

Optional auto-seed on startup: set AUTO_SEED_ON_START=true in .env. The bootstrap code only seeds when no non-admin users exist — safe to leave on.

[!warning] Never run seed:all or seed:users against production. They drop the existing users and addresses collections.


2. Redis

2.1 Connection

Dev: redis://redis:6379 (no password). Prod: redis://:<REDIS_PASSWORD>@redis:6379. The compose command line is redis-server --requirepass "$REDIS_PASSWORD".

Inspect:

docker exec -it nickapp-redis redis-cli -a "$REDIS_PASSWORD"
> INFO server
> DBSIZE
> KEYS *           # prod-unsafe on large datasets, use SCAN

2.2 What we store

  • Rate-limit counters for express-rate-limit
  • Session data for refresh-token tracking and revocation lists
  • Socket.IO adapter state (when scaled horizontally — currently single-node)
  • Application caches (TTL'd keys for expensive aggregates)
  • Idempotency keys for webhook deduplication

Key prefixes follow <service>:<entity>:<id>. E.g. payment:idem:<requestId>, auth:refresh:<userId>.

2.3 Persistence

Redis 8 defaults to RDB snapshots + optional AOF. Our compose uses the default config:

  • RDB snapshot triggers: save 3600 1, save 300 100, save 60 10000.
  • AOF is disabled by default.
  • RDB file lives at /data/dump.rdb inside the redis_data volume.

To enable AOF for stronger durability, override the command in docker-compose.production.yml:

redis:
  command: ["sh","-lc","redis-server --requirepass \"$${REDIS_PASSWORD}\" --appendonly yes --appendfsync everysec"]

appendfsync everysec is the common compromise: at most 1 second of writes lost on crash, with negligible perf impact.

2.4 Eviction policy

Default is noeviction — Redis refuses writes when memory is full. For our use (caches that can be regenerated), set:

docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" \
  CONFIG SET maxmemory 256mb
docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" \
  CONFIG SET maxmemory-policy allkeys-lru

Persist by adding to a custom redis.conf mounted at /usr/local/etc/redis/redis.conf (then change the compose command: to ["redis-server","/usr/local/etc/redis/redis.conf","--requirepass",...]).

2.5 Backup

Redis backups are usually unnecessary (the data is regeneratable) but still cheap:

# Snapshot now
docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" BGSAVE
docker cp nickapp-redis:/data/dump.rdb ./backups/redis-$(date +%F).rdb

BGSAVE is non-blocking (forks). For AOF, copy /data/appendonly.aof too.

2.6 Cache flush

When deploying breaking changes to cached schemas:

# Flush everything (DEV ONLY)
docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" FLUSHALL

# Targeted (safer)
docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" \
  --scan --pattern 'payment:idem:*' | \
  xargs -L 1 docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" DEL

[!warning] FLUSHALL will sign out every user with an active refresh token and reset every rate-limit counter. Avoid in production unless that is what you want.

2.7 Monitoring

docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" INFO stats
docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" INFO memory
docker exec nickapp-redis redis-cli -a "$REDIS_PASSWORD" SLOWLOG GET 10

Watch evicted_keys, keyspace_misses, rejected_connections — see Monitoring for thresholds.


3. Maintenance windows

For both DBs, schedule a window when:

  • Bumping major version (Mongo 8 → 9, Redis 8 → 9)
  • Restoring from backup
  • Running a destructive migration

Suggested checklist:

  1. Announce in #ops Slack / status page.
  2. Trigger mongodump (see Backup & Recovery).
  3. Stop the backend container so writes stop: docker compose stop nickapp-backend.
  4. Perform the operation.
  5. Restart backend: docker compose start nickapp-backend.
  6. Verify health: curl https://amn.gg/api/health.
  7. Close window.

  • Backup & Recovery — formal backup/restore procedures, RTO/RPO targets, offsite storage.
  • Monitoring — what metrics to watch (slow queries, evictions, replication lag).
  • Incident Response — runbooks for "MongoDB unreachable" and "Redis unreachable".
  • Data Models — schema details for every collection.