Locality in OceanBase: How Replica Placement Becomes an Availability Contract

Zhennan Wang
Zhennan Wang
Published on June 5, 2026
7 minute read
Key Takeaways
  • Locality is a contract, not a config. You declare the replica layout you want, and OceanBase keeps it true through every node loss and topology change.
  • Only F replicas vote. R and C are Paxos Learners that scale reads without inflating quorum - the one rule that ties every locality choice back to availability.
  • Locality fixes count, type, and zone - and only those. Region, leader, and arbiter come from three other dimensions; conflating them is the most common locality mistake.

You picked a topology - three replicas across three IDCs, or four plus an arbiter, or one of the other shapes laid out in our previous post. Now what? The whiteboard doesn't run anything. Someone has to tell the database where each replica should live, and something has to keep them there when a node dies at 3 AM.

That something is Locality - a single tenant-level attribute that names the target replica layout, and a Root Service that keeps the cluster matching it. Locality is declarative: you don't migrate replicas by hand or script a failover. You declare the distribution, and OceanBase converges to it - then keeps converging after every loss. The rest of this post walks through the vocabulary, the syntax, and the enforcement rules that turn a locality string into something the cluster keeps true.

The Vocabulary: F, R, C

Locality is written in terms of replica types. OceanBase exposes three replica types in practice - the same three the first post introduced, now in their full grammar.

TypeKeywordClogVotes in Paxos?Can be Leader?Role
FFULL / FSynchronousYesYesServes writes and strong reads (Leader); weak reads (Follower). Every tenant needs at least one.
RREADONLY / RAsynchronous (Learner)NoNoComplete data, weak-consistency reads. Read scale-out without inflating quorum.
CCOLUMNSTORE / CAsynchronous (Learner)NoNoSame consensus role as R, but baseline data stored column-wise. Attaches AP workloads to the same cluster.

The one line that ties this whole series together: only F replicas vote. R and C receive the clog asynchronously as Learners and serve weak-consistency reads, but they are invisible to the quorum calculation. So when the second post said "two-region 3-IDC keeps a majority alive after a single-IDC failure," the majority it meant is a majority of F replicas. Adding R or C replicas scales reads without changing - and without inflating - the quorum you need to keep writing. That is precisely why they exist.

The Grammar: Reading a Locality String

A locality string is a comma-separated list of TYPE{count}@zone clauses, one per zone:

ALTER TENANT t1 LOCALITY = 'F@z1, F@z2, F@z3, R@z4';

Reading it left to right: one full replica in z1, one in z2, one in z3, and one read-only replica in z4. That declares a three-F quorum across three zones plus a read scale-out replica in a fourth - the canonical 3F + R pattern, now spelled out.

A few grammar facts worth knowing:

  • The count {N} is per-zone and fixed to 1, so it's almost always omitted - F@z1 is FULL{1}@z1. Three replicas means three clauses (F@z1, F@z2, F@z3), never F{3}@z1.
  • @zone binds the clause to a zone, never to a region. Region is a separate attribute (covered below).
  • The arbiter is not in the string. There is no A@zone clause. The arbitration service is cluster-level (ALTER SYSTEM ADD ARBITRATION SERVICE …) and enabled per tenant (ENABLE_ARBITRATION_SERVICE). This trips people up: a 2F1A deployment's locality is just F@z1, F@z2 - the arbiter is configured entirely outside locality.

One scope note for anyone migrating from V3.x: in OceanBase 4.x, Locality is tenant-level only. Earlier versions allowed Locality at the table, database, and table-group level; that surface area was deliberately removed when 4.x consolidated topology declaration to a single tenant attribute. If your operational scripts still issue ALTER TABLE … LOCALITY = …, they need to move to ALTER TENANT.

From Topology to Contract

Here is the payoff - the seven topologies from the last post, translated into the locality you'd actually write. The surprising part is in the third column.

TopologyLocality stringWhat sets the geographyArbiter?
Single IDC 3FF@z1, F@z2, F@z3All zones share one region-
Same-city 3-IDCF@z1, F@z2, F@z3Same region; each zone is a separate IDC-
Two-region 3-IDC (5F, 4+1)F@z1, F@z2, F@z3, F@z4, F@z5z1-z4 in region A, z5 in region B-
Three-region 5-IDC (5F, 2+2+1)F@z1, F@z2, F@z3, F@z4, F@z5z1,z2 in A; z3,z4 in B; z5 in C-
Same-city 2F1AF@z1, F@z2Two IDCs in one regionConfigured separately
Two-region 4F1AF@z1, F@z2, F@z3, F@z4z1-z4 in primary region; arbiter in secondaryConfigured separately
Three-region 4F1AF@z1, F@z2, F@z3, F@z4z1,z2 in A; z3,z4 in B; arbiter in CConfigured separately

