Skip to main content
Temporal Server requires two types of databases: a persistence store for workflow execution state and a visibility store for workflow search and listing. This guide covers setup for all supported databases.

Database Requirements

Persistence Store

Stores workflow execution history, state, and metadataSupported:
  • Cassandra 3.11+
  • MySQL 8.0+
  • PostgreSQL 12+
  • SQLite (development only)

Visibility Store

Enables workflow search and filteringSupported:
  • MySQL 8.0+ (standard)
  • PostgreSQL 12+ (standard)
  • Elasticsearch 7.10+ (advanced)
Important: You cannot change the number of history shards after initial deployment. Choose an appropriate value based on your expected workload.

MySQL Setup

Prerequisites

  • MySQL 8.0.29 or higher
  • Sufficient privileges to create databases and users
  • Network connectivity from Temporal servers

Database Creation

1

Create databases

CREATE DATABASE temporal CHARACTER SET utf8mb4;
CREATE DATABASE temporal_visibility CHARACTER SET utf8mb4;
2

Create user and grant permissions

CREATE USER 'temporal'@'%' IDENTIFIED BY 'temporal';
GRANT ALL PRIVILEGES ON temporal.* TO 'temporal'@'%';
GRANT ALL PRIVILEGES ON temporal_visibility.* TO 'temporal'@'%';
FLUSH PRIVILEGES;
3

Download or build temporal-sql-tool

# Option 1: Build from source
git clone https://github.com/temporalio/temporal.git
cd temporal
make temporal-sql-tool

# Option 2: Use Docker
docker pull temporalio/server:latest
4

Initialize schema for persistence store

# Set version tables
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin mysql8 \
  setup-schema -v 0.0

# Apply schema
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin mysql8 \
  update-schema --schema-name mysql/v8/temporal
5

Initialize schema for visibility store

# Set version tables
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal_visibility \
  --plugin mysql8 \
  setup-schema -v 0.0

# Apply schema
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal_visibility \
  --plugin mysql8 \
  update-schema --schema-name mysql/v8/visibility

Using Custom Schema Directory

If you need to apply schema from a local directory:
# Clone the repository
git clone https://github.com/temporalio/temporal.git
cd temporal

# Apply schema from versioned directory
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin mysql8 \
  update-schema -d ./schema/mysql/v8/temporal/versioned

Schema Upgrade

To upgrade to a specific version:
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin mysql8 \
  update-schema -d ./schema/mysql/v8/temporal/versioned -v 1.15

MySQL Configuration Options

persistence:
  datastores:
    default:
      sql:
        pluginName: "mysql8"
        databaseName: "temporal"
        connectAddr: "mysql-host:3306"
        connectProtocol: "tcp"
        user: "temporal"
        password: "temporal"
        maxConns: 20
        maxIdleConns: 20
        maxConnLifetime: "1h"

PostgreSQL Setup

Prerequisites

  • PostgreSQL 12 or higher
  • Sufficient privileges to create databases and users

Database Creation

1

Create databases

CREATE DATABASE temporal;
CREATE DATABASE temporal_visibility;
2

Create user and grant permissions

CREATE USER temporal WITH PASSWORD 'temporal';
GRANT ALL PRIVILEGES ON DATABASE temporal TO temporal;
GRANT ALL PRIVILEGES ON DATABASE temporal_visibility TO temporal;
3

Initialize schema for persistence store

# Set version tables
./temporal-sql-tool \
  --ep postgres-host \
  --port 5432 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin postgres12 \
  setup-schema -v 0.0

# Apply schema
./temporal-sql-tool \
  --ep postgres-host \
  --port 5432 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin postgres12 \
  update-schema --schema-name postgresql/v12/temporal
4

Initialize schema for visibility store

# Set version tables
./temporal-sql-tool \
  --ep postgres-host \
  --port 5432 \
  --user temporal \
  --password temporal \
  --db temporal_visibility \
  --plugin postgres12 \
  setup-schema -v 0.0

