mirror of https://github.com/citusdata/citus.git
PG18 – Respect VACUUM/ANALYZE ONLY semantics for Citus tables (#8365)
fixes #8364 PostgreSQL 18 changes VACUUM/ANALYZE to recurse into inheritance children by default, and introduces `ONLY` to limit processing to the parent. Upstream change: [https://github.com/postgres/postgres/commit/62ddf7ee9](https://github.com/postgres/postgres/commit/62ddf7ee9) For Citus tables, we should treat shard placements as “children” and avoid propagating `VACUUM/ANALYZE` to shards when the user explicitly asks for `ONLY`. This PR adjusts the Citus VACUUM handling to align with PG18 semantics, and adds regression coverage on both regular distributed tables and partitioned distributed tables. --- ### Behavior changes * Introduce a per-relation helper struct: ```c typedef struct CitusVacuumRelation { VacuumRelation *vacuumRelation; Oid relationId; } CitusVacuumRelation; ``` This lets us keep both: * the resolved relation OID (for `IsCitusTable`, task building), and * the original `VacuumRelation` node (for column list and ONLY/inh flag). * Replace the old `VacuumRelationIdList` / `ExtractVacuumTargetRels` flow with: ```c static List *VacuumRelationList(VacuumStmt *vacuumStmt, CitusVacuumParams vacuumParams); ``` `VacuumRelationList` now: * Iterates over `vacuumStmt->rels`. * Resolves `relid` via `RangeVarGetRelidExtended` when `relation` is present. * Falls back to locking `VacuumRelation->oid` when only an OID is available. * Respects `VACOPT_FULL` for lock mode and `VACOPT_SKIP_LOCKED` for locking behavior. * Builds a `List *` of `CitusVacuumRelation` entries. * Update: ```c IsDistributedVacuumStmt(List *vacuumRelationList); ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *vacuumRelationList, CitusVacuumParams vacuumParams); ``` to operate on `CitusVacuumRelation` instead of bare OIDs. * Implement `ONLY` semantics in `ExecuteVacuumOnDistributedTables`: ```c RangeVar *relation = vacuumRelation->relation; if (relation != NULL && !relation->inh) { /* ONLY specified, so don't recurse to shard placements */ continue; } ``` Effect: * `VACUUM / ANALYZE` (no `ONLY`) on a Citus table: behavior unchanged, Citus creates tasks and propagates to shard placements. * `VACUUM ONLY <citus_table>` / `ANALYZE ONLY <citus_table>`: * Core still processes the coordinator relation as usual. * Citus **skips** building tasks for shard placements, so we do not recurse into distributed children. * The code compiles and behaves as before on pre-PG18; the new behavior becomes observable only when the core planner starts setting `inh = false` for `ONLY` (PG18). * Unqualified `VACUUM` / `ANALYZE` (no rels) is unchanged and still handled via `ExecuteUnqualifiedVacuumTasks`. * Remove now-redundant helpers: * `VacuumColumnList` * `ExtractVacuumTargetRels` Column lists are now taken directly from `vacuumRelation->va_cols` via `CitusVacuumRelation`. --- ### Testing Extend `src/test/regress/sql/pg18.sql` and `expected/pg18.out` with two PG18-only blocks that verify we do not recurse into shard placements when `ONLY` is used: 1. **Simple distributed table (`pg18_vacuum_part`)** * Create and distribute a regular table: ```sql CREATE SCHEMA pg18_vacuum_part; SET search_path TO pg18_vacuum_part; CREATE TABLE vac_analyze_only (a int); SELECT create_distributed_table('vac_analyze_only', 'a'); INSERT INTO vac_analyze_only VALUES (1), (2), (3); ``` * On the coordinator: * Run `ANALYZE vac_analyze_only;` and later `ANALYZE ONLY vac_analyze_only;`. * Run `VACUUM vac_analyze_only;` and later `VACUUM ONLY vac_analyze_only;`. * On `worker_1`: * Capture `coalesce(max(last_analyze), 'epoch')` from `pg_stat_user_tables` for `vac_analyze_only_%` into `:analyze_before_only`, then assert: ```sql SELECT max(last_analyze) = :'analyze_before_only'::timestamptz AS analyze_only_skipped; ``` * Capture `coalesce(max(last_vacuum), 'epoch')` into `:vacuum_before_only`, then assert: ```sql SELECT max(last_vacuum) = :'vacuum_before_only'::timestamptz AS vacuum_only_skipped; ``` Both checks return `t`, confirming `ONLY` does not change `last_analyze` / `last_vacuum` on shard tables. 2. **Partitioned distributed table (`pg18_vacuum_part_dist`)** * Create a partitioned table whose parent is distributed: ```sql CREATE SCHEMA pg18_vacuum_part_dist; SET search_path TO pg18_vacuum_part_dist; SET citus.shard_count = 2; SET citus.shard_replication_factor = 1; CREATE TABLE part_dist (id int, v int) PARTITION BY RANGE (id); CREATE TABLE part_dist_1 PARTITION OF part_dist FOR VALUES FROM (1) TO (100); CREATE TABLE part_dist_2 PARTITION OF part_dist FOR VALUES FROM (100) TO (200); SELECT create_distributed_table('part_dist', 'id'); INSERT INTO part_dist SELECT g, g FROM generate_series(1, 199) g; ``` * On the coordinator: * Run `ANALYZE part_dist;` then `ANALYZE ONLY part_dist;`. * Run `VACUUM part_dist;` then `VACUUM ONLY part_dist;` (PG18 emits the expected warning: `VACUUM ONLY of partitioned table "part_dist" has no effect`). * On `worker_1`: * Capture `coalesce(max(last_analyze), 'epoch')` for `part_dist_%` into `:analyze_before_only`, then assert: ```sql SELECT max(last_analyze) = :'analyze_before_only'::timestamptz AS analyze_only_partitioned_skipped; ``` * Capture `coalesce(max(last_vacuum), 'epoch')` into `:vacuum_before_only`, then assert: ```sql SELECT max(last_vacuum) = :'vacuum_before_only'::timestamptz AS vacuum_only_partitioned_skipped; ``` Both checks return `t`, confirming that even for a partitioned distributed parent, `VACUUM/ANALYZE ONLY` does not recurse into shard placements, and Citus behavior matches PG18’s “ONLY = parent only” semantics.main
parent
3399d660f3
commit
31911d8297
|
|
@ -48,21 +48,27 @@ typedef struct CitusVacuumParams
|
||||||
#endif
|
#endif
|
||||||
} CitusVacuumParams;
|
} CitusVacuumParams;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Information we track per VACUUM/ANALYZE target relation.
|
||||||
|
*/
|
||||||
|
typedef struct CitusVacuumRelation
|
||||||
|
{
|
||||||
|
VacuumRelation *vacuumRelation;
|
||||||
|
Oid relationId;
|
||||||
|
} CitusVacuumRelation;
|
||||||
|
|
||||||
/* Local functions forward declarations for processing distributed table commands */
|
/* Local functions forward declarations for processing distributed table commands */
|
||||||
static bool IsDistributedVacuumStmt(List *vacuumRelationIdList);
|
static bool IsDistributedVacuumStmt(List *vacuumRelationList);
|
||||||
static List * VacuumTaskList(Oid relationId, CitusVacuumParams vacuumParams,
|
static List * VacuumTaskList(Oid relationId, CitusVacuumParams vacuumParams,
|
||||||
List *vacuumColumnList);
|
List *vacuumColumnList);
|
||||||
static char * DeparseVacuumStmtPrefix(CitusVacuumParams vacuumParams);
|
static char * DeparseVacuumStmtPrefix(CitusVacuumParams vacuumParams);
|
||||||
static char * DeparseVacuumColumnNames(List *columnNameList);
|
static char * DeparseVacuumColumnNames(List *columnNameList);
|
||||||
static List * VacuumColumnList(VacuumStmt *vacuumStmt, int relationIndex);
|
static void ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *relationList,
|
||||||
static List * ExtractVacuumTargetRels(VacuumStmt *vacuumStmt);
|
|
||||||
static void ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *relationIdList,
|
|
||||||
CitusVacuumParams vacuumParams);
|
CitusVacuumParams vacuumParams);
|
||||||
static void ExecuteUnqualifiedVacuumTasks(VacuumStmt *vacuumStmt,
|
static void ExecuteUnqualifiedVacuumTasks(VacuumStmt *vacuumStmt,
|
||||||
CitusVacuumParams vacuumParams);
|
CitusVacuumParams vacuumParams);
|
||||||
static CitusVacuumParams VacuumStmtParams(VacuumStmt *vacstmt);
|
static CitusVacuumParams VacuumStmtParams(VacuumStmt *vacstmt);
|
||||||
static List * VacuumRelationIdList(VacuumStmt *vacuumStmt, CitusVacuumParams
|
static List * VacuumRelationList(VacuumStmt *vacuumStmt, CitusVacuumParams vacuumParams);
|
||||||
vacuumParams);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PostprocessVacuumStmt processes vacuum statements that may need propagation to
|
* PostprocessVacuumStmt processes vacuum statements that may need propagation to
|
||||||
|
|
@ -97,7 +103,7 @@ PostprocessVacuumStmt(Node *node, const char *vacuumCommand)
|
||||||
* when no table is specified propagate the command as it is;
|
* when no table is specified propagate the command as it is;
|
||||||
* otherwise, only propagate when there is at least 1 citus table
|
* otherwise, only propagate when there is at least 1 citus table
|
||||||
*/
|
*/
|
||||||
List *relationIdList = VacuumRelationIdList(vacuumStmt, vacuumParams);
|
List *vacuumRelationList = VacuumRelationList(vacuumStmt, vacuumParams);
|
||||||
|
|
||||||
if (list_length(vacuumStmt->rels) == 0)
|
if (list_length(vacuumStmt->rels) == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -105,11 +111,11 @@ PostprocessVacuumStmt(Node *node, const char *vacuumCommand)
|
||||||
|
|
||||||
ExecuteUnqualifiedVacuumTasks(vacuumStmt, vacuumParams);
|
ExecuteUnqualifiedVacuumTasks(vacuumStmt, vacuumParams);
|
||||||
}
|
}
|
||||||
else if (IsDistributedVacuumStmt(relationIdList))
|
else if (IsDistributedVacuumStmt(vacuumRelationList))
|
||||||
{
|
{
|
||||||
/* there is at least 1 citus table specified */
|
/* there is at least 1 citus table specified */
|
||||||
|
|
||||||
ExecuteVacuumOnDistributedTables(vacuumStmt, relationIdList,
|
ExecuteVacuumOnDistributedTables(vacuumStmt, vacuumRelationList,
|
||||||
vacuumParams);
|
vacuumParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,39 +126,58 @@ PostprocessVacuumStmt(Node *node, const char *vacuumCommand)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* VacuumRelationIdList returns the oid of the relations in the given vacuum statement.
|
* VacuumRelationList returns the list of relations in the given vacuum statement,
|
||||||
|
* along with their resolved Oids (if they can be locked).
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
VacuumRelationIdList(VacuumStmt *vacuumStmt, CitusVacuumParams vacuumParams)
|
VacuumRelationList(VacuumStmt *vacuumStmt, CitusVacuumParams vacuumParams)
|
||||||
{
|
{
|
||||||
LOCKMODE lockMode = (vacuumParams.options & VACOPT_FULL) ? AccessExclusiveLock :
|
LOCKMODE lockMode = (vacuumParams.options & VACOPT_FULL) ? AccessExclusiveLock :
|
||||||
ShareUpdateExclusiveLock;
|
ShareUpdateExclusiveLock;
|
||||||
|
|
||||||
bool skipLocked = (vacuumParams.options & VACOPT_SKIP_LOCKED);
|
bool skipLocked = (vacuumParams.options & VACOPT_SKIP_LOCKED);
|
||||||
|
|
||||||
List *vacuumRelationList = ExtractVacuumTargetRels(vacuumStmt);
|
List *relationList = NIL;
|
||||||
|
|
||||||
List *relationIdList = NIL;
|
VacuumRelation *vacuumRelation = NULL;
|
||||||
|
foreach_declared_ptr(vacuumRelation, vacuumStmt->rels)
|
||||||
RangeVar *vacuumRelation = NULL;
|
|
||||||
foreach_declared_ptr(vacuumRelation, vacuumRelationList)
|
|
||||||
{
|
{
|
||||||
|
Oid relationId = InvalidOid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If skip_locked option is enabled, we are skipping that relation
|
* If skip_locked option is enabled, we are skipping that relation
|
||||||
* if the lock for it is currently not available; else, we get the lock.
|
* if the lock for it is currently not available; otherwise, we get the lock.
|
||||||
*/
|
*/
|
||||||
Oid relationId = RangeVarGetRelidExtended(vacuumRelation,
|
if (vacuumRelation->relation)
|
||||||
|
{
|
||||||
|
relationId = RangeVarGetRelidExtended(vacuumRelation->relation,
|
||||||
lockMode,
|
lockMode,
|
||||||
skipLocked ? RVR_SKIP_LOCKED : 0, NULL,
|
skipLocked ? RVR_SKIP_LOCKED : 0, NULL,
|
||||||
NULL);
|
NULL);
|
||||||
|
}
|
||||||
|
else if (OidIsValid(vacuumRelation->oid))
|
||||||
|
{
|
||||||
|
/* fall back to the Oid directly when provided */
|
||||||
|
if (!skipLocked || ConditionalLockRelationOid(vacuumRelation->oid, lockMode))
|
||||||
|
{
|
||||||
|
if (!skipLocked)
|
||||||
|
{
|
||||||
|
LockRelationOid(vacuumRelation->oid, lockMode);
|
||||||
|
}
|
||||||
|
relationId = vacuumRelation->oid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (OidIsValid(relationId))
|
if (OidIsValid(relationId))
|
||||||
{
|
{
|
||||||
relationIdList = lappend_oid(relationIdList, relationId);
|
CitusVacuumRelation *relation = palloc(sizeof(CitusVacuumRelation));
|
||||||
|
relation->vacuumRelation = vacuumRelation;
|
||||||
|
relation->relationId = relationId;
|
||||||
|
relationList = lappend(relationList, relation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return relationIdList;
|
return relationList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -161,12 +186,13 @@ VacuumRelationIdList(VacuumStmt *vacuumStmt, CitusVacuumParams vacuumParams)
|
||||||
* otherwise, it returns false.
|
* otherwise, it returns false.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
IsDistributedVacuumStmt(List *vacuumRelationIdList)
|
IsDistributedVacuumStmt(List *vacuumRelationList)
|
||||||
{
|
{
|
||||||
Oid relationId = InvalidOid;
|
CitusVacuumRelation *vacuumRelation = NULL;
|
||||||
foreach_declared_oid(relationId, vacuumRelationIdList)
|
foreach_declared_ptr(vacuumRelation, vacuumRelationList)
|
||||||
{
|
{
|
||||||
if (OidIsValid(relationId) && IsCitusTable(relationId))
|
if (OidIsValid(vacuumRelation->relationId) &&
|
||||||
|
IsCitusTable(vacuumRelation->relationId))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -181,24 +207,31 @@ IsDistributedVacuumStmt(List *vacuumRelationIdList)
|
||||||
* if they are citus tables.
|
* if they are citus tables.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *relationIdList,
|
ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *relationList,
|
||||||
CitusVacuumParams vacuumParams)
|
CitusVacuumParams vacuumParams)
|
||||||
{
|
{
|
||||||
int relationIndex = 0;
|
CitusVacuumRelation *vacuumRelationEntry = NULL;
|
||||||
|
foreach_declared_ptr(vacuumRelationEntry, relationList)
|
||||||
Oid relationId = InvalidOid;
|
|
||||||
foreach_declared_oid(relationId, relationIdList)
|
|
||||||
{
|
{
|
||||||
|
Oid relationId = vacuumRelationEntry->relationId;
|
||||||
|
VacuumRelation *vacuumRelation = vacuumRelationEntry->vacuumRelation;
|
||||||
|
|
||||||
|
RangeVar *relation = vacuumRelation->relation;
|
||||||
|
if (relation != NULL && !relation->inh)
|
||||||
|
{
|
||||||
|
/* ONLY specified, so don't recurse to shard placements */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsCitusTable(relationId))
|
if (IsCitusTable(relationId))
|
||||||
{
|
{
|
||||||
List *vacuumColumnList = VacuumColumnList(vacuumStmt, relationIndex);
|
List *vacuumColumnList = vacuumRelation->va_cols;
|
||||||
List *taskList = VacuumTaskList(relationId, vacuumParams, vacuumColumnList);
|
List *taskList = VacuumTaskList(relationId, vacuumParams, vacuumColumnList);
|
||||||
|
|
||||||
/* local execution is not implemented for VACUUM commands */
|
/* local execution is not implemented for VACUUM commands */
|
||||||
bool localExecutionSupported = false;
|
bool localExecutionSupported = false;
|
||||||
ExecuteUtilityTaskList(taskList, localExecutionSupported);
|
ExecuteUtilityTaskList(taskList, localExecutionSupported);
|
||||||
}
|
}
|
||||||
relationIndex++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,39 +517,6 @@ DeparseVacuumColumnNames(List *columnNameList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* VacuumColumnList returns list of columns from relation
|
|
||||||
* in the vacuum statement at specified relationIndex.
|
|
||||||
*/
|
|
||||||
static List *
|
|
||||||
VacuumColumnList(VacuumStmt *vacuumStmt, int relationIndex)
|
|
||||||
{
|
|
||||||
VacuumRelation *vacuumRelation = (VacuumRelation *) list_nth(vacuumStmt->rels,
|
|
||||||
relationIndex);
|
|
||||||
|
|
||||||
return vacuumRelation->va_cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ExtractVacuumTargetRels returns list of target
|
|
||||||
* relations from vacuum statement.
|
|
||||||
*/
|
|
||||||
static List *
|
|
||||||
ExtractVacuumTargetRels(VacuumStmt *vacuumStmt)
|
|
||||||
{
|
|
||||||
List *vacuumList = NIL;
|
|
||||||
|
|
||||||
VacuumRelation *vacuumRelation = NULL;
|
|
||||||
foreach_declared_ptr(vacuumRelation, vacuumStmt->rels)
|
|
||||||
{
|
|
||||||
vacuumList = lappend(vacuumList, vacuumRelation->relation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return vacuumList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* VacuumStmtParams returns a CitusVacuumParams based on the supplied VacuumStmt.
|
* VacuumStmtParams returns a CitusVacuumParams based on the supplied VacuumStmt.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1802,6 +1802,156 @@ NOTICE: renaming the new table to pg18_nn.generated_stored_ref
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
-- PG18 Feature: VACUUM/ANALYZE support ONLY to limit processing to the parent.
|
||||||
|
-- For Citus, ensure ONLY does not trigger shard propagation.
|
||||||
|
-- PG18 commit: https://github.com/postgres/postgres/commit/62ddf7ee9
|
||||||
|
CREATE SCHEMA pg18_vacuum_part;
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
CREATE TABLE vac_analyze_only (a int);
|
||||||
|
SELECT create_distributed_table('vac_analyze_only', 'a');
|
||||||
|
create_distributed_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
INSERT INTO vac_analyze_only VALUES (1), (2), (3);
|
||||||
|
-- ANALYZE (no ONLY) should recurse into shard placements
|
||||||
|
ANALYZE vac_analyze_only;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
SELECT coalesce(max(last_analyze), 'epoch'::timestamptz) AS analyze_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%'
|
||||||
|
\gset
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
-- ANALYZE ONLY should not recurse into shard placements
|
||||||
|
ANALYZE ONLY vac_analyze_only;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
SELECT max(last_analyze) = :'analyze_before_only'::timestamptz
|
||||||
|
AS analyze_only_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%';
|
||||||
|
analyze_only_skipped
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
-- VACUUM (no ONLY) should recurse into shard placements
|
||||||
|
VACUUM vac_analyze_only;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
SELECT coalesce(max(last_vacuum), 'epoch'::timestamptz) AS vacuum_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%'
|
||||||
|
\gset
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
-- VACUUM ONLY should not recurse into shard placements
|
||||||
|
VACUUM ONLY vac_analyze_only;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
SELECT max(last_vacuum) = :'vacuum_before_only'::timestamptz
|
||||||
|
AS vacuum_only_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%';
|
||||||
|
vacuum_only_skipped
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
DROP SCHEMA pg18_vacuum_part CASCADE;
|
||||||
|
NOTICE: drop cascades to table vac_analyze_only
|
||||||
|
SET search_path TO pg18_nn;
|
||||||
|
-- END PG18 Feature: VACUUM/ANALYZE support ONLY to limit processing to the parent
|
||||||
|
-- PG18 Feature: VACUUM/ANALYZE ONLY on a partitioned distributed table
|
||||||
|
-- Ensure Citus does not recurse into shard placements when ONLY is used
|
||||||
|
-- on the partitioned parent.
|
||||||
|
-- PG18 commit: https://github.com/postgres/postgres/commit/62ddf7ee9
|
||||||
|
CREATE SCHEMA pg18_vacuum_part_dist;
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
SET citus.shard_count = 2;
|
||||||
|
SET citus.shard_replication_factor = 1;
|
||||||
|
CREATE TABLE part_dist (id int, v int) PARTITION BY RANGE (id);
|
||||||
|
CREATE TABLE part_dist_1 PARTITION OF part_dist FOR VALUES FROM (1) TO (100);
|
||||||
|
CREATE TABLE part_dist_2 PARTITION OF part_dist FOR VALUES FROM (100) TO (200);
|
||||||
|
SELECT create_distributed_table('part_dist', 'id');
|
||||||
|
create_distributed_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
INSERT INTO part_dist
|
||||||
|
SELECT g, g FROM generate_series(1, 199) g;
|
||||||
|
-- ANALYZE (no ONLY) should recurse into partitions and shard placements
|
||||||
|
ANALYZE part_dist;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
SELECT coalesce(max(last_analyze), 'epoch'::timestamptz) AS analyze_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%'
|
||||||
|
\gset
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
-- ANALYZE ONLY should not recurse into shard placements
|
||||||
|
ANALYZE ONLY part_dist;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
SELECT max(last_analyze) = :'analyze_before_only'::timestamptz
|
||||||
|
AS analyze_only_partitioned_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%';
|
||||||
|
analyze_only_partitioned_skipped
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
-- VACUUM (no ONLY) should recurse into partitions and shard placements
|
||||||
|
VACUUM part_dist;
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
SELECT coalesce(max(last_vacuum), 'epoch'::timestamptz) AS vacuum_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%'
|
||||||
|
\gset
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
-- VACUUM ONLY parent: core warns and does no work; Citus must not
|
||||||
|
-- propagate to shard placements.
|
||||||
|
VACUUM ONLY part_dist;
|
||||||
|
WARNING: VACUUM ONLY of partitioned table "part_dist" has no effect
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
SELECT max(last_vacuum) = :'vacuum_before_only'::timestamptz
|
||||||
|
AS vacuum_only_partitioned_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%';
|
||||||
|
vacuum_only_partitioned_skipped
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
DROP SCHEMA pg18_vacuum_part_dist CASCADE;
|
||||||
|
NOTICE: drop cascades to table part_dist
|
||||||
|
SET search_path TO pg18_nn;
|
||||||
|
-- END PG18 Feature: VACUUM/ANALYZE ONLY on partitioned distributed table
|
||||||
-- cleanup with minimum verbosity
|
-- cleanup with minimum verbosity
|
||||||
SET client_min_messages TO ERROR;
|
SET client_min_messages TO ERROR;
|
||||||
RESET search_path;
|
RESET search_path;
|
||||||
|
|
|
||||||
|
|
@ -1120,6 +1120,166 @@ BEGIN;
|
||||||
SELECT * FROM generated_stored_ref;
|
SELECT * FROM generated_stored_ref;
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- PG18 Feature: VACUUM/ANALYZE support ONLY to limit processing to the parent.
|
||||||
|
-- For Citus, ensure ONLY does not trigger shard propagation.
|
||||||
|
-- PG18 commit: https://github.com/postgres/postgres/commit/62ddf7ee9
|
||||||
|
CREATE SCHEMA pg18_vacuum_part;
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
CREATE TABLE vac_analyze_only (a int);
|
||||||
|
SELECT create_distributed_table('vac_analyze_only', 'a');
|
||||||
|
INSERT INTO vac_analyze_only VALUES (1), (2), (3);
|
||||||
|
|
||||||
|
-- ANALYZE (no ONLY) should recurse into shard placements
|
||||||
|
ANALYZE vac_analyze_only;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
SELECT coalesce(max(last_analyze), 'epoch'::timestamptz) AS analyze_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%'
|
||||||
|
\gset
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
-- ANALYZE ONLY should not recurse into shard placements
|
||||||
|
ANALYZE ONLY vac_analyze_only;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
SELECT max(last_analyze) = :'analyze_before_only'::timestamptz
|
||||||
|
AS analyze_only_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%';
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
-- VACUUM (no ONLY) should recurse into shard placements
|
||||||
|
VACUUM vac_analyze_only;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
SELECT coalesce(max(last_vacuum), 'epoch'::timestamptz) AS vacuum_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%'
|
||||||
|
\gset
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
-- VACUUM ONLY should not recurse into shard placements
|
||||||
|
VACUUM ONLY vac_analyze_only;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
SELECT max(last_vacuum) = :'vacuum_before_only'::timestamptz
|
||||||
|
AS vacuum_only_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part'
|
||||||
|
AND relname LIKE 'vac_analyze_only_%';
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part;
|
||||||
|
|
||||||
|
DROP SCHEMA pg18_vacuum_part CASCADE;
|
||||||
|
SET search_path TO pg18_nn;
|
||||||
|
|
||||||
|
-- END PG18 Feature: VACUUM/ANALYZE support ONLY to limit processing to the parent
|
||||||
|
|
||||||
|
-- PG18 Feature: VACUUM/ANALYZE ONLY on a partitioned distributed table
|
||||||
|
-- Ensure Citus does not recurse into shard placements when ONLY is used
|
||||||
|
-- on the partitioned parent.
|
||||||
|
-- PG18 commit: https://github.com/postgres/postgres/commit/62ddf7ee9
|
||||||
|
CREATE SCHEMA pg18_vacuum_part_dist;
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
SET citus.shard_count = 2;
|
||||||
|
SET citus.shard_replication_factor = 1;
|
||||||
|
|
||||||
|
CREATE TABLE part_dist (id int, v int) PARTITION BY RANGE (id);
|
||||||
|
CREATE TABLE part_dist_1 PARTITION OF part_dist FOR VALUES FROM (1) TO (100);
|
||||||
|
CREATE TABLE part_dist_2 PARTITION OF part_dist FOR VALUES FROM (100) TO (200);
|
||||||
|
|
||||||
|
SELECT create_distributed_table('part_dist', 'id');
|
||||||
|
|
||||||
|
INSERT INTO part_dist
|
||||||
|
SELECT g, g FROM generate_series(1, 199) g;
|
||||||
|
|
||||||
|
-- ANALYZE (no ONLY) should recurse into partitions and shard placements
|
||||||
|
ANALYZE part_dist;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
SELECT coalesce(max(last_analyze), 'epoch'::timestamptz) AS analyze_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%'
|
||||||
|
\gset
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
-- ANALYZE ONLY should not recurse into shard placements
|
||||||
|
ANALYZE ONLY part_dist;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
SELECT max(last_analyze) = :'analyze_before_only'::timestamptz
|
||||||
|
AS analyze_only_partitioned_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%';
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
-- VACUUM (no ONLY) should recurse into partitions and shard placements
|
||||||
|
VACUUM part_dist;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
SELECT coalesce(max(last_vacuum), 'epoch'::timestamptz) AS vacuum_before_only
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%'
|
||||||
|
\gset
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
-- VACUUM ONLY parent: core warns and does no work; Citus must not
|
||||||
|
-- propagate to shard placements.
|
||||||
|
VACUUM ONLY part_dist;
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
SELECT max(last_vacuum) = :'vacuum_before_only'::timestamptz
|
||||||
|
AS vacuum_only_partitioned_skipped
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = 'pg18_vacuum_part_dist'
|
||||||
|
AND relname LIKE 'part_dist_%';
|
||||||
|
|
||||||
|
\c - - - :master_port
|
||||||
|
SET search_path TO pg18_vacuum_part_dist;
|
||||||
|
|
||||||
|
DROP SCHEMA pg18_vacuum_part_dist CASCADE;
|
||||||
|
SET search_path TO pg18_nn;
|
||||||
|
|
||||||
|
-- END PG18 Feature: VACUUM/ANALYZE ONLY on partitioned distributed table
|
||||||
|
|
||||||
-- cleanup with minimum verbosity
|
-- cleanup with minimum verbosity
|
||||||
SET client_min_messages TO ERROR;
|
SET client_min_messages TO ERROR;
|
||||||
RESET search_path;
|
RESET search_path;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue