Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved build propagation, producer handling, memory allocation, regression baseline, and license-audit issues prevent approval.
7 open issues
Add RAT license coverage for new RPT files · New Avoid treating physical segment IDs as dense part indexes · New Avoid Bloom allocation for non-executing producer nodes · New Serialize total producer parts in Bloom fold payloads · New Regenerate rpt_regions baseline with psql formatting · New Remove null-rejecting predicate from FULL JOIN test · New Export with_rpt in configure substitutions · New
What changed in this PR
This PR introduces Anser runtime Bloom filters and an analysis-only RPT+ extension for planning predicate-transfer schedules across joins. It also adds supporting dispatcher, serialization, build, and test infrastructure.
Changes:
- Add Bloom-filter production, consumption, payload handling, and extension hooks.
- Add RPT transfer-plan analysis, SQL inspection, and regression fixtures.
- Extend dispatch-side messaging and integrate optional builds and CI coverage.
| File | Description |
|---|---|
| src/test/modules/meson.build | Register the libpq notification test. |
| src/test/modules/Makefile | Include the notification test in Make builds. |
| src/test/modules/libpq_isbusy_notify/t/001_isbusy_notify.pl | Run the standalone notification check. |
| src/test/modules/libpq_isbusy_notify/meson.build | Define the test executable and TAP integration. |
| src/test/modules/libpq_isbusy_notify/Makefile | Build the standalone libpq test. |
| src/test/modules/libpq_isbusy_notify/libpq_isbusy_notify.c | Check queued notifications while PQisBusy remains true. |
| src/test/modules/libpq_isbusy_notify/.gitignore | Ignore generated test artifacts. |
| src/Makefile.global.in | Export the Anser build setting. |
| src/include/lib/bloomfilter.h | Declare Bloom bitset access and reconstruction APIs. |
| src/include/cdb/cdbvars.h | Define the sideband protocol message type. |
| src/include/cdb/cdbdisp.h | Expose an extension notification hook. |
| src/backend/tcop/postgres.c | Accept and discard unexpected sideband messages. |
| src/backend/nodes/gen_node_support.pl | Correct CustomScan binary method-name serialization. |
| src/backend/lib/bloomfilter.c | Implement Bloom bitset access and reconstruction. |
| src/backend/commands/sequence.c | Skip sideband messages while awaiting sequence replies. |
| src/backend/cdb/dispatcher/cdbdisp_async.c | Handle buffered notifications and nonblocking output. |
| pom.xml | Add Anser license-audit exclusions. |
| licenses/LICENSE-mit-duckdb-rpt.txt | Record the upstream transfer-graph license. |
| gpcontrib/robust_predicate_transfer/src/rptinit.cpp | Register RPT settings, planner hook, and SQL inspection. |
| gpcontrib/robust_predicate_transfer/sql/rpt_regions.sql | Test transfer-region boundaries. |
| gpcontrib/robust_predicate_transfer/sql/rpt_graph.sql | Test graph structure and consumer eligibility. |
| gpcontrib/robust_predicate_transfer/robust_predicate_transfer.control | Define RPT extension metadata. |
| gpcontrib/robust_predicate_transfer/robust_predicate_transfer--1.0.sql | Expose the transfer-plan SQL function. |
| gpcontrib/robust_predicate_transfer/README.md | Describe scope, setup, and testing. |
| gpcontrib/robust_predicate_transfer/Makefile | Build RPT and prepare regression runs. |
| gpcontrib/robust_predicate_transfer/include/rpt.h | Define the transfer-analysis data model. |
| gpcontrib/robust_predicate_transfer/expected/rpt_regions.out | Supply region-test expected output. |
| gpcontrib/robust_predicate_transfer/expected/rpt_graph.out | Supply graph-test expected output. |
| gpcontrib/robust_predicate_transfer/docs/design.md | Document architecture and correctness constraints. |
| gpcontrib/Makefile | Add optional extension build targets. |
| gpcontrib/anser/test/Makefile | Build and run standalone fold tests. |
| gpcontrib/anser/test/anserfold_test.c | Check Bloom unions and header preservation. |
| gpcontrib/anser/test/.gitignore | Ignore the fold-test executable. |
| gpcontrib/anser/src/anserpayload.c | Register payload-specific merge and checksum behavior. |
| gpcontrib/anser/src/anserinit.c | Register Anser settings and lifecycle hooks. |
| gpcontrib/anser/src/anserfilter.c | Implement Bloom sizing, serialization, and merging. |
| gpcontrib/anser/src/anserbloomproduce.c | Build and publish producer filters. |
| gpcontrib/anser/src/anserbloomconsume.c | Receive and reconstruct consumer filters. |
| gpcontrib/anser/sql/anser_test.sql | Exercise payload, protocol, and planning helpers. |
| gpcontrib/anser/sql/anser_multijoin.sql | Compare filtered and unfiltered multi-join results. |
| gpcontrib/anser/sql/anser_faults.sql | Exercise delayed delivery and protocol fault cases. |
| gpcontrib/anser/Makefile | Build Anser and prepare its test environment. |
| gpcontrib/anser/include/ansersideband.h | Define transport framing and sideband APIs. |
| gpcontrib/anser/include/anserplan.h | Declare injection and scan-pushdown interfaces. |
| gpcontrib/anser/include/anserpayload.h | Define payload-operation contracts. |
| gpcontrib/anser/include/anserfilter.h | Define Bloom payload framing and thresholds. |
| gpcontrib/anser/include/anserbloom.h | Expose producer and consumer executor helpers. |
| gpcontrib/anser/include/anser.h | Define channel identity and shared settings. |
| gpcontrib/anser/expected/anser_multijoin.out | Supply multi-join expected results. |
| gpcontrib/anser/expected/anser_faults.out | Supply fault-test expected results. |
| gpcontrib/anser/anser_test/Makefile | Build the separate SQL test harness. |
| gpcontrib/anser/anser_test/anser_test.control | Define test-extension metadata. |
| gpcontrib/anser/anser_test/anser_test--1.0.sql | Register SQL-accessible test helpers. |
| devops/build/automation/cloudberry/scripts/configure-cloudberry.sh | Support opt-in Anser automation builds. |
| configure.ac | Declare optional Anser and RPT builds. |
| configure | Include generated configure-option handling. |
| .github/workflows/build-cloudberry.yml | Add Anser and RPT integration-test jobs. |
Review effort: Balanced
Comment on lines
+1431
to
+1433
| <exclude>gpcontrib/anser/anser_test.control</exclude> | ||
| <exclude>gpcontrib/anser/**/*.sql</exclude> | ||
| <exclude>gpcontrib/anser/**/*.out</exclude> |
| AnserBloomFilterProduceState *state; | ||
| uint64 seed; | ||
|
|
||
| if (channel_key == NULL || total_parts == 0 || part_index >= total_parts) |
| state->part_index = part_index; | ||
| state->total_parts = total_parts; | ||
| seed = AnserBloomSeed(channel_key->condition_key); | ||
| state->filter = AnserBloomCreate(total_elems, max_payload_bytes, seed); |
Comment on lines
+165
to
+179
| /* | ||
| * Serialize as a self-contained single part (index 0 of 1). The coordinator | ||
| * stores the first part verbatim and OR-folds each later part, bumping the | ||
| * merged header's fold count, so the final count reflects how many parts were | ||
| * unioned. state->part_index / state->total_parts identify this producer to | ||
| * the channel (expected_producers), not the on-wire part layout. | ||
| */ | ||
| payload_size = AnserBloomSerializedSize(state->filter); | ||
| payload = palloc(payload_size); | ||
| ok = AnserBloomSerializePart(state->filter, | ||
| 0, | ||
| 1, | ||
| payload, | ||
| payload_size, | ||
| &payload_len); |
Comment on lines
+31
to
+35
| rpt_transfer_plan | ||
| ----------------------- | ||
| transfer order: 2 1 | ||
| vertex 1 rpt_b rows=10 unfiltered | ||
| vertex 2 rpt_a rows=100 unfiltered |
|
|
||
| -- FULL JOIN produces no usable edge at all: no region. | ||
| SELECT rpt_transfer_plan( | ||
| 'SELECT * FROM rpt_a a FULL JOIN rpt_b b ON a.a = b.a WHERE a.b > 50') |
| have_yaml = @have_yaml@ | ||
| YAML_LIBS = @YAML_LIBS@ | ||
| with_zstd = @with_zstd@ | ||
| with_anser = @with_anser@ |
leborchuk
force-pushed
the
rpt
branch
2 times, most recently
from
November 9, 2026 12:56
5d800d2 to
80b6f2d
Compare
Hash and nested-loop joins create two partition propagation requests for every combination of their other property requests: request 0 may push DPE predicates to a child, request 1 passes the incoming requirement through. Parallel hash joins always create both, and so does every nested-loop join. When the join has no partition consumers below it and its parent requires no partition propagation, both requests produce an empty spec for every child. The second request then optimizes exactly the same child contexts as the first one, only for its cost context to be dropped as a duplicate. With parallel plans enabled this repeats a large share of the optimization jobs of every join group. Reject such requests in CEngine::FCheckReqdProps, before any child is optimized, and add CPartitionPropagationSpec::IsEmpty() for the check. Measured on a debug build with star, outer, semi, anti, aggregate, multi-key, union and DPE queries: plans and costs are unchanged, and ORCA planning time with parallel plans drops by 10-19% on the join-heavy queries. Claude-Session: https://claude.ai/code/session_01M1CxTMjApWd2XBG3C4wnyi Claude-Session: https://claude.ai/code/session_01ALBeQzzkBwDJDY1UJ65JrA (cherry picked from commit f4d28f67ae59dc572bb78d46b087b7cbc9bbec65)
A request of a group expression is optimized once per context of its group. Under contexts that differ only in the required distribution, it usually computes the same child requirements and reaches the same child contexts again, after computing every child requirement, looking up the child contexts, waiting for the child jobs and deriving the plan properties of each optimized child. Store the child contexts a request reaches on its group expression, keyed by the request number, the search stage and the parent's required plan properties apart from distribution. A later request with an equal key reuses them if it would reach the same contexts: - Every child requirement except distribution only depends on the matching part of the parent's requirements, which the key makes equal, and on the plans of the children optimized before, which are the best plans of the same contexts. Only Ped() sees the whole parent requirement, so the required distribution of each child is computed again and compared. The plan properties of earlier children are derived again for this, as DerivePrevChildProps() does: some PdsRequired() implementations change the derived distribution of the first child in place, and the properties cached on a cost context are shared. - A child context must have a plan, must not be the context itself, and must be the one a lookup of its requirements finds. A group can hold several contexts with equal requirements when a requirement changes after its context was inserted. The cost bound checks made after each child are repeated with the reused contexts. CTE sequences and multi-stage search are left out. The developer GUC optimizer_enable_child_opt_memo (default on) turns the memo off. optimizer_verify_child_opt_memo optimizes the children anyway and logs where the result differs from the reused contexts. optimizer_print_optimization_stats prints the memo counters. In 13 join and aggregation queries the plans are identical with and without the memo, and 11914 of 16127 lookups reuse child contexts with parallel plans (5020 of 7870 serial). Planning time drops by 12.6% geomean (15.4% in total) with parallel plans and by 8.3% (9.4%) serial. Verify mode finds no difference there or in 25 related regression tests, whose results do not change apart from run-to-run noise. Claude-Session: https://claude.ai/code/session_01M1CxTMjApWd2XBG3C4wnyi Claude-Session: https://claude.ai/code/session_01ALBeQzzkBwDJDY1UJ65JrA (cherry picked from commit 9bb35ccdd3c1e13809776bbe76189c1bc34175c4)
Since 37224a3 CXformUtils::PdrgpcrIndexKeys() has been a thin wrapper around PdrgpcrIndexColumns(), which appends the index's INCLUDE columns after its key columns. CPredicateUtils::ExtractIndexPredicates() therefore treated a predicate on an INCLUDE-only column as indexable. Ordinary comparisons were still rejected later by CMDIndexGPDB::IsCompatible(), but the shortcuts for boolean column references (col -> col = true, NOT col -> col = false) and for IS [NOT] NULL bypass that check and went straight into the index condition list. The DXL-to-plan translator then maps the column through GetKeyPos(), which returns ulong_max for a non-key column, so the Var's varattno wrapped to 0 and execution failed with ERROR: bogus index qualification (nodeIndexscan.c:1250) or, for IS [NOT] NULL, ERROR: btree index keys must be ordered by attribute (nbtutils.c:777) Restore the key-only semantics: reintroduce the EIndexCols selector (EicKey / EicKeyAndIncluded) on PdrgpcrIndexColumns()/PcrsIndexColumns() and make PdrgpcrIndexKeys()/PcrsIndexKeys() request key columns only. The cost model keeps asking for key + INCLUDE columns so that ComputeUnusedIndexWeight() costing is unchanged. Index-only-scan coverage checks are unaffected: they already use PcrsIndexReturnableColumns(). Add regression coverage to gp_covering_index. Fixes apache#1948
memoize_optimizer.out expects an Index Scan on expr_key_idx_x_t for the expr_key join, but ORCA chooses an Index Only Scan whenever the preceding VACUUM managed to mark expr_key all-visible. Whether it can depends on the snapshots held by tests running concurrently in the same parallel group, so the test intermittently fails with - -> Index Scan using expr_key_idx_x_t on expr_key t2 ... + -> Index Only Scan using expr_key_idx_x_t on expr_key t2 ... + Heap Fetches: N The scan type is irrelevant to what the test checks (memoize cache hits and misses on an expression key), so disable optimizer_enable_indexonlyscan around that query. The GUC only affects ORCA; the planner output merely gains the echoed SET/RESET lines.
PexprBitmapSelectBestIndex() breaks ties between equally selective bitmap index candidates by the number of index columns, as a proxy for how big the index is on disk. It got that number from PdrgpcrIndexKeys(), which the previous commit made key-only, so an index with INCLUDE columns now looked exactly as narrow as one without. Two indexes on the same key then tie, and the first one in pg_index order wins - which can be the much wider one: CREATE INDEX wide ON t(a) INCLUDE (payload); -- 12 MB CREATE INDEX narrow ON t(a); -- 720 kB SELECT * FROM t WHERE a IN (1, 100, 1000) OR b = 17; -> Bitmap Index Scan on wide -- was: narrow INCLUDE columns are stored in every index tuple, so the width comparison must count them; only index *conditions* have to stay key-only. Ask for EicKeyAndIncluded there. Key columns still come first in the array, so the adjacent first-index-key check is unaffected. Also fix a typo in the PcrsIndexReturnableColumns comment. Reported-by: Alena0704 <apache#1957>
EXPLAIN (ANALYZE, BUFFERS) did not include buffer usage collected by query executors in the instrumentation deposited on the coordinator. Send per-node buffer usage to the coordinator and accumulate block counters across all workers within each slice. Node statistics are inclusive, so a parent also contains buffer usage accumulated by its descendants within the same slice.
…uted xid SetupUDPIFCInterconnect_Internal() records one CursorICHistoryEntry per interconnect instance on the QD and prunes old entries only when the statement runs in a different transaction than the previous one, using the distributed xid as the transaction identity. Distributed xids are assigned lazily (only for transactions that write or are dispatched two-phase), so autocommit read-only statements always see InvalidDistributedTransactionId on both sides of the comparison. The table is therefore never pruned on read-only sessions and grows by one 48-byte entry per Motion-containing statement for the life of the backend (2.8 MB after 60000 statements, plus the heap fragmentation the steady stream of small long-lived allocations induces). Use MyProc->lxid instead: it is assigned to every top-level transaction, including read-only ones, and stays constant within a transaction block, so entries belonging to cursors that are still open in the current transaction remain protected exactly as before. Verified on a single long-lived session running the queries from apache#1947: UdpInterconnectMemContext grew 0 -> 2814 KB over 60000 statements before the change and stays at 12 KB after it; RssAnon went 12.7 -> 24.6 MB before and is flat at 14.4 MB after. Pruning still does not happen inside an explicit transaction block or while a cursor is open, and FETCH from an open cursor after 300 intervening statements works. Related: apache#1947
paramValues is declared and never used. The GNUmakefile builds extended_protocol_resqueue without the usual warning flags, so nothing has reported it; with -Wall the compiler warns about it. Assisted-by: Claude Code
MERGE into a DISTRIBUTED REPLICATED table did not broadcast the source relation, so each segment merged only the source rows stored locally. The replicas diverged and the reported row count was wrong. Treat MERGE like UPDATE/DELETE when flagging a replicated result relation (upd_del_replicated_table), so the join broadcasts the source to all segments. Also reject volatile functions in MERGE actions on replicated tables. The existing check only looked at the subplan targetlist, but MERGE action expressions are kept in mergeActionLists, so e.g. "UPDATE SET v = random()" was accepted and the replicas diverged.
src/common is compiled twice, for the frontend programs and for the backend, but two of its files include backend headers in both: - percentrepl.c includes cdb/cdbvars.h, postmaster/postmaster.h and utils/builtins.h for GpIdentity, terminal_fd and pg_ltoa(), which it uses only under #ifndef FRONTEND. - kmgr_utils.c includes postgres.h, which no frontend file may include, and utils/builtins.h after choosing between postgres.h and postgres_fe.h, and uses neither. Through them, the frontend copies of both files depended on utils/fmgrprotos.h, which the backend build generates from its function catalog, and on postgres.h itself. A frontend compile has no business with either, and it could not run until the backend's headers had been generated; a top-level make generates every header before it builds src/common, which is why this went unnoticed. After this change neither file's frontend compile includes a generated header at all. Move percentrepl.c's three includes under #ifndef FRONTEND, and drop kmgr_utils.c's two. Assisted-by: Claude Code
Anser needs to move a small piece of information -- today a bloom filter over a join key -- from the segments to the coordinator and back, over the dispatch connection that is already open. Four things in the backend stand in the way, and none of them mentions Anser. A QE can already send a NOTIFY to the coordinator, but the dispatcher dropped any it did not recognise. Add cdbdisp_notify_hook so an extension can claim one, and keep the ones that arrive in the same read as a command completion instead of losing them. The other direction had no message type at all. Add GP_SIDEBAND_MESSAGE, which the QE command loop accepts and discards when nobody is waiting -- a waiter can be cancelled after the coordinator has written, and an unknown type would otherwise be fatal. A CustomScan travelled through the binary plan writer as the text " :methods " plus a token, and the reader took the first four bytes of that as a length and asked for 1.7 GB. Any plan with a CustomScan failed to deserialize on the segments; write the name as a counted string instead. catversion is bumped accordingly. Finally, a bloom filter cannot be shipped without reaching its bitset, so expose it along with the sizing its holder must agree on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anser is a publish/subscribe facility for MPP execution. Producers on the segments publish something about a running query, the coordinator combines the per-segment parts into one, and consumers on the segments receive it and prune work with it. Today that is a bloom filter over a hash join's build key, used to skip probe rows that cannot join. A channel is the rendezvous for one such piece of information in one query, keyed by session, command, and the condition it describes. It lives only in the coordinator backend running the query, for exactly as long as that query runs: no shared memory, no background worker, no second connection to authenticate. Parts travel up as a NOTIFY and the merged payload comes back as a sideband message. Both nodes wrap a base scan rather than an intermediate node, which makes the filter a superset of the keys the joins below would have left -- less selective, never wrong. The direction is fixed to build -> probe, because a consumer on the inner side would wait on a producer the join is not yet reading, which is a deadlock and not a slow path. Every failure ends in unfiltered execution: a filter too small to reject anything, a squelched producer, a malformed part, a missed deadline. The planner gate is the one that costs nothing, and the later ones exist because a row estimate can be wrong. Built only with --with-anser and off by default; the subsystem creates no catalog objects and is configured entirely by GUCs. Includes the regression suites and the CI job that runs them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anser builds as a MODULE_big with no SHLIB_EXPORTS, so the module is compiled -fvisibility=hidden and every extern in its headers lands in .symtab but not .dynsym. A second extension cannot reach any of it: RTLD_GLOBAL does not help, because a hidden symbol never reaches the dynamic symbol table, and load_external_function() fails for the same reason. Mark the public declarations PGDLLEXPORT, which is the convention the tree already uses for a cross-module C API -- see contrib/hstore and src/pl/plpython. Two plan-resolution helpers are promoted into that API rather than left for a caller to reimplement. Both encode which plan shapes are safe to inject into, so a second copy would be a second place to get that wrong. The pushdown itself is split so the decision can be tested on its own. A scan key is numbered by the table's attributes while the consumer is numbered by the scan's output targetlist, and the two coincide only when the scan emits every column in order -- so the filter was handed a position where an attribute number was wanted, and a projecting scan tested the wrong column and discarded every row. The decision now maps one to the other, and refuses the shapes that have no mapping: a computed column, a whole-row or system Var, a Var of another relation. A second function walks from the consumer to the deepest node that accepts the key, so stacked consumers over one relation all push into the same scan instead of probing tuple by tuple. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A filter that is a subset of the build side's keys rejects rows that join, so every one of these is a wrong answer rather than a slow one. A build side under a nested loop yields different keys per outer tuple, but neither node rebuilds on rescan: the producer stays published and the consumer keeps the filter it has, so the second iteration is probed against the first iteration's keys. Refuse such a join at injection time, by intersecting the loop's nestParams with the build subtree's extParam -- the same test UpdateChangedParamSet makes at runtime. Cleanup was scoped by counting ExecutorEnd, which only ever measures recursion into ExecutorEnd itself. A statement run through SPI from a function body goes through its whole Start/Run/End inside the outer query's Run, so its End looked outermost and took the running query's channels with it. Count over execution instead, as auto_explain does. Two readers share the QD->QE connection, and the nextval one died on a sideband message: it took the type byte, then errored without the body, leaving the stream desynchronised and the session unusable. It now skips one whole and waits on, and Anser no longer subscribes when it has no deadline to wait out. The coordinator could also leave a QE's notification unread for the whole lifetime of the slice that sent it, because PQisBusy() reports the command rather than the queued message, and the bytes may already have been absorbed by a flush. Drain before deciding to wait. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A blocking push stranded the dispatcher uncancellably when a consumer had timed out: deliver without blocking, and let a consumer withdraw. AnserSliceProducers counted a singleton reader and direct dispatch as the whole cluster, so the channel never completed. A producer whose slice ran but whose node never did stayed silent, leaving consumers to wait out the deadline. A consumer ignored QueryFinishPending, so a query the coordinator had already finished -- a LIMIT satisfied early -- still sat out the full deadline. CHECK_FOR_INTERRUPTS does not cover that flag. Injection is now refused when the build and probe sides share a slice. Slices are divided by Motions only, so a co-located join offers no synchronisation point to hang the exchange on and no network cost to save against the filter's own. probe_slice already carried the answer but was never read, and was left uninitialized on one path. Channel cleanup is wholesale, and inside SPI the executor nesting level never returns to zero, so a PL/pgSQL body held one merged filter per statement until the outermost one ended -- about 1 MB a query, and up to the full anser.max_info_size. A query now takes its own channels with it, keyed by the command count the channel already carries. Only finished ones: gp_command_count names the newest query descriptor rather than the one that is ending, and the two differ whenever a portal has outlived the statements that ran after it. Planning joins that nesting level too. Constant folding can run a query through SPI, and that query reached ExecutorEnd believing it was the outermost one -- the split counter PostgreSQL merged in 76db9cb. The anser.timeout_ms default drops from 100 s to 10 s. Its rationale had outlived itself: delivery no longer blocks, so a wide slice no longer needs the headroom it was sized for. The help text said otherwise, in four places. Hash keys are now checked on the base type and against the type's own equality operator. A domain inherits typlen and typbyval, so a domain over float8 passed both the by-value test and a float exclusion that names only the built-in OIDs -- and -0.0 would have stopped matching 0.0, dropping rows that join. The anser_test harness moves to a library of its own. It was linked into anser.so, which every user preloads, and its extension shipped with the subsystem; it now builds only for installcheck. It resolves the C API against the preloaded library at load time, which is what the PGDLLEXPORT markers on the public headers were for. Tests move to redistributed joins to match the Motion rule. The parallel section had also been running on the 1 ms deadline left over from the preceding block, which let it pass without a filter ever arriving; that block has since moved out of the file altogether. It keeps its coverage under GPORCA only, since the Postgres planner marks Motion paths parallel-unsafe. The catversion bump is withdrawn. The CustomScan serialization fix reaches only outfast/readfast, the binary format the dispatcher uses; the text branch that nodeToString writes into catalogs is untouched, and a CustomScan is a Plan node, which catalogs never store anyway. configure-cloudberry.sh no longer passes --with-anser unconditionally, which had put the extension into every CI and release build despite the configure default of no. The ic-anser job does not need it: the gpcontrib/anser target builds, installs and preloads the extension itself, so the matrix entry no longer arms the cluster either -- and the postgres_conf_addons hook added for that is gone with it. anser_test carries its own PG_MODULE_MAGIC. It had been relying on the one in anserinit.c, which stopped covering it when it became a library of its own. anser.max_info_size is synced to the segments. Both ends read it, and a segment hands it to pq_getmessage() as the cap on the message it is about to read, so a disagreement does not merely size things differently -- it frames the wire differently. Without the flag a gang created after the SET fell back to the compiled-in default. Syncing narrows that window rather than closing it, since a gang busy with an open cursor is never sent the SET, so a read that overruns the cap now terminates the connection instead of going on: pq_getmessage takes the length word and leaves the body behind, and a message boundary cannot be recovered from there. The README said POSTMASTER where the code said USERSET, and now says what the trade-off is. SHLIB_PREREQS is dropped. Anser links nothing and only reads libpq's headers, and under PGXS the prerequisite expands to "make -C $(libdir) all", for which there is no such target. preload-anser no longer mistakes tr's exit status for psql's: a psql that failed left the list empty, and the recipe then wrote shared_preload_libraries back as just anser, dropping every other library. It also leaves the cluster alone when it is already armed instead of restarting on every installcheck. The cases that need gp_inject_fault move to anser_faults. The regression driver already excludes a file that calls one when the debug extensions are off -- faultinjector_prep in src/Makefile.global.in -- so sharing a file with them took the rest of anser_runtime_filter down as well. The TAP helper uses pg_hton32 rather than htonl and so no longer needs <arpa/inet.h>, which MSVC does not have. The fold no longer touches the part header. It incremented total_parts once per fold, but a producer already writes the channel's producer count there, so a merged header carried 2N-1 -- which is N only when N is 1, and that is the shape the one test covering it pinned. The number is what a consumer reports and what the log prints when a filter goes missing, so it was wrong exactly where someone would be reading it. A unit test now folds three parts, where N and 2N-1 differ; it needs no cluster and runs from installcheck. anser.max_info_size stops at what the protocol can carry. The bound was INT_MAX, so a large setting overflowed the pq_getmessage() cap computed from it into a negative int and every delivery was then refused -- and anything past PQ_LARGE_MESSAGE_LIMIT could never have been read in the first place. The frame around the payload has a name now rather than being spelled out at the single place that needed it. The outer-join case in anser_multijoin produces NULL keys at last. Its right side held every key the left could offer, and the join above it is strict in that key, so reduce_outer_joins() made the LEFT JOIN an inner one before any path was considered. It now joins a table with gaps, through a subquery that keeps the reduction from applying. The tables the chains rest on are left alone: thinning one of those would have cost eighteen assertions to fix one. No filter reaches that join, and none can. Keeping the outer join costs a SubqueryScan, which the key walk does not descend through, and there is no way around it: a NULL-keyed row survives only under an outer join, an outer join survives only behind a subquery boundary. The block says so now, and checks what it can -- that such a plan returns the same rows with the filter on and off. The pushdown cases say whether anything was injected. They compared a filtered count against an unfiltered one, which also agree when no filter was ever built -- and the computed-key case turns out to be one of those: its subquery is pulled up, the hash clause becomes an expression, and anser_hashjoin_keys takes plain Vars only. The comment claiming otherwise is corrected rather than the behaviour. Every GUC a test pins is reset at the end of it. Three files out of four left one behind, and anser_faults all five. Smaller things: send_fake_sideband_message() checks what pqPutnchar() returns; the nextval reply no longer serializes uninitialized locals on its error path, where a QE reads overflow before error and would report reaching a sequence's maximum rather than the real failure; the condition key's zero-padding is written down beside the struct that requires it; and the comments naming AnserBloomShapeFor, AnserChannelLock and anser_rf_size now name things that exist. configure is regenerated for --with-anser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A QE connection whose output buffer still holds bytes at cleanup time carries a partially delivered asynchronous payload (an Anser sideband message; normal command traffic is fully flushed before cleanup). The peer may already have read a prefix of it, and nobody flushes the rest once the command is over -- checkDispatchResult only flushes connections whose command is still running. Reusing such a connection makes the next dispatch overwrite the remainder of the buffer (PQsendGpQuery_shared), and the peer then misreads its stream. Have cleanupQE decline to recycle any connection with pending output, so the next dispatch builds a fresh one.
Squashes the review-round commits: the delivery-protocol rework, the debug tracing used to diagnose it, and the single-slot QE assembly. A whole-filter push left tens of MB queued in the QD's output buffer, and on the UDP interconnect it was flushed only while a dispatch socket was readable. Once parts and subscribes were consumed, inbound traffic stopped, flushing stopped with it, and the consumer sat in pq_getmessage() with no deadline of its own -- an uncancellable deadlock, seen as a cursor's FETCH 1 never returning. Deliver the merged payload in chunks instead, one GP_SIDEBAND_MESSAGE each, sending the next only when the consumer asks with a NEXT (kind 'N', its part field carrying the offset wanted). At most one chunk -- sized to fit the connection's send buffer -- is ever in flight, so a consumer walking away leaves nothing torn behind, and every NEXT keeps waking the coordinator: the delivery drives its own servicing. Chunk coordinates (total_len, offset) join the CRC, the QE assembles into one slot and drops chunks for keys it has unsubscribed from, and the wait deadline runs from the last progress, with a hard cap from the subscribe. All waits use the monotonic clock. Wire tag is anser4. Also zero-initialises the dispatcher's buffered[] array (defensive). Track delivery connections with libpq connection-destroy/reset events so InitPlan and error teardown cannot leave dangling PGconn pointers. Register through the existing internal libpq ABI; no new core hook or exported symbol is required. Refuse subscriptions if registration cannot allocate. Make torn-delivery cleanup allocation-free and nonblocking, poisoning connections instead of waiting during error handling. Add a standalone connection-event test covering destruction, reset, registration failure, other event users, and preservation of merged payloads. Validation: connection-event tests pass with -O3 -DNDEBUG and ASan/UBSan; changed C sources pass syntax checks. Cluster regressions remain pending.
Add gpcontrib/robust_predicate_transfer, Phase I of robust predicate transfer over Anser: a pure analysis pass over a finished PlannedStmt that discovers transfer regions (connected components of the join graph over base scans), builds equivalence classes of join keys by union-find, and computes the asymmetric transfer plan (chaining forward stage, leader-broadcast backward stage) ported from the RPT+ artifact (MIT, Stichting DuckDB Foundation; see licenses/LICENSE-mit-duckdb-rpt.txt). Adds a plan-order consumer-eligibility check for the MPP deadlock rule. SQL-visible via rpt_transfer_plan(text); planner hook logs the dump at DEBUG1 under rpt.debug. Nothing is injected or executed. Tests are EXPLAIN-free and property-based where estimates or tie-breaks could vary.
Preserve protected join directions when bypassing unfiltered tables, terminate rewiring on actual changes, and choose direction-aware parents with separate forward/backward depth tie-breaks. Enforce the chain cap and identify reducer scans and bridge tables. Classify both stages from distribution policies, including implied edges and replicated sources. Keep local backward filters non-blocking and require cross-slice safety for global consumers. Document producer placement and parallel-worker completeness requirements. Unwind STL owners before reporting PostgreSQL errors, fail open in the diagnostic planner hook, and build dumps before one PostgreSQL append. Wire --with-rpt into normal builds, require Anser, restrict diagnostic access, and resolve IndexOnlyScan keys through the index target list. Add synthetic tests with checks active under NDEBUG and strengthen SQL assertions for both optimizers using relation names where needed. Validation: C++ compilation, unitcheck with -O3 -DNDEBUG, an intentional CHECK failure, ASan/UBSan, configure checks, and dump comparison passed. SQL installcheck could not start: linking pg_regress requires libpq, which is unavailable in the local build. Assisted-by: ChatGPT
Prepare the Phase II go/no-go survey without injecting executor nodes. Extract backward eligibility into a shared analysis pass and record per-class decisions, refusal reasons, relink parents, tuple-output key positions, slice IDs, and bounded-prefetch requirements. Require a component-root producer, a global transfer, and a hash-build ordering proof. Reject nullable-side crossings, multi-tuple prefetch paths, all SubPlan placements even below Motions, and any non-empty build-side extParam. Apply a per-class consumer cap and report rough NDV benefit estimates, preserving unknown statistics explicitly. Add a planning-only, read-only survey runner comparing RPT eligibility with Anser's injected producer count under both optimizer settings. Count SELECT statements rather than files, split multi-SELECT templates, and require TPC-H Q15's CTE form. Expect 103 TPC-DS statements from 99 templates. Record per-statement results, failures, and raw plan artifacts. Add reserved QD-only backward settings, document diagnostic EXECUTE privileges, and update the runtime design to use one statement without a session cache. Runtime injection remains gated on benchmark results. Add native safety tests and production-walker/resolver SQL regressions, including a correlated SubPlan with an internal Motion. SQL assertions identify tables by name and account for retained optimizer plan shapes. Validation: C++ compilation, native tests with -O3 -DNDEBUG, ASan/UBSan, all 10 Python survey tests, and git diff --check passed. The new SQL regressions and full benchmark survey have not run on a cluster. Assisted-by: ChatGPT
Build a separate forward schedule rooted at the largest relation, including unfiltered fact tables, without changing backward survey leaders or eligibility decisions. Add a finished-plan builder for the distributed reducer InitPlan: append coordinator and non-parallel reader slices, Gather and Sequence nodes, an empty Result with an implicit-AND false qual, and a boolean PARAM_EXEC. Continue node IDs above both existing plans and reducer children, preserving existing InitPlans and slice mappings. Add standalone layout tests for fresh and existing InitPlans, checking slice indices and parents, parameter numbering, node IDs and qual shape. Run these tests through unitcheck and ignore generated test binaries. This is preparatory Phase III work. The builder is not called by the planner hook yet; reducer scans, filter wrappers, cost gating and runtime injection remain unimplemented. SQL execution and cross-dispatch filter delivery have not been validated on a cluster. Validation: native unitcheck and 10 survey tests pass with -O3 -DNDEBUG; InitPlan layout tests pass AddressSanitizer and UndefinedBehaviorSanitizer. Assisted-by: OpenAI Codex
Build the forward reducer on a private copy of the finished statement and attach it as an uncorrelated distributed InitPlan. Apply completed filters to main-plan roots, or also to chain scans with rpt.forward_apply=all. Keep Anser per-join injection exclusive with RPT and fail open on planner construction errors while preserving query cancellation. Copy key-only SeqScans using the statement's existing range table and snapshot. Retain quals only when every operation belongs to an explicit built-in comparison/hash allowlist; otherwise drop the whole qual list. Reflect dropped quals in filter sizing and selectivity estimates. Gate components by root rows, estimated selectivity and payload exchange cost. Hand intermediate co-distributed filters between reducer scans in-process, without coordinator traffic or blocking waits. Use statement-scoped Anser channels for global hops and filters consumed by the main dispatch. Phase II backward runtime injection remains deferred. Add multi-hop, local/global, apply-all, early-termination, prepared and SPI SQL coverage plus forced transport-failure cases. Correct backward survey expectations for simplified outer joins whose root is on the probe side. Provide an empty-InitPlan measurement switch, workload generator, recorded exchange measurements and a staged cluster experiment plan. Validation: native unitcheck and 10 survey tests pass with -O3 -DNDEBUG; graph, InitPlan and Anser connection-event tests pass ASan/UBSan. Changed C/C++ sources pass syntax checks. The new SQL suites and cross-dispatch runtime behavior still require validation on a running cluster. Assisted-by: OpenAI Codex
Restrict raw-Datum Bloom keys to canonical built-in by-value types, so custom equality operators cannot introduce false negatives. Add the modulo-equality reproducer as a regression under both optimizers. Supply a PlannedStmt-backed context when checking existing InitPlans and expression SubPlans. Test this path with the real backend plan walker. Parse C++ locale headers before PostgreSQL gettext macros to avoid clean non-NLS build failures. Rename the reserved ANALYZE helper parameter and correct fault-test expected output. Replan fault queries when toggling RPT and assert that injected faults actually fire. Add step-marked forward diagnostics with an independent watchdog that captures activity before cancellation and leaves server backends intact. The reported forward-test hang remains unlocalized; no hang fix is claimed. Remove implementation-phase references and detailed design documents from version control while retaining local copies. Keep a general README with paper and artifact links, and record artifact attribution in NOTICE and LICENSE alongside the existing MIT license text. Validation: native graph, injection and backend-walker tests pass with -O3 -DNDEBUG; 10 survey and four diagnostic tests pass. Graph/walker tests pass ASan/UBSan. GCC 15 compiles all extension sources without the forced libintl include; local linking stops at the absent postgres executable. Cluster SQL regressions and the original GCC 13 environment remain unrun. Assisted-by: OpenAI Codex
Use the largest-root forest for forward and backward transfers. Build one backward producer per root-led equivalence class above the root's forward consumers, and place member consumers only where the existing eligibility proof permits waiting. Share channel and plan-node IDs and track scan stacks so both passes relink the original parent safely. Add backward sizing, selectivity and exchange-cost gates, including a per-consumer delivery term shared with forward selection. Support backward-only injection and retain fail-open planning and cancellation. No core or Anser changes are required. Make range-table identities explicit throughout RPT and resolve relation aliases on demand. Document the call chain and runtime structures, and extend the survey and diagnostic runner with largest-root decisions. Add native placement and selection coverage plus SQL and transport-fault regressions under both optimizers. Allow 120 seconds for assertion blocks, verify oversized producers do not complete filter deliveries, and ignore native-test dSYM bundles. Validation: native tests with -DNDEBUG and ASan/UBSan; compiler checks; 11 survey tests and 6 diagnostic tests; git diff --check. Cluster regressions and benchmark experiments remain pending on the dev cluster. Assisted-by: ChatGPT
Install anser_test through Anser's install-anser-test target before running RPT regressions. Normal Anser installation omits this extension, but backward fault tests require its delivery counter. The prerequisite also covers installcheck-with-install through its installcheck call. Validated the recursive installation target with make -n and checked whitespace with git diff --check. Cluster regressions were not run. Assisted-by: ChatGPT
Keep the logical join graph before SkipUnfilteredTable rewrites it for forward transfers. Grow one forest over both views and choose each stage's parent from its own edges, so unfiltered roots retain backward consumers without re-enabling prohibited outer-join directions. Use Anser's resolved targetlist helper for local filter stacks and add native coverage for deep stacks, reordered outputs and unfiltered backward edges. Exercise stacked consumers and producer/consumer stacks with EXPLAIN VERBOSE in the SQL regressions. Stop treating InitPlan presence alone as evidence of external producer parameters. Make backward fixtures use incompressible padding and the hash-size heuristic, and verify a cached injected plan before arming the cancellation timeout. The reported ORCA cancellation cause remains unconfirmed pending a new cluster run. Validation: compiler checks; native tests with -DNDEBUG; graph and injection tests with ASan/UBSan; 11 survey and 6 diagnostic tests; SQL/expected consistency and git diff --check. Cluster SQL regressions remain pending. Assisted-by: ChatGPT
Always apply forward filters only to main-plan roots. Remove the forward_apply switch, its extra consumer placement and cost branches, and the corresponding SQL and native test variants. Keep intermediate local transfers inside the reducer and retain the real reducer InitPlan. Remove debug_noop_initplan and its measurement script. Mark forward and backward as session-settable developer controls with GUC_NOT_IN_SAMPLE; rpt.enable remains the user-facing master switch. Refresh the README with the current planner and executor call chains, graph views, structures, injection order and diagnostic workflow. Add a four-table star collector and a step-by-step guide for gathering graph decisions, results, actual text/JSON EXPLAIN ANALYZE plans and rotating baseline/forward/backward/full timing samples under both optimizers. Validation: compiler checks; native tests with -DNDEBUG; 11 survey and 6 diagnostic tests; SQL/expected and README link checks; git diff --check. The collector was statically checked. Cluster regressions and example measurements remain pending on the development cluster. Assisted-by: ChatGPT
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.


It's MVP for robust predicate transfer