Skip to content

Region catalogue

Beyond a handful of regions, “code and label” no longer suffices: each region comes with the domains under which it is reachable and the lifetimes that apply to it. That lives in one JSON file holding all regions, rolled out identically to every node.

Terminal window
DNSE_REGIONS_FILE=/etc/dnseditor-cloud/regions.json

Without the variable that exact path is expected. If nothing is there, the old path still applies (DNSE_REGIONS, or the built-in catalogue) — an existing installation keeps running unchanged.

{
"session": { "ttl_hours": 12, "touch_interval_minutes": 5 },
"dns": { "frontend_ttl_seconds": 60, "backend_ttl_seconds": 60 },
"ui": { "region_hint": false },
"frontends": ["dns-editor.net"],
"regions": [
{
"code": "eu",
"label": "Europe",
"frontends": ["dns-editor.de", "dns-editor.eu"],
"backends": ["eu01.dns-editor.net", "eu02.dns-editor.net", "eu03.dns-editor.net"],
"dyndns": "dyn.eu.dns-editor.net",
"agents": "agent.eu.dns-editor.net"
}
]
}
FieldMeaning
coderegion code; an organisation’s home region is stored by it
labeldisplay name; the code stands in when it is missing
frontendsthe entry domains assigned to this region
backendsthe region’s API domains, in order
dyndns / agentsthe addresses handed out by the setup dialogs
frontends (top level)entry domains without a region
session.ttl_hourslifetime of a login
session.touch_interval_minutesminimum gap between two refreshes of the session lifetime
dns.*_ttl_secondsTTL of our own DNS records
ui.region_hintallows the hint about a more suitable entry domain (default: off)

None of this is hard-coded — codes, labels, the number of regions and every domain come from the file. Every field except code is optional; missing values are filled with the defaults. A domain that is not there shows up in the operator panel as “not set” — it is not invented.

An example lives in the repository at packaging/regions.example.json.

The first entry is the preferred target, the rest are the fallback within the same region. That is why individual names stand here instead of one name with several addresses: leaving the switch to the browser would make it neither steerable nor visible when it happens.

For dyndns and agents it is the opposite — exactly one address. A router or a cert agent is set up once and never changes its address by itself; resilience belongs in DNS there (one name, several addresses), not in a list nobody reads.

A neutral name pointing at addresses in every region belongs under frontends at the top level. The interface can look nothing up for it — so it measures which backend answers fastest.

It must be listed all the same: otherwise its origin is unknown to the API, and the browser rejects every call.

The former single fields frontend and backend are still read and moved to the front of the lists. A catalogue from before therefore keeps working; an update forces no rollout.

It assigns an entry domain to the region, but it does not bind the user: whoever arrives via dns-editor.com stays there. What changes is only which backend the interface talks to — and that follows the organisation’s home region, not the chosen entry domain.

Without placement a region is a column value. Rows do carry the right region, but they sit wherever the database puts them — a promise we could not keep towards customers.

"placement": "ts_eu" names an existing tablespace that ORM++ binds the region’s partitions to:

{ "code": "eu", "label": "Europe", "placement": "ts_eu", "frontends": ["dns-editor.de"] }

All or nothing. If one region carries a placement and another does not, the node refuses to start. That is the real decision behind the check: a half-filled catalogue would be the worst position — the installation would come up, some regions would sit in the right place and the rest in the unbound DEFAULT partition, invisibly so, because the geo column says the same thing either way. Two regions sharing one tablespace are rejected as well.

Operations create the tablespaces, not the application

Section titled “Operations create the tablespaces, not the application”

Replica count and placement blocks are an operational decision. ORM++ therefore creates no tablespaces — it only verifies that the declared one exists, and aborts the migration otherwise, before any DDL runs (ErrPlacementNotFound).

The binary prints the matching statements:

Terminal window
dnseditor-cloud tablespaces --replicas 3 --zones "eu=eu-1a,eu-1b,eu-1c"

It prints them and does not execute them — that step belongs to someone doing it deliberately. The reason follows.

The binding is only created when the partition is created

Section titled “The binding is only created when the partition is created”

ALTER TABLE … SET TABLESPACE is no substitute. YugabyteDB accepts it, reports “data movement successfully initiated” and rewrites the catalogue — but without ysql_beta_feature_tablespace_alteration the tablets do not move. The catalogue then claims a placement that does not exist.

For operations this means: tablespaces must exist before the first migration with placement runs. A tablespace created or changed afterwards has no retroactive effect; if an existing partition sits somewhere other than declared, the migration reports ErrPlacementMismatch instead of faking a rebind.

SELECT * FROM yb_local_tablets WHERE table_name LIKE '%_geo_%';

Explicitly not via pg_class.reltablespace: the two can diverge, and that is exactly the trap above. Anything that only checks the column value proves nothing.

An unreadable catalogue, duplicate codes, a region without code, or an explicitly configured but missing path prevent startup. That is deliberate: carrying on with wrong or silently defaulted backend domains would be worse than a service that does not come up — nodes would hand out different addresses and nobody would see it.

Startup logs what applies:

level=INFO msg="region catalogue loaded" source=file:/etc/dnseditor-cloud/regions.json
fingerprint=0e309ca7fe00 regions=3

The file must be identical on all nodes. So that a half-finished rollout shows up, every node reports the fingerprint of its loaded catalogue in its heartbeat. The operator panel shows the per-node state under System → Regions and warns visibly when they diverge.

