Skip to main content

Multi-region high availability

Catalyst keeps your workflows and agents running when a pod restarts, a node fails, or an availability zone goes down. None of that helps when the whole cloud region goes down: the cluster, its database, and its gateway are all in the same place.

A region group solves that. It connects several Catalyst regions to one replicated database, so a project can survive the loss of an entire region. A project created in the group is placed in every member region and keeps one set of hostnames. Your applications hold a single endpoint and never learn which region is serving them.

A group is active/passive. One member runs against the database that accepts writes — the active member — and it serves all traffic. The others run against read-only replicas. They stay deployed and keep their applications running, but they take over only once their database is promoted.

At a glance

PropertyWhat to expect
ModelActive/passive. Exactly one member accepts writes at a time and serves all traffic.
Traffic failoverAutomatic. The load balancer follows the health check, within about 20 seconds.
Database failoverManual. You promote the replica — and that is deliberate.
RPO — how much data you loseThe replication lag at the moment you promote. Replication is asynchronous, so Catalyst does not bound it. Every passive member exports cra_region_replication_lag_seconds, so you know your exposure in advance.
RTO — how long recovery takesMostly how long you take to decide to promote. The traffic move is the smaller half, at about 20 seconds.
What fails overCatalyst's own state: projects, components, secrets, workflow history, jobs and reminders.
What does notAnything regional your project depends on — a pub/sub broker, a rate-limit store. Those must be multi-region in their own right.
What your applications seeOne endpoint and one set of hostnames, before and after a failover. No redeploy, no configuration change.
CostRoughly double a single region. A passive member is a full region and serves no traffic.
Available onSelf-managed BYOC regions today.
Plan the group before its regions hold anything

Every member must be a region of the same type, and empty when it joins. You cannot move an existing project into a group, and you cannot move a project out of one. Today the documented path is self-managed BYOC regions, where you own the database, the keys, and the network that make a group work. For Dedicated Cloud or managed BYOC regions, contact Diagrid.

When to use a region group

Your goalUse this
Keep a project running through the loss of a whole cloud regionA region group
Survive the loss of a node or an availability zone inside one regionHigh availability in that region
Serve users closer to them, or add throughputIndependent regions
Isolate environments, business units, or data residency domainsIndependent regions

A region group is a disaster recovery mechanism, not a performance one. A passive member serves no traffic, so a group adds no throughput and shortens no round trip. It also does not replace high availability inside a region — most deployments want both.

What survives what

Each column adds to the one before it. A region group is the last column, and it is the only one that covers the loss of a whole cloud region.

FailureSingle region, default installSingle region with HARegion group
A pod restarts or crashesSurvivesSurvivesSurvives
A Kubernetes node is lostSurvives, with a gap in serving while single-replica pods rescheduleSurvivesSurvives
An availability zone is lostOutage until the zone returnsSurvivesSurvives
A cloud region is lostOutage until the region returnsOutage until the region returnsSurvives, once you promote the replica
A dependency of your own is lost — a pub/sub broker, a rate-limit storeOutageOutageOutage. A group fails over Catalyst's state, not your broker

Workflows and agents are durable in every column: they are recorded as they run, so they resume wherever the platform comes back rather than starting over. What the columns differ on is how long the platform is unavailable in between.

"Single region with HA" means gateway.ha.enabled: true and the external PostgreSQL and Kafka deployed across availability zones, as Production planning describes. A region group does not replace that. A member region without HA of its own still fails over for a zone outage rather than riding through it, which costs you a promotion and whatever had not replicated. Most production deployments want both.

Recovery starts with a decision you make, not one the platform makes for you. See Failover: what is automatic and what is not.

Budget for a second full region

A passive member is a full region. It runs the same data plane at the same size and serves no traffic, so a two-region group roughly doubles the infrastructure cost of a single region.

Architecture

Every member of a group publishes a health check endpoint on its gateway, at /diagrid/region/writable. It answers:

ResponseMeaning
200This region's database accepts writes. It is the active member.
503This region runs against a read-only replica. It is a passive member.
404This region is not in a group.

You put a global load balancer in front of the member regions, point its health check at that path, and it sends traffic to whichever member answers 200.

The database is therefore the only authority on which region is active. Catalyst observes what the database reports and publishes it. It never changes it.