Look at rows 1-4. Same-region 3-IDC, two-region 5F, and three-region 5F have nearly identical locality strings - just N copies of F@zone, one entry per zone. The locality says nothing about regions at all. So what makes one of them "same-region" and another "three-region"?

The answer is a separate dimension: the zone-to-region attribute. Region is not part of the locality string - it's a property of each zone, set when the zone is added:

ALTER SYSTEM ADD ZONE zone5 IDC 'sh1', REGION 'shanghai';

That zone-to-region assignment - not the locality string - is what places replicas in cities. Locality declares how many F replicas and in which zones; the region attribute declares where those zones physically are. The two are deliberately decoupled, which is why the same F@z1,…,F@z5 string can describe a two-region or a three-region deployment depending only on how the zones map to regions.

The Contract Is Enforced, Not Just Stored

Locality isn't a config value that sits in a table. Change it, and the cluster acts.

When you run ALTER TENANT ... LOCALITY = '...', the Root Service diffs the new declaration against the current replica distribution and generates the tasks needed to close the gap: add a replica, drop one, change a replica's type, or move it to a different zone. The statement returns quickly; the data movement happens asynchronously in the background. Declaring locality is signing a new contract - not synchronously hauling data around.

Three rules govern how those changes are sequenced:

  • One action per statement. Add a replica or drop a replica, not both. To replace a zone, add the new one first, then drop the old - never edit in place. The exceptions are 3F↔5F (always allowed in a single step) and 2F↔4F when an arbiter replica is present.
  • Watch the 2F→1F transition. Before v4.4.0 the cluster rejected it outright - a two-replica tenant could not drop to one. From v4.4.0 the locality change is allowed, but the resulting 1F tenant has no redundancy, so this becomes a deliberate operator choice rather than a guardrail.
  • One change at a time - rollback excepted. While a change is converging, oceanbase.DBA_OB_TENANTS.PREVIOUS_LOCALITY holds the old string and a second ALTER TENANT … LOCALITY to a new target is rejected. The exception is a rollback - re-issuing the previous locality string aborts the in-flight change and reverts the tenant.

The "add first, then drop" rule is what an IDC retirement looks like in practice:

oceanbase database

A locality change is two declarations, not one. To retire a zone, add the new replica first, then drop the old - never replace in place. The transitional 4F state preserves the original 3-of-3 quorum guarantee while data copies to z4.

The contract is also maintained. When a server goes permanently offline, the surviving replicas no longer match the declared locality, so the Root Service automatically rebuilds the missing F replica on another OBServer to restore the declared count and quorum. You don't re-issue the locality; the cluster already knows the target and works to satisfy it. This is the difference between "place these replicas once" and "keep this distribution true" - and it's the whole point of a declarative model. (Watching that movement happen - migration, replication, rebuild - is the next post.)

Locality's Two Companions

Locality answers how many, what type, which zone. Two adjacent settings answer the questions it deliberately leaves out - and keeping the boundaries straight avoids most confusion:

  • primary_zone decides which of the F replicas should host Leaders. Locality pins down the set of voting replicas; primary_zone expresses a preference for where leadership - and therefore the write path and strong reads - should land within that set (using ; for priority tiers and , within a tier, e.g. 'z1;z2,z3'). The docs recommend adjusting primary_zone before a locality change that removes the current primary zone, so leadership moves first and the replica drop is clean.
  • Units / resource pools decide which physical machine and how much resource each replica gets. Locality says "one F replica in zone3"; the unit says "with 8 CPU and 32 GB, running on this OBServer." Locality is logical placement; units are physical placement.

So: locality = replica count and type per zone, region attribute = which region/city, primary_zone = where the Leader sits, unit = which machine and how big. Four dimensions, one tenant.

What's Next

A locality string is a target state. The interesting question is what the cluster does to reach it - and to get back to it after a node fails. That's the job of migration, replication, and rebuild: the Root Service's machinery for moving replicas and log streams between servers without taking the database offline. The next post follows a locality change from ALTER TENANT to a converged cluster, task by task.

Try It Yourself

On OceanBase Community Edition, stand up a multi-zone tenant and run:

ALTER TENANT t1 LOCALITY = 'F@z1, F@z2, F@z3, R@z4';

Then watch oceanbase.DBA_OB_LS_LOCATIONS as the read-only replica appears in z4 on its own - no manual copy step. Its MEMBER_LIST column shows the three voting F replicas; the LEARNER_LIST column shows the new R replica, a concrete view of "only F votes." Drop the R@z4 clause and watch it get removed. That round trip - target declared, cluster converging - is locality in one minute.


Further reading:

Share
X
linkedin
mail