From 01766b033a4594161c239ee01d5f783a429df908 Mon Sep 17 00:00:00 2001 From: "Andrei V. Lepikhov" Date: Fri, 21 Aug 2026 16:05:31 +0200 Subject: [PATCH] Resolve spock.progress's LSN column per endpoint spock.progress's apply-progress column was renamed with no overlap: remote_lsn on Spock 5.x, remote_commit_lsn on 6.x. zodan.sql is a standalone file whose version is independent of the Spock installed on the nodes, so neither spelling works everywhere and it must resolve the name at run time -- once per endpoint, since add_node and the node it queries over dblink are different servers. Hard-coding remote_commit_lsn broke add_node on a 5.x mesh from the third node onwards: the failing query lives in a loop over other existing nodes, so a two-node join never reached it. It also failed badly. The retry loop treated a missing column as transient and spun for the whole spock.sync_timeout() budget -- 1800s in the rig that found it -- then reported a replication timeout, while the RAISE NOTICE naming the real cause was suppressed unless verb was set. So also split permanent errors out of that handler: undefined_column, undefined_table, undefined_function and insufficient_privilege now abort at once saying it is not replication lag, and the surviving transient notice is no longer conditional on verb. This matters beyond the column name -- any future version-dependent reference in this script now costs one clear message instead of half an hour of misattributed timeout. --- samples/Z0DAN/zodan.sql | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/samples/Z0DAN/zodan.sql b/samples/Z0DAN/zodan.sql index d0a444b2..afaa1647 100644 --- a/samples/Z0DAN/zodan.sql +++ b/samples/Z0DAN/zodan.sql @@ -92,6 +92,17 @@ RETURNS int[] LANGUAGE sql IMMUTABLE AS $$ SELECT (spock.version_to_array(v))[1:2]; $$; +-- spock.progress's apply-progress column is remote_lsn on Spock 5.x and +-- remote_commit_lsn on 6.x; resolve it per endpoint. The IN list keeps %I safe. +CREATE OR REPLACE FUNCTION spock.progress_commit_lsn_column(p_dsn text) +RETURNS text LANGUAGE sql AS $$ + SELECT * FROM dblink(p_dsn, + $q$SELECT attname::text FROM pg_attribute + WHERE attrelid = 'spock.progress'::regclass + AND attname IN ('remote_commit_lsn', 'remote_lsn') + ORDER BY attname LIMIT 1$q$) AS t(attname text); +$$; + -- ============================================================================ -- Procedure: check_spock_version_compatibility -- Purpose: Verify all nodes have the same Spock version before adding a node @@ -1884,12 +1895,12 @@ BEGIN v_prev_statement_timeout text; BEGIN progress_sql := format( - 'SELECT p.remote_commit_lsn ' + 'SELECT p.%I ' 'FROM spock.progress p ' 'JOIN spock.node n ON n.node_id = p.remote_node_id ' 'WHERE p.node_id = (SELECT node_id FROM spock.node_info()) ' ' AND n.node_name = %L', - rec.node_name); + spock.progress_commit_lsn_column(src_dsn), rec.node_name); RAISE NOTICE ' - Waiting for source node % to apply % changes up to sync LSN %...', src_node_name, rec.node_name, _catchup_lsn; @@ -1905,12 +1916,13 @@ BEGIN PERFORM set_config('statement_timeout', coalesce(v_prev_statement_timeout, '0'), true); EXCEPTION + WHEN undefined_column OR undefined_table + OR undefined_function OR insufficient_privilege THEN + RAISE EXCEPTION 'Cannot read apply progress from source node % (not replication lag): % (SQLSTATE %)', + src_node_name, SQLERRM, SQLSTATE; WHEN OTHERS THEN - -- Let user know if something wrong happens - IF verb THEN - RAISE NOTICE 'An error happened: %', SQLERRM; - END IF; -- Transient probe failure; restore timeout and retry. + RAISE NOTICE 'An error happened: %', SQLERRM; PERFORM set_config('statement_timeout', coalesce(v_prev_statement_timeout, '0'), true); src_progress_lsn := NULL; END; @@ -2497,10 +2509,12 @@ BEGIN -- Slot exists but is not active (unusual). Advance defensively. RAISE NOTICE ' Slot % found at LSN % (inactive)', src_slot_name, current_lsn; - SELECT p.remote_commit_lsn INTO target_lsn - FROM spock.progress p - JOIN spock.node n ON n.node_id = p.remote_node_id - WHERE n.node_name = src_node_name; + EXECUTE format( + 'SELECT p.%I FROM spock.progress p ' + 'JOIN spock.node n ON n.node_id = p.remote_node_id ' + 'WHERE n.node_name = $1', + spock.progress_commit_lsn_column(new_node_dsn)) + INTO target_lsn USING src_node_name; IF target_lsn IS NOT NULL AND target_lsn > current_lsn THEN RAISE NOTICE ' Snapshot LSN for %: %', src_node_name, target_lsn; @@ -2593,10 +2607,12 @@ BEGIN -- Advance the slot to resume_lsn: the last commit from this node -- that N1 had applied at snapshot time (stored in N3's spock.progress). - SELECT p.remote_commit_lsn INTO target_lsn - FROM spock.progress p - JOIN spock.node n ON n.node_id = p.remote_node_id - WHERE n.node_name = rec.node_name; + EXECUTE format( + 'SELECT p.%I FROM spock.progress p ' + 'JOIN spock.node n ON n.node_id = p.remote_node_id ' + 'WHERE n.node_name = $1', + spock.progress_commit_lsn_column(new_node_dsn)) + INTO target_lsn USING rec.node_name; IF target_lsn IS NULL THEN RAISE NOTICE ' WARNING: No spock.progress entry for %, falling back to pg_current_wal_lsn()', rec.node_name;