Introduction
In YugabyteDB, the catalog version is a small but critical number that ensures all nodes in a cluster are in sync with schema changes. Each time a DDL statement, such as CREATE TABLE, DROP INDEX, or ALTER TABLE, is executed, this version is incremented.
Backend sessions cache the version they last saw, and if the storage layer detects a mismatch, the backend is forced to refresh its catalog cache before retrying.
Most of the time this process happens automatically. But what if you want to force a refresh without creating or dropping objects? That’s where manually bumping the catalog version comes in. This technique is especially handy for testing scenarios, demos, or nudging connections to refresh metadata after certain maintenance steps.
In this YugabyteDB Tip, we’ll walk through:
● How catalog versioning works.
● How to view the current catalog version.
● A one-off SQL sequence for manual bumps.
● A reusable stored procedure to make this safe and repeatable.
IMPORTANT:
These approaches are not recommended for production use. These utilities are internal tech-preview features and may change in future releases. Please review the “Notes & Caveats” section below before using them!
What is the “catalog version”?
The catalog version tracks changes to the YSQL system catalog (metadata about tables, indexes, schemas, etc.).
● Stored in the
pg_yb_catalog_versiontable.● Each backend caches the version it last saw.
● On mismatch, the backend refreshes its catalog cache and retries.
By default, YugabyteDB uses per-database catalog version mode (--ysql_enable_db_catalog_version_mode=true). This means DDLs only increment the catalog version for the affected database, allowing DDLs on different databases to proceed concurrently without unnecessary cache invalidations. This improves scalability in multi-tenant environments.
Checking the catalog version
yugabyte) with:
SELECT b.datname, a.db_oid, a.current_version, a.last_breaking_version
FROM pg_yb_catalog_version a
JOIN pg_database b ON b.oid = a.db_oid
WHERE b.datname = 'yugabyte';
Example:
yugabyte=# SELECT b.datname, a.db_oid, a.current_version, a.last_breaking_version
yugabyte-# FROM pg_yb_catalog_version a
yugabyte-# JOIN pg_database b ON b.oid = a.db_oid
yugabyte-# WHERE b.datname = 'yugabyte';
datname | db_oid | current_version | last_breaking_version
----------+--------+-----------------+-----------------------
yugabyte | 13515 | 2 | 2
(1 row)
One-off manual bump
To bump the catalog version manually, you need to temporarily allow non-DDL writes to system tables. Here’s a simple sequence:
-- 1) Enable non-DDL sys-table writes:
SET yb_non_ddl_txn_for_sys_tables_allowed = 1;
-- 2) Perform the bump:
UPDATE pg_yb_catalog_version
SET current_version = current_version + 1,
last_breaking_version = last_breaking_version + 1
WHERE db_oid = 13515; -- replace with your db OID
-- 3) Re-disable writes:
SET yb_non_ddl_txn_for_sys_tables_allowed = 0;
-- 4) Verify:
SELECT b.datname, a.db_oid, a.current_version, a.last_breaking_version
FROM pg_yb_catalog_version a
JOIN pg_database b ON b.oid = a.db_oid
WHERE b.datname = 'yugabyte';
Reusable stored procedure
To make this safer and more convenient, you can wrap the bump logic in a stored procedure. This procedure:
● Takes a database name as input (no need to look up OIDs manually).
● Optionally bumps only the
current_versionor bothcurrent_versionandlast_breaking_version.● Ensures the
yb_non_ddl_txn_for_sys_tables_allowedGUC is reset even if something fails.
CREATE OR REPLACE FUNCTION yb_bump_catalog_version(
p_dbname text,
p_breaking boolean DEFAULT true
) RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = pg_catalog
AS $$
DECLARE
v_prev_guc text;
v_db_oid oid;
current_before bigint;
breaking_before bigint;
current_after bigint;
breaking_after bigint;
BEGIN
SELECT d.oid INTO v_db_oid
FROM pg_database d
WHERE d.datname = p_dbname;
IF NOT FOUND THEN
RAISE EXCEPTION 'Database % not found', p_dbname;
END IF;
SELECT c.current_version, c.last_breaking_version
INTO current_before, breaking_before
FROM pg_yb_catalog_version c
WHERE c.db_oid = v_db_oid;
v_prev_guc := current_setting('yb_non_ddl_txn_for_sys_tables_allowed', true);
PERFORM set_config('yb_non_ddl_txn_for_sys_tables_allowed', '1', false);
BEGIN
UPDATE pg_yb_catalog_version c
SET current_version = c.current_version + 1,
last_breaking_version = c.last_breaking_version + CASE WHEN p_breaking THEN 1 ELSE 0 END
WHERE c.db_oid = v_db_oid
RETURNING c.current_version, c.last_breaking_version
INTO current_after, breaking_after;
EXCEPTION WHEN OTHERS THEN
PERFORM set_config('yb_non_ddl_txn_for_sys_tables_allowed', COALESCE(v_prev_guc,'0'), false);
RAISE;
END;
PERFORM set_config('yb_non_ddl_txn_for_sys_tables_allowed', COALESCE(v_prev_guc,'0'), false);
RAISE NOTICE 'Bumped catalog version for % (oid=%) from (current=%, breaking=%) to (current=%, breaking=%)',
p_dbname, v_db_oid, current_before, breaking_before, current_after, breaking_after;
END;
$$;
Usage
Bump both counters (default):
SELECT yb_bump_catalog_version('yugabyte');
Example:
yugabyte=# SELECT yb_bump_catalog_version('yugabyte');
NOTICE: Bumped catalog version for yugabyte (oid=13515) from (current=11, breaking=5) to (current=12, breaking=6)
yb_bump_catalog_version
----------------------------
(yugabyte,13515,11,5,12,6)
(1 row)
yugabyte=# SELECT * FROM yb_bump_catalog_version('yugabyte');
NOTICE: Bumped catalog version for yugabyte (oid=13515) from (current=12, breaking=6) to (current=13, breaking=7)
dbname | db_oid | current_before | breaking_before | current_after | breaking_after
----------+--------+----------------+-----------------+---------------+----------------
yugabyte | 13515 | 12 | 6 | 13 | 7
(1 row)
Bump only current_version:
SELECT yb_bump_catalog_version('yugabyte', false);
Example:
yugabyte=# SELECT yb_bump_catalog_version('yugabyte', false);
NOTICE: Bumped catalog version for yugabyte (oid=13515) from (current=13, breaking=7) to (current=14, breaking=7)
yb_bump_catalog_version
----------------------------
(yugabyte,13515,13,7,14,7)
(1 row)
yugabyte=# SELECT * FROM yb_bump_catalog_version('yugabyte', false);
NOTICE: Bumped catalog version for yugabyte (oid=13515) from (current=14, breaking=7) to (current=15, breaking=7)
dbname | db_oid | current_before | breaking_before | current_after | breaking_after
----------+--------+----------------+-----------------+---------------+----------------
yugabyte | 13515 | 14 | 7 | 15 | 7
(1 row)
Notes & caveats
-
● Risk: Direct writes to system catalogs can be dangerous. Use carefully and test in lower environments first.
-
● Per-database mode: With
--ysql_enable_db_catalog_version_mode=true(default), only the target database is affected. -
● Breaking vs non-breaking: Normally only some DDLs bump
last_breaking_version. Use thefalseoption if you just want a light nudge. -
● Scope: This doesn’t change schema, just version counters.
-
● Permissions: Requires superuser.
Example session
yugabyte=# -- Before
yugabyte=# SELECT b.datname, a.db_oid, a.current_version, a.last_breaking_version
yugabyte-# FROM pg_yb_catalog_version a
yugabyte-# JOIN pg_database b ON b.oid = a.db_oid
yugabyte-# WHERE b.datname = 'yugabyte';
datname | db_oid | current_version | last_breaking_version
----------+--------+-----------------+-----------------------
yugabyte | 13515 | 15 | 7
(1 row)
yugabyte=# -- Bump both counters
yugabyte=# SELECT yb_bump_catalog_version('yugabyte');
NOTICE: Bumped catalog version for yugabyte (oid=13515) from (current=15, breaking=7) to (current=16, breaking=8)
yb_bump_catalog_version
----------------------------
(yugabyte,13515,15,7,16,8)
(1 row)
yugabyte=# -- After
yugabyte=# SELECT b.datname, a.db_oid, a.current_version, a.last_breaking_version
yugabyte-# FROM pg_yb_catalog_version a
yugabyte-# JOIN pg_database b ON b.oid = a.db_oid
yugabyte-# WHERE b.datname = 'yugabyte';
datname | db_oid | current_version | last_breaking_version
----------+--------+-----------------+-----------------------
yugabyte | 13515 | 16 | 8
(1 row)
Summary
The YSQL catalog version is a core consistency mechanism that keeps schema metadata aligned across the cluster. While it normally increments automatically on DDL operations, there are times when you may want to bump it manually … without creating objects.
Whether you use a quick SQL sequence or the safer yb_bump_catalog_version() helper, you now have a clean way to force backends to refresh their catalog cache on demand.
Have Fun!
