On Wed, Jul 29, 2026 at 3:21 PM Richard Guo <[email protected]> wrote:
> 4. The commit message claims that "it doesn't seem to result in any
> significant planning-time penalty". I'm somewhat skeptical of that.
I did some testing of planning time. Independent removals are fine,
even faster than before: 64 removable joins that each join directly to
t0 plan in 0.57 ms here vs 1.11 ms on master. They all go in the
first pass, and one extra pass over a single-rel query is cheaper than
the per-removal cleanup the old code did.
Chains of dependent joins are not fine though:
select t0.id from t0
left join t1 on t0.nxt = t1.id
left join t2 on t1.nxt = t2.id
...
left join t64 on t63.nxt = t64.id;
Only t64 can go in the first pass; t63 still looks needed because
attr_needed for t63.nxt includes t64's join, and we only find out
otherwise after the restart. So each link costs a full pass, and this
plans in 20.9 ms vs 1.98 ms on master. The gap widens quickly with
the chain length, and views built on views give the same shape.
I think we can fix this without giving up the restart design. After a
scan has removed something, recompute attr_needed and ph_needed into
temp arrays from the surviving sources (targetlist, the quals in each
rel's baserestrictinfo/joininfo, no-const ECs, PHV expressions,
lateral refs), treating anything whose relids overlap the removed set
as dead, and scan again until nothing more goes. That's basically
rebuild_*_attr_needed() again, but read-only, and it can only
overestimate what's needed, so a mistake just postpones a removal to
the next restart, and the real derived data is never touched.
Thoughts?
- Richard