# Apply schema
./temporal-sql-tool \
  --ep postgres-host \
  --port 5432 \
  --user temporal \
  --password temporal \
  --db temporal_visibility \
  --plugin postgres12 \
  update-schema --schema-name postgresql/v12/visibility

PostgreSQL Configuration

persistence:
  datastores:
    default:
      sql:
        pluginName: "postgres12"  # or "postgres12_pgx" for better performance
        databaseName: "temporal"
        connectAddr: "postgres-host:5432"
        connectProtocol: "tcp"
        user: "temporal"
        password: "temporal"
        maxConns: 20
        maxIdleConns: 20
        maxConnLifetime: "1h"
        tls:
          enabled: false
    visibility:
      sql:
        pluginName: "postgres12"
        databaseName: "temporal_visibility"
        connectAddr: "postgres-host:5432"
        connectProtocol: "tcp"
        user: "temporal"
        password: "temporal"
        maxConns: 10
        maxIdleConns: 10
        maxConnLifetime: "1h"
Use postgres12_pgx instead of postgres12 for better performance with the pgx driver.

Cassandra Setup

Prerequisites

  • Cassandra 3.11 or higher
  • Replication factor of 3 recommended for production

Keyspace Creation

1

Build temporal-cassandra-tool

git clone https://github.com/temporalio/temporal.git
cd temporal
make temporal-cassandra-tool
2

Create keyspace

# For development (SimpleStrategy)
./temporal-cassandra-tool \
  --ep cassandra-host \
  create -k temporal --rf 1

# For production (NetworkTopologyStrategy)
./temporal-cassandra-tool \
  --ep cassandra-host \
  create -k temporal \
  --replication-strategy NetworkTopologyStrategy \
  --replication-factors "datacenter1:3,datacenter2:3"
3

Initialize schema

# Set version tables
./temporal-cassandra-tool \
  -ep cassandra-host \
  -k temporal \
  setup-schema -v 0.0

# Apply schema
./temporal-cassandra-tool \
  -ep cassandra-host \
  -k temporal \
  update-schema -d ./schema/cassandra/temporal/versioned

Cassandra Configuration

persistence:
  datastores:
    default:
      cassandra:
        hosts: "cassandra-host1,cassandra-host2,cassandra-host3"
        keyspace: "temporal"
        port: 9042
        user: "cassandra"
        password: "cassandra"
        maxConns: 20
        tls:
          enabled: false
          caFile: ""
          certFile: ""
          keyFile: ""
          enableHostVerification: false
          serverName: ""
Cassandra is not supported for visibility stores. You must use MySQL, PostgreSQL, or Elasticsearch for visibility.

Schema Tables

Key Cassandra tables created:
  • executions - Workflow execution state and history
  • namespaces - Namespace metadata
  • queue - Task queue information
  • cluster_metadata - Cluster configuration
  • tasks - Transfer, timer, and replication tasks

Elasticsearch Setup

Elasticsearch provides advanced visibility features including full-text search and custom search attributes.

Prerequisites

  • Elasticsearch 7.10+ or 8.x
  • At least 2GB heap size recommended

Installation

docker run -d \
  --name elasticsearch \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  elasticsearch:7.10.1

Elasticsearch Configuration

persistence:
  visibilityStore: es-visibility
  datastores:
    es-visibility:
      elasticsearch:
        version: "v7"  # or "v8"
        logLevel: "error"
        url:
          scheme: "http"
          host: "elasticsearch-host:9200"
        indices:
          visibility: "temporal_visibility_v1_dev"
        closeIdleConnectionsInterval: "15s"

Index Templates

Temporal automatically creates index templates on startup. No manual index creation is required.
Elasticsearch indices are automatically created with appropriate mappings when Temporal Server starts.

SQLite Setup (Development Only)

SQLite is only for local development. Never use in production.

Configuration

