Thread

  1. Re: [PATCH] Optionally record Plan IDs to track plan changes for a query

    Андрей Казачков <andrey.kazachkov@tantorlabs.ru> — 2025-12-25T14:33:11Z

      
        Dear PostgreSQL Hackers,
      
      
         
      
      
        I’ve been testing the proposed v5 plan id work and found out instability of
      
      
        computing the plan identifier after feeding different query texts that
      
      
        produces the same physical plan trees but with different plan ids. The main
      
      
        pattern regards with fields of Plan node structures that depend on positions of
      
      
        RTEs in a RTE list.
      
      
         
      
      
        For your convenience, I’ve attached minimal reproducible examples that
      
      
        demonstrate this pattern. Both are based on the v6-0001 patch, which
      
      
        incorporates v5-0001..v5-0003 changes and fixes jumbling empty arrays.
      
      
         
      
      
        ```
      
      
        create table foo1 (num int);
      
      
        create table foo2 (num int);
      
      
         
      
      
        insert into foo1 (num)
      
      
        select *
      
      
        from generate_series(1, 1000);
      
      
         
      
      
        insert into foo2 (num)
      
      
        select *
      
      
        from generate_series(1, 10);
      
      
         
      
      
        analyze foo1;
      
      
        analyze foo2;
      
      
         
      
      
        set compute_plan_id to true;
      
      
        ```
      
      
         
      
      
        The first example changes join order between two tables:
      
      
        ```
      
      
        explain (costs off, verbose)
      
      
        select 1 from foo1 join foo2 on foo1.num = foo2.num;
      
      
         
      
      
                    QUERY PLAN
      
      
        -------------------------------------
      
      
         Hash Join
      
      
           Output: 1
      
      
           Hash Cond: (foo1.num = foo2.num)
      
      
           ->  Seq Scan on public.foo1
      
      
                 Output: foo1.num
      
      
           ->  Hash
      
      
                 Output: foo2.num
      
      
                 ->  Seq Scan on public.foo2
      
      
                       Output: foo2.num
      
      
         Plan Identifier: 538643160186222168
      
      
         
      
      
        explain (costs off, verbose)
      
      
        select 1 from foo2 join foo1 on foo1.num = foo2.num;
      
      
         
      
      
                     QUERY PLAN
      
      
        --------------------------------------
      
      
         Hash Join
      
      
           Output: 1
      
      
           Hash Cond: (foo1.num = foo2.num)
      
      
           ->  Seq Scan on public.foo1
      
      
                 Output: foo1.num
      
      
           ->  Hash
      
      
                 Output: foo2.num
      
      
                 ->  Seq Scan on public.foo2
      
      
                       Output: foo2.num
      
      
         Plan Identifier: -953143034841089498
      
      
        ```
      
      
        Here the reordering of relations in the JOIN operator changes their order in
      
      
        the RTE list, which in turn changes RTE position-sensitive fields like `varno`
      
      
        in Vars, and those differences leak into jumbling.
      
      
         
      
      
        The second example assumes transformation of some table expressions to a
      
      
        new form like CTE-inlining or completely its eliminating. Let's see example:
      
      
        ```
      
      
        explain (costs off, verbose)
      
      
        WITH foo_cte as (
      
      
          SELECT num FROM foo1)
      
      
        select * from foo_cte;
      
      
         
      
      
                      QUERY PLAN
      
      
        --------------------------------------
      
      
         Seq Scan on public.foo1
      
      
           Output: foo1.num
      
      
         Plan Identifier: 3494394630757173099
      
      
         
      
      
        explain (costs off, verbose)
      
      
        select * from foo1;
      
      
         
      
      
                      QUERY PLAN
      
      
        --------------------------------------
      
      
         Seq Scan on public.foo1
      
      
           Output: num
      
      
         Plan Identifier: 8116143677260771228
      
      
        ```
      
      
        In the example with a CTE, RTE list contains the outdated subquery item and the
      
      
        relation one, whereas without a CTE we have just the relation item. Although
      
      
        the CTE is inlined, its RTE is not removed from the rtable; as a result,
      
      
        position-sensitive fields (such as `varno` for Vars) differ from the no-CTE
      
      
        case.
      
      
         
      
      
        A possible way to address it during jumbling process:
      
      
        1. Remove/skip unused RTE list elements.
      
      
        2. Sort active RTE list elements by some stable criteria.
      
      
        3. Adjust fields referring to active RTEs.
      
      
         
      
      
        But the main challenge is identifying all “position-sensitive” fields across
      
      
        node types efficiently and maintainably. I’d happy to see your feedback on
      
      
        this issue.
      
      
         
      
      
        Additionally, I noticed that the `location` field is being jumbled for several
      
      
        structures (for example, `Var`). Since it’s only a token location, I believe we
      
      
        should not include it in the final plan id value.
      
      
         
      
      
        --
      
      
        Sincerely,
      
      
        Andrey Kazachkov