What a failover does to running work

Your workflows, agents, jobs and reminders live in the group's replicated database, so a passive member already holds them. When it is promoted it continues them from the history it has replicated, and what that history contains at that moment determines everything below.

  • Workflows and agents continue where the replica had them. The promoted member replays each one from its recorded history and carries on. Nothing is lost that had replicated, and nothing has to be resubmitted.
  • Whatever had not replicated is gone. Replication is asynchronous, so a workflow that advanced in the seconds before the failover resumes from its last replicated step. The activities after that step run again.
  • Activities, jobs and reminders run at least once. A job the old member had dispatched but not recorded as complete fires again in the promoted one. Catalyst is never told that a promotion has occurred, so nothing suppresses the duplicate. Handlers must be idempotent — this is the one thing a region group asks of your application code.
  • Calls in flight fail. They reach a database that no longer accepts writes, or a region traffic has already left. The SDKs retry them, and they succeed against the promoted member once it is writable.
  • Resources created mid-failover stay pending. A project, component or App ID created while the group has no writer is provisioned once writes are accepted again.
  • Signed workflow history needs one PKI root for the whole group. If a project signs its workflow history and the members do not share a root CA, a workflow resumed in another member fails signature verification and is tombstoned as tampered rather than retried. That root cannot be added after the regions join — see Requirements.

Your applications hold one endpoint throughout and never learn that any of this happened.

Failover: what is automatic and what is not

Catalyst never promotes a database, never demotes one, and never moves traffic between members.

  • The traffic half of a failover is automatic. The health check turns and the load balancer follows.
  • The database half is not. You promote the replica.

That split is deliberate, for three reasons:

  • A promotion cannot be undone. Databases promote a replica so it accepts writes. They do not turn a writer back into a replica. The only way back is to destroy that region's database and rebuild it as a replica of the new one.
  • Unreachable is not the same as lost. A health check that has failed for two minutes can mean a gateway restart, an agent restart during a control plane outage, or a network partition between the checker and a region that is serving its clients perfectly well.
  • Nothing fences the old writer. An unnecessary promotion leaves two databases accepting writes and diverging, with no way to reconcile them afterwards. The risk of over-eager automation here is not downtime. It is two sources of truth.

You can automate the promotion, and you should if your RTO demands it. Build an automation that waits longer, requires more than one signal, and refuses to promote a replica whose replication lag it cannot read.

Requirements

A group is more than two regions configured alike. The settings below must match in every member. The control plane enforces none of them, and a misconfigured group looks healthy until a failover exposes it.

SettingWhere you set itWhat it must beWhat happens if it differs
The databaseagent.config.project.external_postgresqlOne PostgreSQL cluster replicated across the regions, with a single writer. Each region points at the instance next to it.The members hold unrelated data, and a failover recovers nothing.
Database name and userYour infrastructureIdentical in every member, character for character.A promotion becomes a database replacement.
The secrets providerglobal.secrets.providerpostgresql in every member, so secrets live in the replicated cluster instead of in one cluster's Kubernetes.The promoted region cannot read any secret the other one stored. Components fail to initialize, and there is nothing to recover from.
The key encryption keyglobal.secrets.postgresql.kek_providerThe same key identity, named the same way, at the same key version.The replicated secret rows are present, but the promoted region cannot decrypt them.
The Dapr scheduler backendagent.config.internal_dapr.schedulerThe PostgreSQL backend, on the replicated cluster.Every job and actor reminder is lost at the moment of the failover.
The Dapr PKI root — optionalagent.config.internal_dapr.pkiOne root CA, with a per-region intermediate. Needed only if a project in the group signs its workflow history, and it cannot be added after the regions join.A workflow resumed in another member fails signature verification, and is tombstoned as tampered rather than retried.

The control plane compares two of these and warns without refusing: the key encryption key, and the Dapr trust anchors. Read the group's status after creating it, and before creating any project in it:

diagrid regiongroup get my-group --output json | jq '.status'

An empty .status.messages is the expected result. The status also names which member currently accepts writes.

Deploy a region group on your cloud

A region group is not tied to any one cloud. Catalyst asks the platform underneath for four primitives, and any platform that offers them can host a group.

