PostgreSQL Deployment
Ratchet on PostgreSQL 14+.
Prerequisites
- PostgreSQL 14 or later
- UTF-8 encoding
- psql CLI tool
Schema Setup
Apply DDL
psql -U ratchet -d mydb -f ratchet-store-postgresql/src/main/resources/ddl/postgresql-schema.sql
Or copy into your migration tool:
cp postgresql-schema.sql src/main/resources/db/migration/V1__ratchet_schema.sql
flyway migrate
Verify Installation
\dt scheduler_*
You should see:
ratchet_schema_versionscheduler_jobscheduler_job_queuescheduler_business_key_reservationscheduler_job_tagscheduler_job_executionscheduler_job_logscheduler_job_archivescheduler_batchscheduler_batch_metricsscheduler_nodescheduler_lockscheduler_resource_limitscheduler_resource_permitscheduler_workflow_conditionscheduler_dlq_alerts
Configuration
DataSource
Configure your data source for PostgreSQL:
<!-- persistence.xml -->
<persistence-unit name="your-application-pu" transaction-type="JTA">
<jta-data-source>java:/RatchetDS</jta-data-source>
<class>run.ratchet.store.entity.JobEntity</class>
<class>run.ratchet.store.entity.JobExecutionEntity</class>
<class>run.ratchet.store.entity.ResourceLimitEntity</class>
<class>run.ratchet.store.entity.BatchMetricsEntity</class>
<class>run.ratchet.store.entity.WorkflowConditionEntity</class>
<class>run.ratchet.store.entity.ArchivedJobEntity</class>
<class>run.ratchet.store.entity.NodeEntity</class>
<class>run.ratchet.store.entity.DlqAlertEntity</class>
<class>run.ratchet.store.entity.JobLogEntity</class>
<class>run.ratchet.store.entity.ResourcePermitEntity</class>
<class>run.ratchet.store.entity.BatchEntity</class>
<class>run.ratchet.store.entity.LockEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL14Dialect" />
</properties>
</persistence-unit>
The PostgreSQL store does not require a fixed persistence-unit name. By default it uses the
deployment's unnamed @PersistenceContext. If your application has multiple persistence units,
provide a CDI alternative for RatchetEntityManagerProvider:
@ApplicationScoped
@Alternative
@Priority(Interceptor.Priority.APPLICATION)
public class RatchetPuProvider implements RatchetEntityManagerProvider {
@PersistenceContext(unitName = "your-application-pu")
EntityManager em;
@Override
public EntityManager getEntityManager() {
return em;
}
}
WildFly CLI
/subsystem=datasources/data-source=RatchetDS:add( \
jndi-name=java:/RatchetDS, \
driver-name=postgresql, \
connection-url=jdbc:postgresql://localhost:5432/ratchet, \
user-name=ratchet, \
password=secret, \
min-pool-size=5, \
max-pool-size=20, \
valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker)
Connection String
jdbc:postgresql://localhost:5432/ratchet
Advanced Features
SKIP LOCKED (Optimistic Claiming)
Ratchet uses PostgreSQL's SKIP LOCKED clause for lock-free job claiming:
SELECT * FROM scheduler_job
WHERE status = 'PENDING'
AND scheduled_time <= NOW()
ORDER BY priority + FLOOR(GREATEST(0, EXTRACT(EPOCH FROM (statement_timestamp() - scheduled_time)) / 60) / 15) DESC,
scheduled_time ASC
LIMIT 10
FOR UPDATE SKIP LOCKED;
This allows multiple Ratchet nodes to safely claim different jobs simultaneously without blocking each other.
Partial Indexes
The schema uses partial indexes for performance. Instead of indexing all rows, partial indexes only cover the rows that matter for active queries:
-- Unique business key only for active jobs (PENDING, RUNNING, PAUSED)
CREATE UNIQUE INDEX idx_job_active_business_key
ON scheduler_job (business_key)
WHERE status IN ('PENDING', 'RUNNING', 'PAUSED') AND business_key IS NOT NULL;
This approach replaces MySQL's generated active_business_key column with a more space-efficient partial unique index.
Generated Columns
Target class and method name are extracted from the JSON payload as generated columns for indexing:
target_class TEXT GENERATED ALWAYS AS (payload::jsonb ->> 'target') STORED
method_name TEXT GENERATED ALWAYS AS (payload::jsonb ->> 'method') STORED
CHECK Constraints
PostgreSQL uses CHECK constraints for data validation instead of MySQL's ENUM types:
CONSTRAINT chk_job_status CHECK (status IN ('PENDING', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELED', 'PAUSED'))
CONSTRAINT chk_job_type CHECK (job_type IN ('SINGLE', 'RECURRING', 'BATCH_PARENT', 'BATCH_CHILD', ...))
CONSTRAINT chk_job_priority CHECK (priority BETWEEN 0 AND 4)
Performance Tuning
Connection Pooling
Use PgBouncer in transaction mode for efficient connection pooling:
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
Shared Buffers
Set to 25% of available RAM:
ALTER SYSTEM SET shared_buffers = '4GB';
Restart PostgreSQL after changing.
Work Memory
Increase for complex queries:
ALTER SYSTEM SET work_mem = '256MB';
SELECT pg_reload_conf();
Effective Cache Size
Help the planner estimate cache hit rates (set to 75% of available RAM):
ALTER SYSTEM SET effective_cache_size = '12GB';
SELECT pg_reload_conf();
Autovacuum Tuning
Ratchet performs frequent updates to scheduler_job. Tune autovacuum to keep up:
ALTER TABLE scheduler_job SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_scale_factor = 0.02
);
Monitoring
Monitor Job Queue
SELECT
COUNT(*) AS total,
COUNT(CASE WHEN status = 'PENDING' THEN 1 END) AS pending,
COUNT(CASE WHEN status = 'RUNNING' THEN 1 END) AS running,
COUNT(CASE WHEN status = 'FAILED' THEN 1 END) AS failed
FROM scheduler_job;
Query Performance
Enable pg_stat_statements and find slow queries:
SELECT query, calls, mean_exec_time, max_exec_time
FROM pg_stat_statements
WHERE query LIKE '%scheduler_%'
ORDER BY mean_exec_time DESC
LIMIT 10;
Index Usage
Verify indexes are being used:
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE indexname LIKE 'idx_%'
AND tablename LIKE 'scheduler_%'
ORDER BY idx_scan ASC;
Active Nodes
SELECT node_id, heartbeat_ts, started_at
FROM scheduler_node
WHERE heartbeat_ts > NOW() - INTERVAL '30 seconds'
ORDER BY started_at;
Maintenance
Vacuum & Analyze
Regular maintenance is important for tables with frequent updates:
# Manual vacuum during off-peak
psql -d ratchet -c "VACUUM ANALYZE scheduler_job;"
Reindex
Rebuild indexes to reclaim space after heavy updates:
REINDEX TABLE scheduler_job;
REINDEX TABLE scheduler_job_execution;
REINDEX TABLE scheduler_job_archive;
Monitor Table Bloat
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
n_live_tup AS live_rows,
n_dead_tup AS dead_rows,
ROUND(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_pct
FROM pg_stat_user_tables
WHERE relname LIKE 'scheduler_%'
ORDER BY n_dead_tup DESC;
Backup & Recovery
Logical Backup
pg_dump -Fc -v ratchet > ratchet-backup.dump
Point-in-Time Recovery
pg_basebackup -D /backup -Ft -z
Restore
pg_restore -d ratchet ratchet-backup.dump
High Availability
Streaming Replication
For HA, use PostgreSQL streaming replication:
-- On primary
ALTER SYSTEM SET wal_level = replica;
ALTER SYSTEM SET max_wal_senders = 5;
SELECT pg_reload_conf();
Failover
Use Patroni or pg_auto_failover for automatic failover. Ratchet reconnects automatically when the connection pool detects a new primary.