John Weldon

Transparent Multi-Region Routing with NATS Subject Mappings

A customer running a NATS super-cluster across two regions hit a constraint: they couldn’t import the same subject from two regional accounts into their cross-region account. NATS enforces subject import uniqueness per account – you can’t have two sources for the same subject.

The usual workarounds are bad. Region-specific subjects (payments.region1.process) force applications to be region-aware. A central routing proxy adds latency and a single point of failure.

Subject mappings solve this cleanly: applications use one subject everywhere, and server-side configuration handles the routing per region.1

The Problem

The customer had a super-cluster with three clusters connected via gateways:

The constraint: a NATS account can only import a given subject from one source. They couldn’t import payments.process from both regions.

The Solution

Subject mappings are scoped per server configuration. This means mappings in Region 1’s server config only apply to connections in Region 1. The same subject maps to different destinations depending on where the client is connected.

Region 1 server config:

accounts {
    REGION1 {
        mappings = {
            "payments.process": [
                {dest: "payment-processor-region1.echo", weight: 100%}
            ]
        }
    }
}

Region 2 server config:

accounts {
    REGION2 {
        mappings = {
            "payments.process": [
                {dest: "payment-processor-region2.echo", weight: 100%}
            ]
        }
    }
}

Cross-region server config (primary routes to Region 1):

accounts {
    CROSS_REGION {
        mappings = {
            "payments.process": [
                {dest: "payment-processor-region1.echo", weight: 100%}
            ]
        }
    }
}

Applications publish to payments.process regardless of which region they’re deployed in. The mapping routes the request to the correct regional service transparently.

Failover

Under normal operation, the cross-region mapping routes to Region 1. For failover to Region 2, change the mapping destination and reload:

# cross-region/server.conf -- after failover
mappings = {
    "payments.process": [
        {dest: "payment-processor-region2.echo", weight: 100%}
    ]
}
nats-server --signal reload

No application restarts. No client reconnections. The next request routes to Region 2. Failing back is the same operation in reverse.

Why This Works

The key insight: subject mappings are not global. They are scoped to the account definition in each server’s configuration file. Since each cluster has its own server config, the same account can have different mappings in different regions.

This means:

The application binary is identical across all regions. Zero configuration changes per deployment. Routing is an infrastructure concern, not an application concern.

When to Use This Pattern

This pattern fits when:

It does not replace JetStream cross-domain sourcing for stream replication. Subject mappings operate at the request/reply and pub/sub level – they route individual messages, not replicate streams.


  1. Subject Mapping and Partitioning - Mappings are defined per account in the server config. Weighted mappings support traffic splitting and canary deployments. ↩︎