Skip to content

Fix memory leaks in row filtering - #530

Open
McFalljb wants to merge 2 commits into
2ndQuadrant:REL2_x_STABLEfrom
McFalljb:fix/filtered-copy-memory-leak
Open

McFalljb wants to merge 2 commits into
2ndQuadrant:REL2_x_STABLEfrom
McFalljb:fix/filtered-copy-memory-leak

Conversation

@McFalljb

Copy link
Copy Markdown

Summary

Row filtering had two cleanup gaps. Filtered initial copies retained the tuple rebuilt for every scanned row. Streaming changes could also return after a rejected filter without releasing executor state or popping the active snapshot.

This change frees rebuilt tuples on both accepted and rejected initial-copy paths. It also defers the streaming filter result until after its executor and snapshot cleanup has completed.

The tuple reconstruction added in #387 is preserved, including correct handling of columns with atthasmissing.

Verification

I ran the same rejected-row initial-copy test against the original and patched builds using a 200,000-row table. Since no rows match, this isolates memory used while scanning and evaluating the filter from memory used to return matching rows.

Build Returned rows Initial backend RSS Peak backend RSS Growth
Original 9a0e182 0 16.5 MiB 223.1 MiB 206.5 MiB
Patched 0 11.0 MiB 24.6 MiB 13.6 MiB

Streaming row filter cleanup

pglogical_change_filter() returned immediately when a streaming row filter evaluated to false or SQL NULL. That skipped ExecDropSingleTupleTableSlot(), FreeExecutorState(), and PopActiveSnapshot(). The second commit records the filter result, performs that cleanup, and then returns it.

I tested both versions by decoding 200,000 inserts rejected by the same streaming row filter:

Build Emitted inserts Initial backend RSS Peak backend RSS Growth
Before streaming cleanup d94c19f 0 14.1 MiB 96.8 MiB 82.6 MiB
After streaming cleanup 056debd 0 17.0 MiB 46.8 MiB 29.8 MiB

I also verified that an accepting streaming filter emitted all 10 test inserts and that a filter evaluating to SQL NULL emitted none.

Additional initial-copy checks covered 100,000 partially accepted rows and 200,000 fully accepted rows. Each case returned the expected count. The post-ALTER TABLE default used to exercise the #387 behavior was also preserved for every returned row.

Supersedes #529, which GitHub closed when the source fork was deleted.

Fixes #477

mcfalljb added 2 commits August 10, 2026 18:56
Reset per-tuple expression memory and release reconstructed tuples after filter evaluation. Tuplestore copies accepted tuples, so both accepted and rejected scan paths can safely free the temporary tuple while preserving atthasmissing handling.
Defer the filter result until after executor and active snapshot cleanup so rejected and NULL filter results do not leak per-change resources.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Does table replication with row_filter require more memory?

1 participant