From c125b9bec563aeeb5b7027d7b9ba7a542671887c Mon Sep 17 00:00:00 2001 From: eaydingol <60466783+eaydingol@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:57:36 +0300 Subject: [PATCH] Relax version check (#8356) Relaxes the Citus version compatibility check for asynchronous replica upgrades. Citus requires the loaded shared library and the installed extension to have identical major and minor versions. This change keeps the library vs. control-file version checks the same (a restart is still needed after upgrading the library), but allows the installed extension to differ by at most one minor version from the loaded library. With this relaxation in place, replicas can lag by a single minor version during phased upgrades without triggering a version mismatch for distributed queries. Tested the change on release branch 13.2 as main already points to the next major version. Versioned test capability from https://github.com/citusdata/citus/pull/8361 is used in test. See branch for test https://github.com/citusdata/citus/tree/eag/release-13.2/relax-test , the so and sql versions are 13.2-1. ``` // Create extension fails CITUSVERSION=13.0-1 citus_tests/run_test.py adaptive_executor .... CREATE DATABASE ERROR: specified version incompatible with loaded Citus library DETAIL: Loaded library requires 13.2, but 13.0-1 was specified. HINT: If a newer library is present, restart the database and try the command again. .... // Test proceeds as expected CITUSVERSION=13.1-1 citus_tests/run_test.py adaptive_executor CREATE DATABASE CREATE EXTENSION CREATE FUNCTION // Test proceeds as expected CITUSVERSION=13.2-1 citus_tests/run_test.py adaptive_executor CREATE DATABASE CREATE EXTENSION CREATE FUNCTION ``` --- src/backend/columnar/columnar_tableam.c | 53 ++++++++++++++++++- src/backend/distributed/commands/extension.c | 2 +- .../distributed/metadata/metadata_cache.c | 52 +++++++++++++++++- src/include/distributed/metadata_cache.h | 1 + 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/backend/columnar/columnar_tableam.c b/src/backend/columnar/columnar_tableam.c index e739c823e..42d3dcf36 100644 --- a/src/backend/columnar/columnar_tableam.c +++ b/src/backend/columnar/columnar_tableam.c @@ -162,6 +162,8 @@ static bool CitusColumnarHasBeenLoadedInternal(void); static bool CitusColumnarHasBeenLoaded(void); static bool CheckCitusColumnarVersion(int elevel); static bool MajorVersionsCompatibleColumnar(char *leftVersion, char *rightVersion); +static bool MinorVersionsCompatibleRelaxedColumnar(char *leftVersion, char *rightVersion); +static int ParseVersionComponent(const char *version, char **endPtr); /* global variables for CheckCitusColumnarVersion */ static bool extensionLoadedColumnar = false; @@ -2869,7 +2871,7 @@ CheckInstalledVersionColumnar(int elevel) char *installedVersion = InstalledExtensionVersionColumnar(); - if (!MajorVersionsCompatibleColumnar(installedVersion, CITUS_EXTENSIONVERSION)) + if (!MinorVersionsCompatibleRelaxedColumnar(installedVersion, CITUS_EXTENSIONVERSION)) { ereport(elevel, (errmsg("loaded Citus library version differs from installed " "extension version"), @@ -2928,6 +2930,55 @@ MajorVersionsCompatibleColumnar(char *leftVersion, char *rightVersion) } +/* + * ParseVersionComponent parses the integer at the current position and + * advances endPtr past the parsed digits to the next character. + */ +static int +ParseVersionComponent(const char *version, char **endPtr) +{ + errno = 0; + long int val = strtol(version, endPtr, 10); + + if (errno == ERANGE || val > INT_MAX || val < INT_MIN) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("Invalid integer in version string"))); + } + return (int) val; +} + + +/* + * MinorVersionsCompatibleRelaxedColumnar checks if two versions have the same major + * version and their minor versions differ by at most 1. The schema version + * (after '-') is ignored. Returns true if compatible, false otherwise. + * + * Version format expected: "major.minor-schema" (e.g., "13.1-2") + */ +bool +MinorVersionsCompatibleRelaxedColumnar(char *leftVersion, char *rightVersion) +{ + char *leftSep; + char *rightSep; + + int leftMajor = ParseVersionComponent(leftVersion, &leftSep); + int rightMajor = ParseVersionComponent(rightVersion, &rightSep); + + if (leftMajor != rightMajor) + { + return false; + } + + int leftMinor = (*leftSep == '.') ? ParseVersionComponent(leftSep + 1, &leftSep) : 0; + int rightMinor = (*rightSep == '.') ? ParseVersionComponent(rightSep + 1, &rightSep) : + 0; + + int diff = leftMinor - rightMinor; + return diff >= -1 && diff <= 1; +} + + /* * AvailableExtensionVersion returns the Citus version from citus.control file. It also * saves the result, thus consecutive calls to CitusExtensionAvailableVersion will diff --git a/src/backend/distributed/commands/extension.c b/src/backend/distributed/commands/extension.c index d5bb50317..1f3ba351e 100644 --- a/src/backend/distributed/commands/extension.c +++ b/src/backend/distributed/commands/extension.c @@ -80,7 +80,7 @@ ErrorIfUnstableCreateOrAlterExtensionStmt(Node *parseTree) if (newExtensionVersion != NULL) { /* explicit version provided in CREATE or ALTER EXTENSION UPDATE; verify */ - if (!MajorVersionsCompatible(newExtensionVersion, CITUS_EXTENSIONVERSION)) + if (!MinorVersionsCompatibleRelaxed(newExtensionVersion, CITUS_EXTENSIONVERSION)) { ereport(ERROR, (errmsg("specified version incompatible with loaded " "Citus library"), diff --git a/src/backend/distributed/metadata/metadata_cache.c b/src/backend/distributed/metadata/metadata_cache.c index 87463788f..4c21389d4 100644 --- a/src/backend/distributed/metadata/metadata_cache.c +++ b/src/backend/distributed/metadata/metadata_cache.c @@ -335,6 +335,7 @@ static Oid DistAuthinfoRelationId(void); static Oid DistAuthinfoIndexId(void); static Oid DistPoolinfoRelationId(void); static Oid DistPoolinfoIndexId(void); +static int ParseVersionComponent(const char *version, char **endPtr); /* exports for SQL callable functions */ PG_FUNCTION_INFO_V1(citus_dist_partition_cache_invalidate); @@ -2436,7 +2437,7 @@ CheckInstalledVersion(int elevel) char *installedVersion = InstalledExtensionVersion(); - if (!MajorVersionsCompatible(installedVersion, CITUS_EXTENSIONVERSION)) + if (!MinorVersionsCompatibleRelaxed(installedVersion, CITUS_EXTENSIONVERSION)) { ereport(elevel, (errmsg("loaded Citus library version differs from installed " "extension version"), @@ -2514,6 +2515,55 @@ MajorVersionsCompatible(char *leftVersion, char *rightVersion) } +/* + * ParseVersionComponent parses the integer at the current position and + * advances endPtr past the parsed digits to the next character. + */ +static int +ParseVersionComponent(const char *version, char **endPtr) +{ + errno = 0; + long int val = strtol(version, endPtr, 10); + + if (errno == ERANGE || val > INT_MAX || val < INT_MIN) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("Invalid integer in version string"))); + } + return (int) val; +} + + +/* + * MinorVersionsCompatibleRelaxed checks if two versions have the same major + * version and their minor versions differ by at most 1. The schema version + * (after '-') is ignored. Returns true if compatible, false otherwise. + * + * Version format expected: "major.minor-schema" (e.g., "13.1-2") + */ +bool +MinorVersionsCompatibleRelaxed(char *leftVersion, char *rightVersion) +{ + char *leftSep; + char *rightSep; + + int leftMajor = ParseVersionComponent(leftVersion, &leftSep); + int rightMajor = ParseVersionComponent(rightVersion, &rightSep); + + if (leftMajor != rightMajor) + { + return false; + } + + int leftMinor = (*leftSep == '.') ? ParseVersionComponent(leftSep + 1, &leftSep) : 0; + int rightMinor = (*rightSep == '.') ? ParseVersionComponent(rightSep + 1, &rightSep) : + 0; + + int diff = leftMinor - rightMinor; + return diff >= -1 && diff <= 1; +} + + /* * AvailableExtensionVersion returns the Citus version from citus.control file. It also * saves the result, thus consecutive calls to CitusExtensionAvailableVersion will diff --git a/src/include/distributed/metadata_cache.h b/src/include/distributed/metadata_cache.h index 49c903220..a1fb40e40 100644 --- a/src/include/distributed/metadata_cache.h +++ b/src/include/distributed/metadata_cache.h @@ -217,6 +217,7 @@ extern bool CheckCitusVersion(int elevel); extern bool CheckAvailableVersion(int elevel); extern bool InstalledAndAvailableVersionsSame(void); extern bool MajorVersionsCompatible(char *leftVersion, char *rightVersion); +extern bool MinorVersionsCompatibleRelaxed(char *leftVersion, char *rightVersion); extern void ErrorIfInconsistentShardIntervals(CitusTableCacheEntry *cacheEntry); extern void EnsureModificationsCanRun(void); extern void EnsureModificationsCanRunOnRelation(Oid relationId);