<div dir="ltr">Hi, I tested postgis-3.7.0beta1 today on PostgreSQL 19 beta 2.<br><br>It appears that selects on geometry_columns has become much slower in my small test database:<br><br>select count(*) from geometry_columns;<br><br>now takes more than two minutes. The SQL from the 3.6.0 version of the view takes a few milliseconds to run.<br><br>I notice that the view is much more complicated in 3.7.0beta1 than what it was in earlier versions,<br>so I guess the change is related to the new PostGIS rather than the new PostgreSQL.<br><br>Query plan for the new version here:<br><a href="https://explain.depesz.com/s/rtSg">https://explain.depesz.com/s/rtSg</a><br><br>Is this a known problem? I had Codex build a test case that seems to reproduce something similar<br>in an empty database, but I'm not sure if it shows exactly the same problem I'm encountering.<br><br>Anyway, this test case is included below in case it is useful.<br><br>On my machine, the testcase shows the 3.6 version taking 2 millis and the 3.7 version 11 seconds.<br><br>/Fredrik Widlert<br><a href="mailto:fredrik.widlert@digpro.se">fredrik.widlert@digpro.se</a><br><br><br><br>\set ON_ERROR_STOP on<br>\timing on<br><br>/*<br> * Reproducer for slow geometry_columns queries with the PostGIS 3.7<br> * definition.<br> *<br> * Run this with psql as a superuser in a new, otherwise empty database.<br> *<br> * The test deliberately creates many CHECK constraints which have nothing to<br> * do with PostGIS.  The 3.7 geometry_columns definition builds constraint_defs<br> * from every row in pg_constraint, calls pg_get_constraintdef() for each row,<br> * and scans the result three times with regular expressions.  Consequently,<br> * unrelated constraints affect the time needed to inspect geometry columns.<br> *<br> * A partitioned table is used to populate pg_constraint without needing<br> * thousands of CREATE TABLE statements: its 100 CHECK constraints are copied<br> * to each of 200 partitions, producing about 20,000 unrelated<br> * pg_constraint rows.<br> *<br> * The spatial tables use constraint-based geometry columns rather than<br> * typmods.  This is important because it exercises the constraint inference<br> * added to geometry_columns in 3.7.  Views over those tables also exercise<br> * _postgis_geometry_columns_view_column_origin().<br> *<br> * On slower machines, lower the loop upper bounds below.  The product of the<br> * CHECK-constraint and partition counts controls most of the catalog load.<br> */<br><br>CREATE EXTENSION postgis;<br><br>DO $roles$<br>BEGIN<br>   IF NOT EXISTS (<br>               SELECT<br>                FROM pg_catalog.pg_roles<br>              WHERE rolname = 'slow_columns_reader'<br> ) THEN<br>                CREATE ROLE slow_columns_reader NOLOGIN;<br>      END IF;<br><br>     IF NOT EXISTS (<br>               SELECT<br>                FROM pg_catalog.pg_roles<br>              WHERE rolname = 'slow_columns_user'<br>   ) THEN<br>                CREATE ROLE slow_columns_user NOLOGIN;<br>        END IF;<br>END<br>$roles$;<br><br>GRANT slow_columns_reader TO slow_columns_user;<br><br>CREATE SCHEMA slow_columns_repro;<br><br>/*<br> * Create the unrelated catalog load first.  Constraints are added to the<br> * parent before its partitions are created so PostgreSQL copies them into<br> * every partition.<br> */<br>CREATE TABLE slow_columns_repro.constraint_noise (<br>        id integer NOT NULL<br>) PARTITION BY RANGE (id);<br><br>DO $noise$<br>BEGIN<br>  FOR i IN 1..100 LOOP<br>          EXECUTE format(<br>                       'ALTER TABLE slow_columns_repro.constraint_noise '<br>                    'ADD CONSTRAINT %I CHECK (id >= %s)',<br>                      'noise_check_' || i,<br>                  -i<br>            );<br>    END LOOP;<br><br>   FOR i IN 0..199 LOOP<br>          EXECUTE format(<br>                       'CREATE TABLE slow_columns_repro.%I '<br>                 'PARTITION OF slow_columns_repro.constraint_noise '<br>                   'FOR VALUES FROM (%s) TO (%s)',<br>                       'constraint_noise_' || i,<br>                     i,<br>                    i + 1<br>         );<br>    END LOOP;<br>END<br>$noise$;<br><br>/*<br> * AddGeometryColumn(..., use_typmod => false) creates legacy CHECK<br> * constraints for dimensionality, SRID, and geometry type.  The example in<br> * slow_columns.sql used typmods and therefore did not exercise this path.<br> */<br>DO $spatial_objects$<br>DECLARE<br>      table_name text;<br>      view_name text;<br>BEGIN<br>        FOR i IN 0..199 LOOP<br>          table_name := 't' || i;<br>               view_name := 'v' || i;<br><br>              EXECUTE format(<br>                       'CREATE TABLE slow_columns_repro.%I (id bigint)',<br>                     table_name<br>            );<br><br>          PERFORM AddGeometryColumn(<br>                    'slow_columns_repro',<br>                 table_name,<br>                   'shape',<br>                      5845,<br>                 'POINT',<br>                      3,<br>                    false<br>         );<br><br>          EXECUTE format(<br>                       'CREATE VIEW slow_columns_repro.%I AS '<br>                       'SELECT id, shape FROM slow_columns_repro.%I',<br>                        view_name,<br>                    table_name<br>            );<br>    END LOOP;<br>END<br>$spatial_objects$;<br><br>GRANT USAGE ON SCHEMA slow_columns_repro TO slow_columns_reader;<br>GRANT SELECT ON ALL TABLES IN SCHEMA slow_columns_repro<br>       TO slow_columns_reader;<br><br>/*<br> * Preserve the 3.6 definition under another name so both versions see the<br> * exact same catalog and privileges.<br> */<br>CREATE VIEW slow_columns_repro.geometry_columns_36 AS<br>SELECT<br>       current_database()::varchar(256) AS f_table_catalog,<br>  n.nspname AS f_table_schema,<br>  c.relname AS f_table_name,<br>    a.attname AS f_geometry_column,<br>       COALESCE(postgis_typmod_dims(a.atttypmod), 2) AS coord_dimension,<br>     COALESCE(NULLIF(postgis_typmod_srid(a.atttypmod), 0), 0) AS srid,<br>     replace(<br>              replace(<br>                      COALESCE(<br>                             NULLIF(upper(postgis_typmod_type(a.atttypmod)), 'GEOMETRY'),<br>                          'GEOMETRY'<br>                    ),<br>                    'ZM',<br>                 ''<br>            ),<br>            'Z',<br>          ''<br>    )::varchar(30) AS type<br>FROM pg_catalog.pg_class AS c<br>JOIN pg_catalog.pg_attribute AS a<br>      ON a.attrelid = c.oid<br> AND NOT a.attisdropped<br>JOIN pg_catalog.pg_namespace AS n<br>     ON c.relnamespace = n.oid<br>JOIN pg_catalog.pg_type AS t<br>       ON a.atttypid = t.oid<br>WHERE c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'm'::"char",<br>      'f'::"char", 'p'::"char"])<br>        AND c.relname <> 'raster_columns'<br>       AND t.typname = 'geometry'<br>    AND NOT pg_catalog.pg_is_other_temp_schema(c.relnamespace)<br>    AND pg_catalog.has_table_privilege(c.oid, 'SELECT');<br><br>GRANT SELECT ON slow_columns_repro.geometry_columns_36<br>        TO slow_columns_reader;<br><br>/*<br> * Give the planner current catalog statistics.  Without this, results can<br> * depend on whether autovacuum happened to run during fixture creation.<br> */<br>ANALYZE pg_catalog.pg_class;<br>ANALYZE pg_catalog.pg_attribute;<br>ANALYZE pg_catalog.pg_constraint;<br>ANALYZE pg_catalog.pg_namespace;<br>ANALYZE pg_catalog.pg_rewrite;<br>ANALYZE pg_catalog.pg_type;<br><br>SET ROLE slow_columns_user;<br>SET jit = off;<br><br>\echo<br>\echo 'Catalog size used by the reproducer:'<br>SELECT count(*) AS constraint_count<br>FROM pg_catalog.pg_constraint;<br><br>\echo<br>\echo 'PostGIS 3.6 geometry_columns definition (baseline):'<br>EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY ON)<br>SELECT count(*)<br>FROM slow_columns_repro.geometry_columns_36<br>WHERE f_table_schema = 'slow_columns_repro';<br><br>\echo<br>\echo 'Installed PostGIS geometry_columns definition (3.7 regression):'<br>EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY ON)<br>SELECT count(*)<br>FROM public.geometry_columns<br>WHERE f_table_schema = 'slow_columns_repro';<br><br>RESET ROLE;<br><br><br><br><br><br><br></div>