persistence:
  numHistoryShards: 4
  defaultStore: sqlite-default
  visibilityStore: sqlite-visibility
  datastores:
    sqlite-default:
      sql:
        pluginName: "sqlite"
        databaseName: "/tmp/temporal.db"
        connectAddr: "localhost"
        connectProtocol: "tcp"
    sqlite-visibility:
      sql:
        pluginName: "sqlite"
        databaseName: "/tmp/temporal_visibility.db"
        connectAddr: "localhost"
        connectProtocol: "tcp"

Database Schema Reference

Persistence Store Tables

namespaces
  • Stores namespace metadata
  • Contains retention policies and archival settings
shards
  • One row per history shard
  • Stores shard ownership and range IDs
executions
  • Workflow execution state and mutable data
  • Activity and timer states
current_executions
  • Current/latest execution for each workflow ID
  • Used for uniqueness constraints

Visibility Store Tables

executions
  • Workflow execution metadata for search
  • Custom search attributes
  • Start/close time, status, workflow type
custom_search_attributes
  • User-defined search attribute definitions

Connection Pool Tuning

Calculating Pool Sizes

For SQL databases:
# Formula: (Number of service instances) × (Connections per instance)
# Should not exceed database max_connections

# Example: 3 Frontend + 3 History + 2 Matching + 1 Worker = 9 instances
# Default 20 connections × 9 instances = 180 total connections

# Adjust per instance:
maxConns: 15           # Reduces to 135 total
maxIdleConns: 10       # Keep fewer idle connections
maxConnLifetime: "30m" # Shorter lifetime for better load distribution

Performance Tuning

# Optimize for high request rate
sql:
  maxConns: 50
  maxIdleConns: 25
  maxConnLifetime: "15m"

Database Maintenance

Backup Strategies

# Full backup
mysqldump -u temporal -p \
  --single-transaction \
  --routines \
  --triggers \
  temporal > temporal_backup.sql

mysqldump -u temporal -p \
  --single-transaction \
  temporal_visibility > temporal_visibility_backup.sql

# Point-in-time recovery
# Enable binary logging in my.cnf:
# log-bin = mysql-bin
# binlog-format = ROW
# Full backup
pg_dump -U temporal temporal > temporal_backup.sql
pg_dump -U temporal temporal_visibility > temporal_visibility_backup.sql

# Continuous archiving
# Configure postgresql.conf:
# wal_level = replica
# archive_mode = on
# archive_command = 'cp %p /archive/%f'
# Snapshot
nodetool snapshot temporal

# Find snapshot directory
find /var/lib/cassandra/data/temporal -name snapshots

# Backup snapshot files
tar -czf temporal_snapshot.tar.gz \
  /var/lib/cassandra/data/temporal/*/snapshots

Monitoring

Key metrics to monitor:
  • Connection Pool: Active connections, idle connections, wait time
  • Query Latency: P50, P95, P99 for reads and writes
  • Error Rate: Connection errors, query failures
  • Disk Usage: Database size, growth rate
  • Replication Lag: For replicated setups

Troubleshooting

Error: “schema version mismatch”Solution:
# Check current version
mysql -u temporal -p temporal -e "SELECT * FROM schema_version;"

# Apply missing updates
./temporal-sql-tool \
  --ep mysql-host \
  --port 3306 \
  --user temporal \
  --password temporal \
  --db temporal \
  --plugin mysql8 \
  update-schema -d ./schema/mysql/v8/temporal/versioned
Error: “Too many connections” or connection pool exhaustedSolutions:
  1. Reduce maxConns in configuration
  2. Increase database max_connections
  3. Scale down number of service instances
  4. Check for connection leaks
Symptoms: High query latency, timeoutsSolutions:
  1. Enable slow query log
  2. Check database indexes
  3. Analyze query execution plans
  4. Consider read replicas for visibility queries
  5. Tune database parameters (buffer pool, cache)

Build docs developers (and LLMs) love