Skip to content

Scaling & High Availability

This guide covers horizontal scaling, database replication, and high availability patterns for Sorcha deployments.

Service Classification

Stateless Services (Scale Freely)

These services hold no local state and can be scaled horizontally without coordination:

ServiceNotes
API GatewayYARP reverse proxy. Scale behind a load balancer.
Blueprint ServiceWorkflow engine. SignalR uses Redis backplane for multi-instance.
Tenant ServiceAuth and JWT issuance. Tokens are self-contained.
Validator ServiceChain validation. Read-only MongoDB access for governance data.
Peer ServiceP2P networking. Each instance needs a unique PeerService__NodeId.

Stateful Services (Scale with Care)

ServiceStateScaling Considerations
Wallet ServiceEncryption keys stored in volumeKeys must be accessible to all instances. Use shared volume or external key vault.
Register ServiceMongoDB per-register databasesMultiple instances can share MongoDB. Ensure write coordination.

Horizontal Scaling

Docker Compose Scaling

Scale stateless services using Docker Compose:

bash
# Scale Blueprint Service to 3 instances
docker-compose up -d --scale blueprint-service=3

# Scale multiple services
docker-compose up -d --scale blueprint-service=3 --scale tenant-service=2

When scaling services, the API Gateway load-balances across instances automatically using Docker DNS round-robin.

Scaling Architecture

                    ┌──────────────────┐
                    │  Load Balancer   │
                    │  (external)      │
                    └────────┬─────────┘

              ┌──────────────┼──────────────┐
              v              v              v
        ┌───────────┐ ┌───────────┐ ┌───────────┐
        │ API GW #1 │ │ API GW #2 │ │ API GW #3 │
        └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
              │              │              │
              └──────────────┼──────────────┘
                             │  Docker DNS
              ┌──────────────┼──────────────┐
              v              v              v
        ┌───────────┐ ┌───────────┐ ┌───────────┐
        │Blueprint#1│ │Blueprint#2│ │Blueprint#3│
        └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
              │              │              │
              └──────────────┼──────────────┘

                    ┌────────v─────────┐
                    │ MongoDB / Redis  │
                    │  (shared state)  │
                    └──────────────────┘

Per-Service Scaling Notes

API Gateway:

  • Fully stateless. Scale as needed behind an external load balancer.
  • Each instance maintains its own YARP routing table (loaded from configuration).
  • Redis is used for shared DataProtection keys.

Blueprint Service:

  • SignalR connections use Redis as a backplane, so clients can connect to any instance.
  • Blueprint execution is stateless -- workflow state is stored in MongoDB.

Tenant Service:

  • JWT tokens are self-contained and validated by any service with the signing key.
  • Session state (if any) is stored in Redis.
  • Database migrations should only run on one instance at startup.

Validator Service:

  • Read-only access to Register MongoDB. Safe to scale.
  • Each instance needs a unique Validator__ValidatorId.

Wallet Service:

  • The wallet-encryption-keys volume must be shared across instances.
  • Consider migrating to Azure Key Vault or HashiCorp Vault for multi-instance deployments.
  • PostgreSQL handles concurrent access natively.

Database Replication

PostgreSQL Streaming Replication

For high availability, configure PostgreSQL with streaming replication:

yaml
# Primary (in docker-compose.override.yml)
postgres-primary:
  image: postgres:17-alpine
  environment:
    POSTGRES_USER: sorcha
    POSTGRES_PASSWORD: <secure-password>
    POSTGRES_DB: sorcha
  command: >
    postgres
    -c wal_level=replica
    -c max_wal_senders=3
    -c max_replication_slots=3

# Replica
postgres-replica:
  image: postgres:17-alpine
  environment:
    PGUSER: replicator
    PGPASSWORD: <replication-password>
  command: >
    bash -c "
    pg_basebackup -h postgres-primary -D /var/lib/postgresql/data -U replicator -v -P -W
    && postgres
    "

For production, consider managed PostgreSQL services (Azure Database for PostgreSQL, AWS RDS, etc.) that handle replication automatically.

MongoDB Replica Sets

MongoDB replica sets provide automatic failover:

yaml
# In docker-compose.override.yml
mongodb:
  image: mongo:8
  command: ["--replSet", "rs0"]
  environment:
    MONGO_INITDB_ROOT_USERNAME: sorcha
    MONGO_INITDB_ROOT_PASSWORD: <secure-password>

mongodb-secondary:
  image: mongo:8
  command: ["--replSet", "rs0"]
  depends_on:
    - mongodb

Initialize the replica set:

bash
docker exec sorcha-mongodb mongosh -u sorcha -p <password> --eval '
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongodb:27017", priority: 2 },
    { _id: 1, host: "mongodb-secondary:27017", priority: 1 }
  ]
})'

Update connection strings to include replica set:

mongodb://sorcha:<password>@mongodb:27017,mongodb-secondary:27017/?replicaSet=rs0

Redis Clustering

For high availability Redis:

yaml
# Redis with persistence and replication
redis:
  image: redis:8-alpine
  command: >
    redis-server
    --appendonly yes
    --requirepass <redis-password>

redis-replica:
  image: redis:8-alpine
  command: >
    redis-server
    --replicaof redis 6379
    --requirepass <redis-password>
    --masterauth <redis-password>

For production, consider Redis Sentinel or Redis Cluster for automatic failover, or a managed service (Azure Cache for Redis, AWS ElastiCache).

Load Balancer Patterns

External Load Balancer

Place an external load balancer (nginx, HAProxy, Azure Application Gateway, AWS ALB) in front of multiple API Gateway instances:

Internet --> Load Balancer (TLS termination)
                --> API Gateway #1 (HTTP)
                --> API Gateway #2 (HTTP)
                --> API Gateway #3 (HTTP)

nginx example:

nginx
upstream sorcha_gateway {
    server gateway-1:80;
    server gateway-2:80;
    server gateway-3:80;
}

server {
    listen 443 ssl;
    server_name sorcha.example.com;

    ssl_certificate     /etc/ssl/sorcha.crt;
    ssl_certificate_key /etc/ssl/sorcha.key;

    location / {
        proxy_pass http://sorcha_gateway;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support for SignalR
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Session Affinity

SignalR long-polling connections require session affinity (sticky sessions) if not using WebSockets. The Redis backplane handles WebSocket connections across instances without affinity.

Production Deployment Recommendations

ComponentDevelopmentSmall ProductionLarge Production
API Gateway1 instance2 instances3+ instances
Blueprint1 instance1-2 instances3+ instances
Tenant1 instance2 instances2+ instances
Wallet1 instance1 instance2 (with shared key vault)
Register1 instance1-2 instances3+ instances
Validator1 instance1 instance2+ instances
Peer1 instance1 instancePer-network topology
PostgreSQLSinglePrimary + replicaManaged service
MongoDBSingleReplica set (3 nodes)Managed service
RedisSingleSentinel (3 nodes)Managed service / Cluster

Released under the MIT License.