Skip to main content

Database & Redis

ChartChat uses PostgreSQL as its primary data store and Redis for caching, background job queues, and real-time features.


PostgreSQL​

File: packages/server/src/database.ts

The server connects to PostgreSQL using the pg connection pool. The connection is configured via medplum.config.json or environment variables.

Connection pools​

PoolPurpose
Primary (pool)All writes and most reads
Read-only (readonlyPool)Optional — used when readonlyDatabase is configured (e.g., an AWS RDS read replica)

The getDatabasePool(mode) function returns the appropriate pool:

  • DatabaseMode.WRITER → always the primary pool
  • DatabaseMode.READER → read-only pool if configured, otherwise primary pool

Schema & Migrations​

Directory: packages/server/src/migrations/schema/

The database schema is managed through a sequential migration system. There are currently 107+ migration files (v1.ts through v107.ts).

Migrations run automatically on server startup when runMigrations !== false in the config. The server uses an advisory lock (pg_advisory_lock(1)) to prevent concurrent migrations in multi-instance deployments.

Migration types​

TypeDescription
Pre-deploySchema changes that must run before the new code is deployed (column adds, index creates)
Post-deployData backfills and non-blocking changes that run after deployment

The maybeAutoRunPendingPostDeployMigration() function runs any pending post-deploy migrations automatically after the server starts.

Running migrations manually​

From the Admin Portal Super Admin page:

  • Reconcile Schema Drift — detects and applies any missing schema changes
  • Reindex — rebuilds search indexes for a specific resource type

Redis​

File: packages/server/src/redis.ts

Redis is used for multiple purposes across the server.

What's stored in Redis​

Key patternPurposeTTL
ehr:token:{connectionId}Raw EHR OAuth token (access + refresh)Token expiry (~1 hour)
ehr:sync:status:{patientId}Current EHR sync state (syncing / idle)5 minutes
ehr:sync:result:{patientId}Last sync outcome (synced/expired/error providers)5 minutes
oauth:state:{uuid}PKCE OAuth state for EHR connect flowShort TTL (single-use)
BullMQ job queuesBackground worker jobs (bot execution, async FHIR batch, etc.)Managed by BullMQ
WebSocket subscriptionsFHIR subscription notificationsEvent-driven

Connection management​

The server maintains a single global Redis instance (getRedis()). A separate subscriber-mode instance (getRedisSubscriber()) is used for pub/sub — this is required because Redis subscriber connections cannot send regular commands.

On failover (e.g., AWS ElastiCache primary/replica switch), the client automatically reconnects when it receives a READONLY error.

Local setup​

For local development, Redis is started via Docker Compose:

docker-compose up   # from the repo root

This starts Redis on the default port 6379. See Local Setup for the full docker-compose configuration.


Background Workers​

File: packages/server/src/workers/

ChartChat uses BullMQ (backed by Redis) for background job processing.

WorkerQueuePurpose
BotBotQueueExecutes FHIR subscription bots (VM context or AWS Lambda)
BatchBatchQueueProcesses POST /fhir/R4 async FHIR Bundle jobs
SubscriptionSubscriptionQueueDelivers FHIR R4 subscription notifications (webhook + bot dispatch)
DownloadDownloadQueueFetches external URLs in Attachment fields and stores them as Binary resources
CronCronQueueExecutes bots on a schedule via FHIR Timing resources
ReindexReindexQueueRebuilds search index columns for a given resource type
Set AccountsSetAccountsQueueUpdates patient compartment references after account changes
Post-Deploy MigrationPostDeployMigrationQueueRuns data backfill migrations as background AsyncJob resources
Data RetentionDataRetentionQueueDaily job at 02:00 — purges revoked logins (90-day cutoff) per HIPAA policy

For full details on data retention and HIPAA compliance, see Audit, Security & Data Retention.

The BullMQ dashboard is available at /api/admin/queues (requires admin access). Password is set via bullBoardPassword in medplum.config.json.


Seeding​

File: packages/server/src/seed.ts

On first startup, the server seeds the database with:

  • A default super admin account (configurable via defaultSuperAdminEmail / defaultSuperAdminPassword)
  • Default structure definitions, search parameters, and value sets

This only runs once — subsequent startups detect that the seed data exists and skip it.