The fingerprint stands for the effective catalogue, not for the bytes of the file: different indentation does not change it, a different backend domain does.

Changing the catalogue means restarting — the file is read at startup and not again.

The interface needs the catalogue before anyone is signed in:

Terminal window
curl https://eu-central.dns-editor.net/api/v1/regions

The response carries only codes, labels and domains — names that are in DNS anyway. Source, fingerprint and the per-node states stay in the operator panel.

The frontend domains also feed the CORS allowlist: a region in the catalogue means its entry domain may call the API cross-origin. A second list elsewhere deliberately does not exist.

The frontend domains are equal entrances and are never switched automatically. What adapts is the backend: after the login, every call talks to the organisation’s home region.

Before the login, the region of the entry domain applies. Whoever arrives via dns-editor.de talks to eu01 — without anything being measured for it. The assignment is in the catalogue, and after the login the home region counts anyway; a measurement would be effort without effect.

Measuring happens in two cases only: for a region-less entry domain, and to decide the order in which foreign regions are tried as a fallback.

The reason is where the data lives. An organisation’s data is geo-partitioned into its home region. A “closer” backend would not remove the distance, only move it behind the node — and add one more hop.

For this there is a public endpoint that the sign-in form calls before the password:

Terminal window
curl -X POST https://eu01.dns-editor.net/api/v1/auth/resolve \
-H "Content-Type: application/json" \
-d '{"email":"anna@example.com"}'
# → {"region":"an",
# "backend":"an01.dns-editor.net",
# "backends":["an01.dns-editor.net","an02.dns-editor.net","an03.dns-editor.net"]}

backend is the preferred target, backends the full list of the home region — the interface builds its fallback order from it.

Every region can answer the question — identities are global. The interface remembers the backend and starts there next time instead of falling back to its entrance region.

Two things the endpoint deliberately withholds

Section titled “Two things the endpoint deliberately withholds”

It is reachable without a login, so both are intentional and pinned down by tests:

  • Whether the address exists. An unknown address gets the same answer as a known one without a particular home: the primary region. Otherwise it would be a directory service to guess against.
  • Which organisations are behind it. Neither names nor counts. Anyone belonging to several sees the choice only after the password.

There the question is meaningless — there is exactly one backend. If the endpoint does not answer, the interface silently stays with the current one. A missing region lookup must never prevent a login.

If the backend stops answering altogether, the interface walks the others — those of the same region first (eu01 → eu02 → eu03), foreign ones after. The reason is again where the data lives: the login token is valid everywhere, but a foreign backend reaches across to the database. That is a last resort, not an equivalent target.

Two distinctions matter here, because they are easily expected otherwise:

An error response triggers no failover. If an HTTP status comes back, the backend is alive — it just says no. The next node would say the same, and a clear error would turn into a tour of all regions.

Writing calls are not repeated. If the connection breaks during a POST, it is not known whether the server already carried it out — the answer may merely have been lost on the way back. Repeating it would create the zone twice. The backend is switched all the same, so the next attempt already runs on the healthy node.

The hint about a more suitable entry domain

Section titled “The hint about a more suitable entry domain”

If the organisation sits in a different region than the chosen entry domain, the interface can point that out. It is enabled in the catalogue:

"ui": { "region_hint": true }

The default is off, and that is a deliberate decision: the hint invites a domain switch, and that costs a fresh login — a different domain is a different origin, and the login token lives in the storage of the original one. The hint text says so; without that sentence it would be an invitation that ends at the sign-in form.

Where the regions sit close together, the gain is not worth the trouble: only the static files and the TLS handshake travel over the entry domain — every API call goes to the home region anyway.

The hint rests not on a measurement but on comparing the home region with the region of the entry domain. It is therefore stable even when the network is fluctuating. Nothing is ever switched automatically; the user clicks, or dismisses it.

The names live in the catalogue, and so do the addresses — the zone content falls out of them. A second, hand-maintained zone file would be the place where catalogue and DNS drift apart without anyone noticing.

For this, every region carries three additional entries:

{
"code": "eu",
"edge": ["203.0.113.10", "2001:db8:1::10"],
"health": "/healthz",
"addresses": {
"eu01.dns-editor.net": ["203.0.113.11"],
"eu02.dns-editor.net": ["203.0.113.12"]
}
}

edge are the addresses under which this region’s entry domains are reachable — they belong to the region, not to a single domain. addresses maps each backend name to exactly the machine it denotes. health is the path the nameserver queries.

Checked at load time: no non-IPs, and no name that does not belong to the region. A silently ignored typo would mean: the machine is missing from DNS and nobody notices.

NameAnswer
entry domain with a regionchecked across the addresses of that region
entry domain without a regionchecked across all regions, the closest wins
backend nameplain, unchecked

A backend name denotes exactly one machine. A health check in DNS would get in the way of the interface’s fallback chain — both would switch independently.

On a region-bound entry domain there is no proximity selection: only one region is on offer. So only whoever publishes a region-less domain needs a MaxMind database for PowerDNS.

Acceptance: does the check actually check?

Section titled “Acceptance: does the check actually check?”

Because a broken health check does not draw attention, this probe belongs to commissioning — once per zone:

  1. Put one reachable and one dead address into a test record.
  2. Wait about 15 seconds (check interval × minimumFailures).
  3. Query dig +short @<ns> test.dns-editor.de A several times.

If only the reachable address comes back, the check works. If both come back, everything counts as down — then the nameserver is not checking what you think, and the cause is almost always the certificate.