Base CTID split part count on total size (heap + TOAST) - #50
Merged
Conversation
When a table exceeds --split-tables-larger-than but has no single-column integer unique/PK, pgcopydb splits it by CTID. The part count was computed from table->relpages (heap pages only), so a table whose size is dominated by TOAST became a single COPY job that serially detoasted the whole table. The split decision itself uses pg_table_size (TOAST-inclusive), so such a table is correctly chosen for splitting but then gets one part. Base the CTID part count on table->bytes (total on-disk size), matching the key-based branch, then cap at --split-max-parts and at relpages (cannot create more CTID page-ranges than heap pages), and distribute heap pages across the parts. A CTID range scan detoasts each row it reads, so splitting the heap page range parallelizes the TOAST reads. Also fixes a latent case where relpages == 0 produced zero partitions. Under --estimate-table-sizes, bytes is a heap-only estimate, so this degrades to the previous heap-based behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A migration stalled on a single huge table: heap ~3.3 GiB, TOAST ~936 GiB (99% of a ~942 GiB total), with a
textprimary key. With--split-tables-larger-than 50GBthe table became one COPY job that serially detoasted the entire ~936 GiB — extremely slow.Two different size metrics were used in the split path:
pg_table_size(oid)(TOAST-inclusive) against the threshold, so the table is correctly chosen to be split.text(oruuid) PK is not a single-column integer unique/PK key.relpages(heap pages only, TOAST excluded) by pages-per-part. With a tiny heap relative to a huge TOAST, this rounds down to a single part.So TOAST counted for whether to split but was invisible to how many CTID parts to make.
Fix
Base the CTID part count on
table->bytes(total on-disk size, heap + TOAST), matching the key-based branch, then:--split-max-parts;relpages(cannot create more CTID page-ranges than there are heap pages);A CTID range scan reads heap rows and detoasts each row on the fly, so splitting the heap page range parallelizes the TOAST reads. This also closes a latent case where
relpages == 0produced zero partitions.Under
--estimate-table-sizes,bytesis a heap-only estimate, so the change degrades gracefully to the previous heap-based behavior (no regression there).Behavior change beyond TOAST-heavy tables
The CTID part count is now total-size-based for every CTID-split table, not only TOAST-dominant ones. For a plain table the total size ≈ heap size, so counts track the threshold more directly. This is reflected in the updated
table_ctid_candidategrid in the unit test (splits into more parts than before for the same tiny test threshold).Testing
Added a regression test to the existing split suite (
tests/unit/4-list-table-split):table_toast_heavy) with no integer PK andSTORAGE EXTERNAL(uncompressed → deterministic out-of-line size across PG 16/17/18): heap ~4 pages, TOAST ~6.4 MB.ceil(6.5MB/1MB)=7, capped by--split-max-parts 2to 2 parts. The pinned grid is verified identical on PG17 and PG18.Pre-fix, the new assertion fails (the table reports a single part), proving it is a real regression test.
make build(PG18)make tests/unit(PG18)make tests/unit(PG17)citus_indent --checkmake tests(full, PG18)Docs: added a sentence to
docs/concurrency.rstnoting the CTID part count is derived from total on-disk size (heap + TOAST). No CLI/help changes, so nodocs/include/*.rstregeneration.