PlatformStatus
AWSDocumented end to end, with Terraform. AWS multi-region deployment builds the four primitives as an RDS cross-region read replica, a KMS multi-region key, and AWS Global Accelerator.
AzureSupported. No guide yet — build the four primitives yourself, or contact Diagrid. The Azure deployment guide covers the single-region parts.
Other clouds and on-premises KubernetesSupported. Any platform providing the four primitives can host a group. Contact Diagrid if you want one reviewed before you build it.

The four primitives:

  • A PostgreSQL deployment that replicates across regions, and whose replica can be promoted to accept writes.
  • A global load balancer across the member regions that health-checks /diagrid/region/writable and sends traffic to the member answering 200. Prefer one whose address does not change on a failover: the Dapr SDKs hold long-lived gRPC connections that a DNS change would not disturb.
  • A key encryption key both regions can use, held outside either cluster.
  • One wildcard domain and certificate, served by every member, so the group's projects have one set of hostnames.

Anything else a project depends on that is regional — a pub/sub broker, a rate-limit store — has to be multi-region in its own right. A group fails over Catalyst's own state, not your message broker.

Failover and failback

Every failover is the same two actions, in one order or the other:

  • Promote the replica in the member that is taking over. It begins answering 200, and the load balancer starts sending it traffic.
  • Rebuild the other member as a replica: destroy its database and rebuild it from the new writer. This is the only demotion available.

Planned failover

Drain the active member first, so writes stop before the database role moves rather than as a consequence of it: drain, promote, let traffic follow, rebuild the drained member as a replica, then undrain it.

Unplanned failover

Skip the drain, because traffic has already left the lost region — its health check stopped passing. But it has not arrived anywhere else: until you promote, the group has no writer and no healthy endpoint.

Promoting has three consequences:

  • Whatever had not replicated is lost.
  • Writes in flight fail, and are retried by the client.
  • A job or reminder can fire a second time, so handlers must be idempotent.

The lost region comes back holding a writer that was never demoted. Keep traffic off it until you have rebuilt it as a replica.

Failback

Failing back is the planned procedure again with the members swapped. Catalyst has no asymmetry here: whichever member holds the writer is the active one.

Recovery objectives (RPO and RTO)

ObjectiveWhat determines itHow to measure it
Catalyst publishes no RPO or RTO figure for a region group, because neither is Catalyst's to set. Both follow from your replication lag and your own promotion procedure, so measure them rather than assume them.
ObjectiveWhat determines itHow to measure it
RPO — how much data a failover losesThe replication lag at the moment you promote. Cross-region replication is asynchronous, so Catalyst does not bound it.cra_region_replication_lag_seconds, exported by every passive member. Alert on it, so you know your exposure before a failover rather than after it.
RTO — how long recovery takesMostly how long you take to decide to promote. The traffic move is the smaller half: the health check follows the write status within about 20 seconds.cra_region_writable reports which member accepts writes.

A worked example

A group whose replication lag sits under 5 seconds, operated by a team that takes 10 minutes to confirm the outage and start the promotion:

TimeWhere it goes
RPO~5 seconds of workWhatever had not replicated when the region was lost. Workflows resume from their last replicated step and re-run the activities after it.
RTO~11 minutes~10 minutes deciding, under a minute promoting, then about 20 seconds for the health check to turn and traffic to follow.

Decision time dominates, which is why the number to improve first is your runbook rather than your infrastructure. Measure your own during the rehearsal below: record the wall-clock time from "we decided to promote" to "the first write succeeded in the new region".

Rehearse a failover on a group that is not serving production before you depend on it. A full round trip rebuilds both members' databases, so plan the rehearsal as maintenance rather than as something you can undo.

Limitations

  • The group is not multi-writer. Exactly one member accepts writes at a time, and it is your responsibility to restore that state after a failover has briefly broken it.
  • Nothing promotes automatically, and nothing demotes at all. The only demotion is rebuilding a member's database as a replica of the current writer.
  • A passive member is a full region. Size every member identically. It runs the same data plane and has to carry the same load.
  • You decide membership before the projects exist. A region must be empty to join, a region in a group takes no standalone projects, and neither a project nor a region can change side afterwards.
  • Regional services are not failed over. Pub/sub brokers and rate-limit counters are regional. A project in a group brings its own multi-region broker.

Next steps