gcache Sizing for IST & Fast Recovery

This guide builds on the backup and state-transfer model in Backup & Restore Sync and solves one specific operational problem: how to size the Galera write-set cache so a rejoining node replays a few minutes of missed transactions with a fast Incremental State Transfer instead of copying the entire dataset with a disruptive full snapshot. The gcache is a fixed-size ring buffer of recently replicated write-sets held by every node; when a member restarts after a patch, a crash, or a network blip, the donor can stream just the write-sets that member missed — but only if those write-sets are still in the ring. Size the buffer too small and every rejoin degrades into a full State Snapshot Transfer that desyncs a donor and saturates its I/O; size it deliberately and node recovery becomes a seconds-long, non-disruptive event. This page is the sizing and configuration reference for database administrators and platform teams running MariaDB 10.6 through 11.x with Galera 4.

Concept: The Write-Set Ring Buffer and the IST/SST Fork

Every write-set the Group Communication System delivers is appended to a local, ordered log called the gcache (GCache, the group cache). Each entry carries the global sequence number (seqno) assigned at certification, so the cache is effectively a contiguous window of history [first_cached .. last_cached]. The buffer is circular: once it fills, the oldest write-sets are overwritten by the newest, so the low-water mark first_cached continuously advances. This window is the entire basis of Incremental State Transfer — the mechanism introduced alongside SST in Initial Data Synchronization Methods.

When a node rejoins, it announces the last seqno it applied before leaving. The provider on the group selects a donor and asks one question: is the joiner’s next required seqno still inside my gcache window? If yes, the donor streams only the missing write-sets over port 4568 — an IST — and the joiner replays them in seconds while the donor stays fully writable. If the required seqno has already aged out of the ring, IST is impossible, and Galera falls back to a full SST over port 4444: the donor snapshots its entire datadir, desyncs for the duration, and the joiner discards and rebuilds its state from scratch. The single variable that decides which path you get is gcache.size relative to how much write history accumulated during the outage.

How gcache retention decides IST versus full SST on rejoin A rejoining node requests the write-sets it missed starting at sequence number N plus one. The donor holds a gcache ring buffer covering sequence numbers from a low-water mark F to the latest L. The provider tests whether N plus one is still at or above F. If the required seqno is still cached, the donor streams only the missing write-sets as a fast Incremental State Transfer over port 4568 and stays writable. If the required seqno has aged out below F, IST is impossible and the node falls back to a full State Snapshot Transfer over port 4444, which desyncs the donor and copies the entire datadir. Rejoining node needs seqno N+1 → latest Donor gcache ring cached window [ F ·· L ] F = oldest retained seqno circular: F rises as ring fills Is N+1 ≥ F ? seqno still cached on the donor? YES IST · fast path stream missing write-sets over port 4568 · seconds donor stays writable NO Full SST · fallback copy entire datadir over port 4444 · minutes–hours donor desyncs during transfer joiner discards local state SIZING GOAL Keep F below the seqno of your longest expected outage so N+1 never falls out of the window → every rejoin stays on IST.
The rejoin path is decided entirely by whether the joiner's next seqno is still inside the donor's gcache window. Size the ring to cover your worst-case outage and you never fall back to SST.

The practical lesson is that gcache.size is not a performance knob in the usual sense — it is an insurance policy against SST. A node down for ninety seconds during a package upgrade should never trigger a multi-hour full copy just because the buffer defaulted to 128 MB and a write burst rolled the window forward. The rest of this page turns that principle into a concrete sizing procedure, a parameter reference, and the verification you need to prove a rejoin actually took the IST path.

Prerequisites & Environment

