You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since 6.0 added SQL-editor autocomplete, every load of a page with &sql= runs a
catalog query on PostgreSQL — even when the query box is empty. On a production
catalog this costs several seconds of database CPU per page load. Adminer 5 never
ran it.
The base allFields() joins INFORMATION_SCHEMA.COLUMNS + TABLE_CONSTRAINTS + KEY_COLUMN_USAGE. Only COLUMNS carries the schema
predicate; the two join partners have none of their own.
Why it is expensive on PostgreSQL
EXPLAIN (ANALYZE, BUFFERS) on a schema containing 2 tables:
Seq Scan on pg_constraint c_1 (rows=1312) <- reads every constraint in the database
Rows Removed by Join Filter: 1310 <- then discards 1310 of them
Join Filter: (pg_has_role(r.relowner,'USAGE')
OR has_column_privilege(r.oid, a_1.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'))
Two compounding effects:
The join partners are read catalog-wide and filtered afterwards.
information_schema views call privilege functions per row.
So the cost scales with the number of roles/grants and total constraints in the
database — not with the schema being browsed. Browsing a 2-table schema costs
the same as a 1200-table one.
Measured on a 2-table target schema throughout:
catalog state
query time
1.2k tables / 14k columns
20 ms
+ 300 roles, 370k grants
227 ms (11x)
On a production database this reaches ~8 s per page load. Because the driver is
grants and constraint count, it affects every environment regardless of size.
Steps to reproduce
docker network create adm-repro
docker run -d --name pg --network adm-repro \
-e POSTGRES_PASSWORD=p -e POSTGRES_USER=u -e POSTGRES_DB=d \
postgres:16-alpine -c log_statement=all
docker exec pg psql -U u -d d -c "CREATE SCHEMA app;CREATE TABLE app.widgets(id serial primary key, name text);DO \$\$ DECLARE i int; BEGIN FOR i IN 1..1200 LOOP EXECUTE format('CREATE TABLE app.t%s (id int PRIMARY KEY, c1 text)', i); END LOOP; FOR i IN 1..300 LOOP EXECUTE format('CREATE ROLE r%s', i); EXECUTE format('GRANT SELECT ON ALL TABLES IN SCHEMA app TO r%s', i); END LOOP;END \$\$;"
docker run -d --name adm --network adm-repro -p 8080:8080 adminer:6.0.1
Log in to d as u, open the SQL command page and leave the query box empty.
Then:
docker logs pg 2>&1| grep -c KEY_COLUMN_USAGE
The query is issued on every load. Browsing tables (no &sql=) never issues it.
Suggested fixes
Any one of these would resolve it:
Add the schema predicate to the joined views as well, so TABLE_CONSTRAINTS / KEY_COLUMN_USAGE are not read catalog-wide.
Implement allFields() against pg_catalog for the pgsql driver, avoiding the information_schema privilege checks entirely.
Populate autocomplete lazily (on first keystroke) rather than on page render —
the columns are not needed to display the page.
Provide a supported way to disable autocomplete for the built-in drivers. Driver::jushAutocomplete() is a static call on the concrete driver class, so
there is currently no plugin hook to override it without editing the compiled
release. (Related but distinct: Autocomplete: allow to disable #1142, which concerns client-side lag on large
queries.)
Add the schema predicate to the joined views as well, so TABLE_CONSTRAINTS / KEY_COLUMN_USAGE are not read catalog-wide.
Implement allFields() against pg_catalog for the pgsql driver, avoiding the information_schema privilege checks entirely.
Populate autocomplete lazily (on first keystroke) rather than on page render —
the columns are not needed to display the page.
Provide a supported way to disable autocomplete for the built-in drivers. Driver::jushAutocomplete() is a static call on the concrete driver class, so
there is currently no plugin hook to override it without editing the compiled
release. (Related but distinct: Autocomplete: allow to disable #1142, which concerns client-side lag on large
queries.)
Note that only $m["field"] is consumed from allFields() here, so option 2 or 3
need not reproduce the full column metadata for the autocomplete path.
Workaround
Skipping just the allFields() loop for pgsql leaves $Ok pre-filled from the
already-fetched table list, so table-name autocomplete still works and only
column names are lost:
if (JUSH != "pgsql") foreach (driver()->allFields() as ...) { ... }
Adminer version: 6.0.1
Compiled: single file (official
adminer:6.0.1Docker image, unmodified)Driver: PostgreSQL (pgsql)
Database version: PostgreSQL 16
Plugins used: none
Summary
Since 6.0 added SQL-editor autocomplete, every load of a page with
&sql=runs acatalog query on PostgreSQL — even when the query box is empty. On a production
catalog this costs several seconds of database CPU per page load. Adminer 5 never
ran it.
Driver::jushAutocomplete()callsallFields()unconditionally:The base
allFields()joinsINFORMATION_SCHEMA.COLUMNS+TABLE_CONSTRAINTS+KEY_COLUMN_USAGE. OnlyCOLUMNScarries the schemapredicate; the two join partners have none of their own.
Why it is expensive on PostgreSQL
EXPLAIN (ANALYZE, BUFFERS)on a schema containing 2 tables:Two compounding effects:
information_schemaviews call privilege functions per row.So the cost scales with the number of roles/grants and total constraints in the
database — not with the schema being browsed. Browsing a 2-table schema costs
the same as a 1200-table one.
Measured on a 2-table target schema throughout:
On a production database this reaches ~8 s per page load. Because the driver is
grants and constraint count, it affects every environment regardless of size.
Steps to reproduce
Log in to
dasu, open the SQL command page and leave the query box empty.Then:
The query is issued on every load. Browsing tables (no
&sql=) never issues it.Suggested fixes
Any one of these would resolve it:
TABLE_CONSTRAINTS/KEY_COLUMN_USAGEare not read catalog-wide.allFields()againstpg_catalogfor the pgsql driver, avoiding theinformation_schemaprivilege checks entirely.the columns are not needed to display the page.
Driver::jushAutocomplete()is a static call on the concrete driver class, sothere is currently no plugin hook to override it without editing the compiled
release. (Related but distinct: Autocomplete: allow to disable #1142, which concerns client-side lag on large
queries.)
TABLE_CONSTRAINTS/KEY_COLUMN_USAGEare not read catalog-wide.allFields()againstpg_catalogfor the pgsql driver, avoiding theinformation_schemaprivilege checks entirely.the columns are not needed to display the page.
Driver::jushAutocomplete()is a static call on the concrete driver class, sothere is currently no plugin hook to override it without editing the compiled
release. (Related but distinct: Autocomplete: allow to disable #1142, which concerns client-side lag on large
queries.)
Note that only
$m["field"]is consumed fromallFields()here, so option 2 or 3need not reproduce the full column metadata for the autocomplete path.
Workaround
Skipping just the
allFields()loop for pgsql leaves$Okpre-filled from thealready-fetched table list, so table-name autocomplete still works and only
column names are lost: