One app, every region.
Local reads and writes.
Serve Europe and North America from one Redelay app. Each member's data lives in their home region, so their requests never wait for an ocean — and the framework gives you the pieces, opt-in, without changing a single-region app.
One rule: a write waits for its majority partner
MongoDB, Kafka, ClickHouse Keeper and Redis failover all commit once a majority has the write. A write is as fast as the nearest member of that majority — so keep the majority inside the region that writes, and never let it wait across an ocean.
NA member, primary in Europe
NA member, home-region shard
Built on measured round trips
Three numbers decide everything on this page, and all three are measured on a real deployment over its private network (October 2026). The architecture keeps each write's majority inside the first one.
measured between two North-American servers; the same holds for two servers in, say, Germany
measured between four European and two North-American servers
measured; it only votes in an election and is never on the write path
One request by a North-American member
Two reads of their own data and three majority writes. The model counts network round trips only (optimistic: no server time, TLS or queueing). Where we have a benchmark of the whole request, it is shown next to the model.
One replica set, primary in Europe
Every read and write crosses the ocean; each write also waits for the primary's partner next door.
+ local reads (Nearest + recent-write marker)
Reads come from the North-American secondary; the marker sends a member back to the primary only right after their own write. Writes still cross the ocean.
Home-region shards
The member's rows live on a shard whose primary and majority partner are both in North America.
A European member's request costs the same ~7 ms in both layouts. With home regions, North America gets the same — instead of ~612 ms. The ~13 ms measurement is the whole request through the shard router on our two-region benchmark.
What each datastore does
The same rule, applied store by store. Every piece is opt-in: an app without HOME_REGIONS behaves exactly as before.
MongoDB — zone shards
Member-owned collections are sharded on {home_region, user_id}; each zone's shard has its primary and write majority in its region. Global data (catalogue, CMS, settings) stays on the primary shard and is read from local secondaries.
rtmongo.Owner · StampOwner · OwnFilter · ShardByRegionRead your own writes
A recent-write marker on the shared Redis Cluster is set on every master and checked on this node's master — no cross-region hop. A member who just wrote reads the primary for a few seconds; everyone else reads locally.
rtmongo.Nearest · recentwrite.InstallFromEnvKafka — replicas in one region
acks=all waits for every in-sync replica, so a replica across the ocean costs ~1 s per publish. Topics are placed on one region's brokers by rack; publishing goes through the transactional outbox, off the request path.
KAFKA_TOPIC_RACK · go-events/outboxVector search — regional copies
Versioned index snapshots are copied to each region and read locally; a copy that is behind falls back to the writer. Measured: 120 ms from NA to the EU writer, 2.1 ms to the local copy.
QDRANT_READ_URL · QDRANT_REPLICA_URLS · PublishVersionClickHouse & cache — per node
Analytics and cache are node-local ({{.Node.Hostname}}), with batched inserts and the nearest Keeper. Nothing on the request path waits for another region.
clickhouse-{{.Node.Hostname}} · redis-{{.Node.Hostname}}Placement & rollouts
Nodes carry region=eu|na labels; regional services run one task per node, stop-first, with a 30 s pre-drain so the load balancer finds the replacement before the old task stops. Zero 502s across a rolling deploy.
node.labels.region · SHUTDOWN_PREDRAIN_SECONDSWhen the ocean splits
Each zone's shard has its majority inside its own region, so a cut across the Atlantic leaves both regions writing their own members' data. Identity (accounts, sign-in) and other global writes stay with the main region's primary and wait for the link. A tie-breaker vote at a third site keeps a region's majority when one of its nodes is down — and never slows a normal write, because a majority of three is the primary plus the fastest acknowledgement.
- Members keep reading and writing their own data in their home region
- Global data is served from local secondaries
- Events go through the outbox to the region's own brokers
- New sign-ups and global edits wait for the link
Turning it on
Name the regions and the countries that belong to them. A member gets a home region once, at sign-up, from their country; their access token carries it, so every request knows where their data lives without a lookup. Stores opt in one by one.
Any number of regions — the framework is tested with three (eu, na, apac) on a real sharded cluster, and every helper is a no-op until you set HOME_REGIONS.
# every node
HOME_REGIONS="na=US,CA"
HOME_REGION_DEFAULT=eu
// a member-owned store: new rows carry the
// owner's region (the shard key)
favs := crud.NewMongoCRUD[Fav](db.Collection("favs")).
WithOwnerRegion(func(f Fav) string { return f.UserID })
// reads: local, fresh after the member's own write
ctx = rtmongo.Nearest(ctx)
// operator step, once per collection
rtmongo.CheckShardable(ctx, favs.Collection(), "user_id")
rtmongo.ShardByRegion(ctx, favs.Collection(), "user_id", "eu", "na")Why home regions
A member's own data is written by that member, almost always from one place. Putting it in their region gives local writes without the conflicts of multi-master — and keeps one app, one identity and one catalogue.
| Approach | Writes | Reads | Ocean split | In short |
|---|---|---|---|---|
| One primary, replicas everywhere | ocean round trip on every write | local, but stale after your own write | far region read-only | simple, slow for half your users |
| Multi-master database | local | local | both write — conflicts to resolve later | fast, but last-writer-wins on member data |
| A separate app per region | local | local | independent | no global accounts, catalogue or analytics |
| Redelay home regions | local (member's own shard) | local, fresh after your own write | each region keeps writing its members' data | one app, one identity, no conflicts |