Because several gcache parameters are read only when the provider loads, and because the buffer lives on disk, validate the following before you change anything:

  • MariaDB 10.6 LTS or later with the Galera 4 provider libgalera_smm.so. Provider option defaults shift between releases, so confirm the running defaults with SHOW GLOBAL VARIABLES LIKE 'wsrep_provider_options' rather than assuming.
  • A writable, monitored data directory for the gcache file. By default the ring lives as galera.cache in the datadir; a full filesystem there will crash the node, so the gcache.size you choose must fit alongside InnoDB with headroom.
  • A baseline write-rate measurement. You cannot size the ring without knowing how fast write-sets accumulate. Capture wsrep_replicated_bytes over a representative window — the full measurement routine is in Sizing gcache to Avoid a Full SST on Rejoin.
  • A change window for a rolling restart. gcache.size, gcache.page_size, and gcache.recover are all load-time options; changing them requires restarting the node, one at a time, so the group never drops below quorum. The safe sequencing is in Graceful Node Join and Leave Procedures.
  • Consistent provider options across the group. Divergent gcache settings do not fail loudly; they surface later as a page-size mismatch on rejoin. Own the value in exactly one drop-in per the discipline in wsrep.cnf Configuration Deep Dive.

Step-by-Step: Size and Configure gcache for Reliable IST

The workflow is: measure the write rate, multiply by your worst-case outage window, set the ring size (plus the page and recovery options), then roll the change out and prove a rejoin stays on IST.

Step 1 — Measure the sustained write-set rate

gcache fills at the rate write-sets are replicated, not at the rate of raw SQL. Sample the replicated byte counter twice over a fixed interval during peak write load and divide by the elapsed seconds:

# Two samples 300 s apart; bytes/sec of write-set volume entering gcache
before=$(mysql -N -B -e "SHOW GLOBAL STATUS LIKE 'wsrep_replicated_bytes';" | awk '{print $2}')
sleep 300
after=$(mysql -N -B -e "SHOW GLOBAL STATUS LIKE 'wsrep_replicated_bytes';" | awk '{print $2}')
echo "write-set rate: $(( (after - before) / 300 )) bytes/sec"

Run this during your heaviest sustained write window, not an idle one — the ring must survive an outage that happens to coincide with a batch job. Treat the result as bytes per second; the sizing arithmetic that turns it into a gcache.size is worked through in full on the gcache sizing worksheet.

Step 2 — Set gcache.size to cover the worst-case outage

Multiply the measured rate by the longest outage you must survive on IST, then add generous headroom (a factor of 1.5–2×) for write bursts above the average. If a node accumulates 4 MB/s of write-sets and you want to cover a 30-minute maintenance window, that is 4 MB/s × 1800 s ≈ 7 GB; round up to 12G:

[mysqld]
# gcache and its siblings must all live in ONE provider-options string
wsrep_provider_options = "gcache.size=12G; gcache.page_size=1G; gcache.recover=yes"

Remember that wsrep_provider_options is a single semicolon-delimited value: a second declaration replaces the whole string rather than appending, so every gcache key must sit in the same line as your gcs and evs tuning.

Step 3 — Set gcache.page_size for large transactions

When a single write-set (or the in-memory ring) exceeds the RAM allotment, Galera spills to on-disk page-store files sized by gcache.page_size. A page size that is too small forces many tiny files and slows large-transaction handling; too large wastes space. Match it to your largest expected transaction — 1G is a sane production default and comfortably absorbs bulk INSERT ... SELECT or schema changes:

[mysqld]
wsrep_provider_options = "gcache.size=12G; gcache.page_size=1G; gcache.keep_pages_size=2G"

gcache.keep_pages_size tells the provider to retain that many bytes of allocated page-store files rather than deleting them the instant they drain, which avoids churn when write volume is spiky. Its role in eviction is examined in Diagnosing IST Failures & gcache Eviction.

Step 4 — Enable gcache.recover for restart-surviving IST

By default a clean or crash restart discards the on-disk gcache, so the very first rejoin after a restart is forced to SST even though the write history existed moments earlier. Setting gcache.recover=yes makes the provider validate and reuse the existing galera.cache at startup, preserving the cached window across the restart so the node can still donate — and receive — IST:

