diff --git a/src/bin/pgcopydb/cli_clone_follow.c b/src/bin/pgcopydb/cli_clone_follow.c index b0ded2b2..ced0f1e1 100644 --- a/src/bin/pgcopydb/cli_clone_follow.c +++ b/src/bin/pgcopydb/cli_clone_follow.c @@ -773,6 +773,21 @@ cloneDB(CopyDataSpec *copySpecs) return false; } + /* + * On --resume the per-run part/index election tables still hold the pids of + * dead prior-run workers. Clear them now, single-threaded and before any + * table-data worker is forked, so a fresh election among live workers + * re-drives index/constraint creation for split (multi-part) tables. + */ + if (copySpecs->resume) + { + if (!copydb_resume_reset_part_election(copySpecs)) + { + /* errors have already been logged */ + return false; + } + } + /* now register in the catalogs the already known startTime */ if (!summary_start_timing(sourceDB, TIMING_SECTION_TOTAL)) { diff --git a/src/bin/pgcopydb/copydb.h b/src/bin/pgcopydb/copydb.h index b41454cb..cfd73b81 100644 --- a/src/bin/pgcopydb/copydb.h +++ b/src/bin/pgcopydb/copydb.h @@ -413,6 +413,7 @@ bool copydb_prepare_sequence_specs(CopyDataSpec *specs, PGSQL *pgsql, bool reset /* copydb_schema.c */ bool copydb_fetch_schema_and_prepare_specs(CopyDataSpec *specs); +bool copydb_resume_reset_part_election(CopyDataSpec *specs); bool copydb_objectid_is_filtered_out(CopyDataSpec *specs, uint32_t catalogOid, uint32_t oid, diff --git a/src/bin/pgcopydb/copydb_schema.c b/src/bin/pgcopydb/copydb_schema.c index 2aada56b..b9d4ee3f 100644 --- a/src/bin/pgcopydb/copydb_schema.c +++ b/src/bin/pgcopydb/copydb_schema.c @@ -179,6 +179,45 @@ copydb_fetch_schema_and_prepare_specs(CopyDataSpec *specs) } +/* + * copydb_resume_reset_part_election clears the per-run "who finished last" + * election tables (s_table_parts_done, s_table_indexes_done) so that a + * --resume run holds a fresh election among live workers. + * + * These tables only ever store per-run election tokens (a tableoid and the + * getpid() of the worker that observed the last part/index as done); progress + * itself lives in the summary table (done_time_epoch). On resume the stored + * pids belong to dead prior-run processes, so no live worker matches and the + * index/constraint enqueue for a split (multi-part) table is never re-driven. + * Clearing the tokens loses no progress and re-enqueue is idempotent + * (IF NOT EXISTS / doneTime / foundConstraintOnTarget checks). + * + * Must run single-threaded before any COPY/table-data worker is forked, or the + * workers would race and wipe freshly-elected tokens. + */ +bool +copydb_resume_reset_part_election(CopyDataSpec *specs) +{ + DatabaseCatalog *sourceDB = &(specs->catalogs.source); + + log_notice("Resetting stale part/index election state for --resume"); + + if (!catalog_execute(sourceDB, "delete from s_table_parts_done")) + { + /* errors have already been logged */ + return false; + } + + if (!catalog_execute(sourceDB, "delete from s_table_indexes_done")) + { + /* errors have already been logged */ + return false; + } + + return true; +} + + /* * copydb_fetch_source_catalog_setup initializes our local catalog cache and * checks the setup and cache state.