performance_tests.txt

text/plain

Filename: performance_tests.txt
Type: text/plain
Part: 0
Message: Re: BUG #19483: pg_upgrade fails with orphan records in pg_init_priv catalog table
Performance Results
===================

Test Environment:
- CPU: Apple M2 Pro (12 cores)
- RAM: 32 GB
- OS: macOS 26.5.1 (darwin 25.5.0, arm64)
- Compiler: Apple clang 21.0.0 (clang-2100.1.1.101)
- PostgreSQL: 19beta1 (built from source, -O2, no --enable-cassert)
- Disk: internal SSD (APFS)

Wall Clock Timing: pg_dump --schema-only (7 runs each, milliseconds)
---------------------------------------------------------------------

Database: perf_baseline
  pg_init_privs rows: 249 | user functions/aggs: 0

  Unpatched:  49.69  52.29  46.42  49.73  63.11  51.58  53.50
  Patched v6: 52.49  50.23  50.02  51.87  48.83  65.23  64.58

  Median unpatched: 51.58 ms
  Median patched:   52.49 ms
  Delta: +0.91 ms (+1.8%)

Database: perf_10k_functions
  pg_init_privs rows: 249 | user functions/aggs: 10500

  Unpatched:  1619.04  1674.27  1638.30  1598.82  1597.56  1613.76  1591.44
  Patched v6: 1582.23  1584.01  1657.28  1598.13  1573.98  1576.01  1572.51

  Median unpatched: 1613.76 ms
  Median patched:   1582.23 ms
  Delta: -31.53 ms (-2.0%)  [within noise, patched is NOT slower]

Database: perf_dangling_500
  pg_init_privs rows: 749 (500 dangling) | user functions/aggs: 500

  Unpatched:  107.28  104.59  104.23  102.89  102.20  96.26  102.95
  Patched v6: 103.90  105.64  104.61  105.18  106.71  105.50  107.20

  Median unpatched: 102.95 ms
  Median patched:   105.50 ms
  Delta: +2.55 ms (+2.5%)

Summary Table:

  | Database           | initprivs rows | Median unpatched | Median patched | Delta      |
  |--------------------|----------------|------------------|----------------|------------|
  | perf_baseline      | 249            | 51.58 ms         | 52.49 ms       | +0.9 ms    |
  | perf_10k_functions | 249            | 1613.76 ms       | 1582.23 ms     | -31.5 ms * |
  | perf_dangling_500  | 749            | 102.95 ms        | 105.50 ms      | +2.6 ms    |

  * Negative delta = within measurement noise; the patch does NOT slow
    down large-schema dumps.

EXPLAIN ANALYZE: getAdditionalACLs() Query Isolation
-----------------------------------------------------

Database: perf_baseline (249 rows, no dangling entries)

  Unpatched:
    Seq Scan on pg_init_privs (actual time=0.005..0.014 rows=249 loops=1)
      Buffers: shared hit=3
    Planning Time: 0.198 ms
    Execution Time: 0.043 ms

  Patched v6:
    Seq Scan on pg_init_privs pip (actual time=0.042..1.037 rows=249 loops=1)
      Buffers: shared hit=5
      SubPlan array_1
        -> Function Scan on unnest elt (actual time=0.003..0.003 rows=1.65 loops=249)
             SubPlan exists_1
               -> Function Scan on aclexplode ace (actual time=0.001..0.001 rows=0 loops=411)
                    SubPlan exists_3
                      -> Seq Scan on pg_authid (rows=18 loops=1) [hashed]
                    SubPlan exists_5
                      -> Seq Scan on pg_authid (rows=18 loops=1) [hashed]
    Planning Time: 0.620 ms
    Execution Time: 1.082 ms

  Overhead: +1.04 ms (one-time cost at dump startup)

Database: perf_dangling_500 (749 rows, 500 dangling entries)

  Unpatched:
    Seq Scan on pg_init_privs (actual time=0.005..0.032 rows=749 loops=1)
      Buffers: shared hit=9
    Planning Time: 0.230 ms
    Execution Time: 0.069 ms

  Patched v6:
    Seq Scan on pg_init_privs pip (actual time=0.038..2.174 rows=749 loops=1)
      Buffers: shared hit=96
      SubPlan array_1
        -> Function Scan on unnest elt (actual time=0.002..0.002 rows=0.55 loops=749)
             Rows Removed by Filter: 1
             SubPlan exists_1
               -> Function Scan on aclexplode ace (actual time=0.001..0.001 rows=0.55 loops=911)
                    SubPlan exists_3
                      -> Seq Scan on pg_authid (rows=18 loops=1) [hashed]
                    SubPlan exists_5
                      -> Seq Scan on pg_authid (rows=18 loops=1) [hashed]
    Planning Time: 0.517 ms
    Execution Time: 2.225 ms

  Overhead: +2.16 ms (worst case: 500 dangling entries filtered)

Key observation: pg_authid is scanned ONCE and hashed (loops=1). The
hashed subplan is reused for all 749 rows, making the overhead O(n) in
pg_init_privs rows with a very small constant.

EXPLAIN ANALYZE: getAggregates WHERE Clause (UNCHANGED)
--------------------------------------------------------

Database: perf_10k_functions (500 user aggregates, 10000 user functions)

  Hash Left Join (actual time=1.004..1.113 rows=500 loops=1)
    Hash Cond: (p.oid = pip.objoid)
    Filter: (p.pronamespace <> ... OR p.proacl IS DISTINCT FROM pip.initprivs)
    Rows Removed by Filter: 163
    Buffers: shared hit=348
    -> Seq Scan on pg_proc p (actual time=0.039..1.029 rows=663 loops=1)
         Filter: (prokind = 'a')
         Rows Removed by Filter: 13274
    -> Hash (actual time=0.022..0.022 rows=69 loops=1)
         -> Seq Scan on pg_init_privs pip (actual time=0.010..0.013 rows=69 loops=1)
              Filter: (classoid = 1255 AND objsubid = 0)
              Rows Removed by Filter: 180
  Planning Time: 0.834 ms
  Execution Time: 1.168 ms

  This query is byte-for-byte IDENTICAL in patched and unpatched code.
  The patch does NOT modify any WHERE clause. Timing is identical.


The filtering adds ~1ms (249 rows) to ~2ms (749 rows) to the one-time
getAdditionalACLs() query that runs at pg_dump startup. This is a fixed
cost that does NOT scale with the number of functions, aggregates, or
other objects in the database.

The critical performance test is perf_10k_functions (10,000 functions +
500 aggregates): the patched version shows NO regression. The median is
actually 31ms faster, which is just noise. The per-object queries
(getAggregates, getFuncs) are completely unchanged and produce identical
execution plans.