[mysqld]
wsrep_provider_options = "gcache.size=12G; gcache.page_size=1G; gcache.recover=yes"

This single option is the difference between a rolling restart that stays entirely on IST and one that triggers an SST storm the moment the first node comes back.

Step 5 — Roll out and confirm the ring window

Apply the change with a rolling restart, waiting for each node to reach Synced before the next, then confirm the cache actually spans a useful window. The provider reports the current low- and high-water seqnos as status variables:

SHOW GLOBAL STATUS WHERE Variable_name IN (
  'wsrep_local_cached_downto',  -- oldest seqno still in gcache (the F in the diagram)
  'wsrep_last_committed'        -- newest applied seqno (approx. L)
);

The difference wsrep_last_committed − wsrep_local_cached_downto is the number of transactions your ring currently holds; divide by your commit rate to get the outage duration it covers right now.

Parameter Deep-Dive: The gcache Knobs That Matter

These six provider options govern the ring buffer. Tune size, page_size, and recover deliberately; leave the rest at defaults unless a specific symptom points at them.

Parameter Type Default Production value Why it matters
gcache.size size 128M 4G–16G The retained write-set window. This is the primary lever that keeps rejoins on IST instead of SST.
gcache.page_size size 128M 256M–1G Size of on-disk page-store files used when the ring overflows RAM or a write-set is huge.
gcache.keep_pages_size size 0 1G–4G Bytes of page-store files kept allocated rather than freed immediately; damps churn under spiky writes.
gcache.recover boolean no yes Reuse the on-disk cache across a restart so a rebooted node can still serve and take IST.
gcache.dir path datadir fast local disk Where galera.cache lives; move it to NVMe, never to network storage.
gcache.name filename galera.cache (leave default) The cache file name; rarely changed except to place it on a separate device.

gcache.size dominates every recovery outcome: it must exceed the write volume produced across your longest realistic outage plus burst headroom. gcache.page_size only engages for write-sets larger than the in-memory allocation, so raise it if you run large bulk loads or online DDL. gcache.keep_pages_size trades disk for stability — keep a couple of gigabytes allocated on write-spiky systems so the provider is not constantly creating and deleting page files. gcache.recover should be yes on essentially every production node; its only cost is a brief cache-validation pass at startup. gcache.dir matters for I/O: the ring is written on the hot path of every commit, so it belongs on the same class of low-latency local storage as your redo log, never on NFS or a network block device where a stall would propagate straight into flow control.

Verification & Health Checks

After the rolling change, prove two things: the ring is large enough, and a real rejoin took IST rather than SST. First, quantify the window in time, not just bytes:

SELECT
  @cached := (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS
              WHERE VARIABLE_NAME='wsrep_local_cached_downto') AS oldest_cached_seqno,
  @last := (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS
            WHERE VARIABLE_NAME='wsrep_last_committed') AS newest_seqno,
  (@last - @cached) AS transactions_retained;

Then, after restarting a node, confirm which path it took by reading the donor and joiner logs — an IST logs Receiving IST and a seqno range, while an SST logs Running: wsrep_sst_mariabackup. A Python probe makes this a gate. It uses PyMySQL, computes the retained-window duration from the commit rate, and handles the two contention error codes Galera surfaces under load — 1213 (deadlock / certification conflict) and 1205 (lock wait timeout) — so a busy node is not misread as unhealthy:

import sys
import time
import pymysql

def status(cur, name):
    cur.execute("SHOW GLOBAL STATUS LIKE %s", (name,))
    row = cur.fetchone()
    return int(row[1]) if row else 0

