Skip to content

Scan statistics and their EXPLAIN lines are written three times, so the invariant keeping them consistent is held by discipline #495

Description

@ChronicallyJD

Every scan node keeps its own copy of the same six-field record and its own copy of the code that prints it. Adding one line to EXPLAIN therefore means three edits, and the invariant that keeps the three consistent is held by discipline rather than by structure.

From an architecture pass over the hot spots; columnar_vector.c is 4,323 lines and took 38 of the last 300 commits.

The duplication, on current main

The record, twice in columnar_vector.c and once as locals in columnar_customscan.c:

columnar_vector.c:556,594-597,604   npreds, haveStats, groupsRead, groupsSkipped, groupsTotal, usablePreds
columnar_vector.c:693-698           npreds, haveStats, groupsRead, groupsSkipped, groupsTotal, usablePreds
columnar_customscan.c               the same quantities, as locals in the EXPLAIN function

The emission, three times:

columnar_customscan.c:1879,1909,1913    Pushed-Down Filters / Usable Skip Predicates / Chunk Groups Total ...
columnar_vector.c:3537,3555,3557        the same three, again
columnar_vector.c:4229,4235,4237        the same three, a third time

It has already cost, twice, and is about to a third time

#484 had to add Columnar Usable Skip Predicates in three places. Its own rationale says why it could not do fewer:

Fixing one would leave a line of plan text meaning two different things depending on which node ran.

That is exactly right, and it is an invariant with nothing enforcing it.

#493 is that invariant already broken on the line that was not touched: Columnar Pushed-Down Filters reports scan keys on the scalar node and vector predicates on the two aggregate nodes. Whatever shape #493 lands as, it is a fourth edit to the same three places.

And the copies have already drifted in a way nobody noticed. In all three nodes Pushed-Down Filters prints outside the "did we actually read anything" guard while Usable Skip Predicates prints inside it. On the scalar node that gap is reachable — measured on merged main:

EXPLAIN (COSTS OFF) SELECT count(*) FROM t WHERE plain > 190000;

   ->  Custom Scan (PgColumnarScan) on t
         Columnar Pushed-Down Filters: 1     <-- present
                                             <-- nothing from the guarded block

A plain EXPLAIN shows the number that hid #477 for a year and omits the one added to correct it. On the aggregate nodes the same gap is currently unreachable, but only because haveStats happens to be true exactly when a filter exists — an accident of scanFold, not a stated rule.

Deepening

One module owns the record and its emission. Each node holds the record, hands it a read state to capture from, and calls it to print. The nodes stop knowing which lines exist or what order they come in.

Then:

Deletion test: removing two of the three copies concentrates the reporting into one place that knows the whole record, rather than moving the problem somewhere else.

Scope, and what this is not

This is only the record and its printing — not what the numbers mean. #493 owns the question of whether Pushed-Down Filters should be one quantity or two, and that decision is independent: it is about which quantity to report, this is about there being three places that report it. They compose, and #493 is easier if this lands first, but neither blocks the other.

I have not built it. Happy to, if you want it; equally happy to leave it as a note if the churn on those files makes now a bad time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions