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.
DNSE_REGIONS_FILE=/etc/dnseditor-cloud/regions.jsonWithout 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" } ]}| Field | Meaning |
|---|---|
code | region code; an organisation’s home region is stored by it |
label | display name; the code stands in when it is missing |
frontends | the entry domains assigned to this region |
backends | the region’s API domains, in order |
dyndns / agents | the addresses handed out by the setup dialogs |
frontends (top level) | entry domains without a region |
session.ttl_hours | lifetime of a login |
session.touch_interval_minutes | minimum gap between two refreshes of the session lifetime |
dns.*_ttl_seconds | TTL of our own DNS records |
ui.region_hint | allows 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 order of the backends is a statement
Section titled “The order of the backends is a statement”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.
Entry domains without a region
Section titled “Entry domains without a region”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.
Older catalogues
Section titled “Older catalogues”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.
What the frontend domain does NOT do
Section titled “What the frontend domain does NOT do”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.
Placement: from a label to actual storage
Section titled “Placement: from a label to actual storage”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:
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.
Only the tablets prove it
Section titled “Only the tablets prove it”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.
Failing to start is intentional
Section titled “Failing to start is intentional”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=3Rollout: every node, one state
Section titled “Rollout: every node, one state”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.
Public endpoint
Section titled “Public endpoint”The interface needs the catalogue before anyone is signed in:
curl https://eu-central.dns-editor.net/api/v1/regionsThe 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.
Where a login goes
Section titled “Where a login goes”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:
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.
CE and EE do not have it
Section titled “CE and EE do not have it”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.
When a backend fails
Section titled “When a backend fails”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.
GeoDNS: generating our own zones
Section titled “GeoDNS: generating our own zones”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.
Three kinds of name, three answers
Section titled “Three kinds of name, three answers”| Name | Answer |
|---|---|
| entry domain with a region | checked across the addresses of that region |
| entry domain without a region | checked across all regions, the closest wins |
| backend name | plain, 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:
- Put one reachable and one dead address into a test record.
- Wait about 15 seconds (check interval ×
minimumFailures). - Query
dig +short @<ns> test.dns-editor.de Aseveral 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.