def gcache_window_seconds(host: str) -> float:
    """Estimate how many seconds of outage the current gcache ring covers."""
    try:
        conn = pymysql.connect(host=host, user="monitor", password="secret",
                               connect_timeout=5, read_timeout=5)
    except pymysql.err.OperationalError as exc:
        print(f"[FATAL] {host} unreachable: {exc}", file=sys.stderr)
        return -1.0
    try:
        with conn.cursor() as cur:
            cached_downto = status(cur, "wsrep_local_cached_downto")
            last_1 = status(cur, "wsrep_last_committed")
            time.sleep(10)
            last_2 = status(cur, "wsrep_last_committed")
    except pymysql.err.OperationalError as exc:
        # 1213 = certification deadlock, 1205 = lock wait timeout; transient under load.
        if exc.args and exc.args[0] in (1213, 1205):
            print(f"[WARN] {host} contended ({exc.args[0]}); retry", file=sys.stderr)
            return -1.0
        raise
    finally:
        conn.close()

    commit_rate = (last_2 - last_1) / 10.0  # transactions/sec
    retained = last_1 - cached_downto
    if commit_rate <= 0:
        print(f"[OK] {host} idle; ring holds {retained} transactions")
        return float("inf")
    seconds = retained / commit_rate
    print(f"[OK] {host} gcache covers ~{seconds:,.0f}s at {commit_rate:,.0f} txn/s")
    return seconds

if __name__ == "__main__":
    covered = gcache_window_seconds(sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1")
    # Fail the gate if the ring covers less than a 15-minute outage.
    sys.exit(0 if covered >= 900 else 1)

Wire this into the reusable probes in Monitoring Galera Cluster State with Python and alert when the covered window drops below your longest planned maintenance outage — that is the early warning that your next rejoin will fall back to SST.

Automation Integration

Sizing is not a one-time act: write volume grows, and a ring that covered thirty minutes last quarter may cover eight today. Treat the covered-window metric as a first-class SLO. The following Ansible task renders the provider-options string from an inventory variable so the whole fleet shares one source of truth, and gates the restart behind the config validation described in Validating Galera Config in CI Before Restart:

- name: Render Galera provider options with sized gcache
  ansible.builtin.template:
    src: 60-galera.cnf.j2
    dest: /etc/mysql/mariadb.conf.d/60-galera.cnf
  register: galera_cfg

- name: Validate merged configuration before any restart
  ansible.builtin.command: mariadbd --validate-config --defaults-file=/etc/mysql/mariadb.cnf
  changed_when: false

- name: Rolling restart one node at a time when config changed
  ansible.builtin.systemd:
    name: mariadb
    state: restarted
  when: galera_cfg.changed
  throttle: 1   # serialize so the group never drops below quorum

The template that renders gcache.size from write_rate_bytes × outage_seconds × burst_factor closes the loop between measurement and configuration; the provisioning pattern it plugs into is Automating Node Provisioning with Ansible.

Troubleshooting

These are the failures that trace directly to gcache sizing or configuration rather than a network fault.

Symptom Root cause Remediation
Every rejoin runs wsrep_sst_mariabackup even after a brief restart gcache.recover off, or ring too small for the outage window Set gcache.recover=yes; raise gcache.size to cover the measured write rate × outage
IST failed, restarting from SST in the joiner log Required seqno aged out of the donor’s ring during the outage Enlarge gcache.size; confirm the donor’s wsrep_local_cached_downto is below the joiner’s last seqno
WSREP: gcache page size mismatch on rejoin gcache.page_size differs across nodes Realign the value in one drop-in and perform a coordinated rolling restart
Node crashes with galera.cache write errors Datadir filesystem full; the ring cannot extend Free space or move gcache.dir to a larger device; the ring reserves gcache.size up front
gcache appears to hold far fewer transactions than sized Very large write-sets consuming the ring quickly Raise gcache.size and gcache.page_size; shorten transactions where possible

For the deeper diagnostic workflow — reading the exact donor/joiner log lines that reveal why a seqno was not found, and how page eviction interacts with gcache.keep_pages_size — work through Diagnosing IST Failures & gcache Eviction. For the arithmetic that prevents these symptoms in the first place, use the gcache sizing worksheet.