Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/installcheck
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ else
fi

# Execute the test fixtures
psql -v ON_ERROR_STOP= -f test/fixtures.sql -f lints/0001*.sql -f lints/0002*.sql -f lints/0003*.sql -f lints/0004*.sql -f lints/0005*.sql -f lints/0006*.sql -f lints/0007*.sql -f lints/0008*.sql -f lints/0009*.sql -f lints/0010*.sql -f lints/0011*.sql -f lints/0013*.sql -f lints/0014*.sql -f lints/0015*.sql -f lints/0016*.sql -f lints/0017*.sql -f lints/0018*.sql -f lints/0019*.sql -f lints/0020*.sql -f lints/0021*.sql -f lints/0022*.sql -f lints/0023*.sql -f lints/0024*.sql -f lints/0025*.sql -f lints/0026*.sql -f lints/0027*.sql -f lints/0028*.sql -f lints/0029*.sql -d contrib_regression
psql -v ON_ERROR_STOP= -f test/fixtures.sql -f lints/0001*.sql -f lints/0002*.sql -f lints/0003*.sql -f lints/0004*.sql -f lints/0005*.sql -f lints/0006*.sql -f lints/0007*.sql -f lints/0008*.sql -f lints/0009*.sql -f lints/0010*.sql -f lints/0011*.sql -f lints/0013*.sql -f lints/0014*.sql -f lints/0015*.sql -f lints/0016*.sql -f lints/0017*.sql -f lints/0018*.sql -f lints/0019*.sql -f lints/0020*.sql -f lints/0021*.sql -f lints/0022*.sql -f lints/0023*.sql -f lints/0024*.sql -f lints/0025*.sql -f lints/0026*.sql -f lints/0027*.sql -f lints/0028*.sql -f lints/0029*.sql -f lints/0030*.sql -d contrib_regression

# Run tests
${REGRESS} --use-existing --dbname=contrib_regression --inputdir=${TESTDIR} ${TESTS}
Expand Down
30 changes: 30 additions & 0 deletions docs/0030_autovacuum_disabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
**Level:** INFO

**Summary:** Detects tables where `autovacuum_enabled=false` has been set as a storage parameter.

**Ramification:** Dead tuples accumulate without bound, causing table bloat that degrades query performance and increases storage costs. The effect compounds after any UPDATE or DELETE workload.

---

### Rationale

PostgreSQL autovacuum reclaims space from dead tuples left by UPDATE and DELETE operations. Disabling it at the table level (`ALTER TABLE t SET (autovacuum_enabled = false)`) prevents this cleanup entirely for that table, regardless of the cluster-level autovacuum setting.

### How to Resolve

**Re-enable autovacuum and reclaim existing dead tuples immediately:**

```sql
ALTER TABLE public.orders RESET (autovacuum_enabled);
VACUUM ANALYZE public.orders;
```

### False Positives

This lint may fire when the setting is intentional:

- **Read-only archive tables** — no UPDATEs or DELETEs means no dead tuples; autovacuum has nothing to do.
- **Bulk-load staging tables** — autovacuum is temporarily disabled to avoid I/O contention during ETL; should be re-enabled after the load completes.
- **Manual vacuum schedules** — tables vacuumed explicitly via `pg_cron` or another scheduler; autovacuum is disabled to avoid conflicts with the scheduled job.

In these cases the lint can be safely ignored, but verify the table is not accumulating dead tuples via `pg_stat_user_tables.n_dead_tup`.
36 changes: 36 additions & 0 deletions lints/0030_autovacuum_disabled.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
create view lint."0030_autovacuum_disabled" as
select
'autovacuum_disabled' as name,
'Autovacuum Disabled' as title,
'INFO' as level,
'EXTERNAL' as facing,
array['PERFORMANCE'] as categories,
'Table has autovacuum_enabled=false set as a storage parameter. Without autovacuum, dead tuples accumulate and cause table bloat — see the docs for legitimate exceptions before acting.' as description,
format(
'Table `%s`.`%s` has autovacuum_enabled=false set as a storage parameter.',
nsp.nspname,
cls.relname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0030_autovacuum_disabled' as remediation,
jsonb_build_object(
'schema', nsp.nspname,
'name', cls.relname,
'type', 'table'
) as metadata,
format('autovacuum_disabled_%s_%s', nsp.nspname, cls.relname) as cache_key
from pg_catalog.pg_class cls
join pg_catalog.pg_namespace nsp on cls.relnamespace = nsp.oid
where
cls.relkind = 'r'
and 'autovacuum_enabled=false' = any(cls.reloptions)
and nsp.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config',
'_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql',
'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga',
'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog',
'realtime', 'repack', 'storage', 'supabase_functions',
'supabase_migrations', 'tiger', 'topology', 'vault'
)
order by
nsp.nspname,
cls.relname;
1 change: 1 addition & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ nav:
- Signed-In Users Can See Object in GraphQL Schema: '0027_pg_graphql_authenticated_table_exposed.md'
- Public Can Execute SECURITY DEFINER Function: '0028_anon_security_definer_function_executable.md'
- Signed-In Users Can Execute SECURITY DEFINER Function: '0029_authenticated_security_definer_function_executable.md'
- Autovacuum Disabled: '0030_autovacuum_disabled.md'

theme:
name: 'material'
Expand Down
38 changes: 37 additions & 1 deletion splinter.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1838,4 +1838,40 @@ from
order by
schema_name,
function_name,
function_args)
function_args)
union all
(select
'autovacuum_disabled' as name,
'Autovacuum Disabled' as title,
'INFO' as level,
'EXTERNAL' as facing,
array['PERFORMANCE'] as categories,
'Table has autovacuum_enabled=false set as a storage parameter. Without autovacuum, dead tuples accumulate and cause table bloat — see the docs for legitimate exceptions before acting.' as description,
format(
'Table `%s`.`%s` has autovacuum_enabled=false set as a storage parameter.',
nsp.nspname,
cls.relname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0030_autovacuum_disabled' as remediation,
jsonb_build_object(
'schema', nsp.nspname,
'name', cls.relname,
'type', 'table'
) as metadata,
format('autovacuum_disabled_%s_%s', nsp.nspname, cls.relname) as cache_key
from pg_catalog.pg_class cls
join pg_catalog.pg_namespace nsp on cls.relnamespace = nsp.oid
where
cls.relkind = 'r'
and 'autovacuum_enabled=false' = any(cls.reloptions)
and nsp.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config',
'_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql',
'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga',
'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog',
'realtime', 'repack', 'storage', 'supabase_functions',
'supabase_migrations', 'tiger', 'topology', 'vault'
)
order by
nsp.nspname,
cls.relname)
37 changes: 37 additions & 0 deletions test/expected/0030_autovacuum_disabled.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
begin;
set local search_path = '';
-- BASELINE: no user tables, expect 0 rows
select * from lint."0030_autovacuum_disabled";
name | title | level | facing | categories | description | detail | remediation | metadata | cache_key
------+-------+-------+--------+------------+-------------+--------+-------------+----------+-----------
(0 rows)

savepoint a;
-- NEGATIVE: autovacuum_enabled=true explicitly set — must not fire
create table public.active_table (id int);
alter table public.active_table set (autovacuum_enabled = true);
select * from lint."0030_autovacuum_disabled";
name | title | level | facing | categories | description | detail | remediation | metadata | cache_key
------+-------+-------+--------+------------+-------------+--------+-------------+----------+-----------
(0 rows)

rollback to savepoint a;
savepoint b;
-- POSITIVE: autovacuum_enabled=false explicitly set — must fire
create table public.orders (id int);
alter table public.orders set (autovacuum_enabled = false);
select name, detail, cache_key from lint."0030_autovacuum_disabled";
name | detail | cache_key
---------------------+----------------------------------------------------------------------------------+-----------------------------------
autovacuum_disabled | Table `public`.`orders` has autovacuum_enabled=false set as a storage parameter. | autovacuum_disabled_public_orders
(1 row)

-- RESOLUTION: reset the storage parameter to re-enable autovacuum
alter table public.orders reset (autovacuum_enabled);
select * from lint."0030_autovacuum_disabled";
name | title | level | facing | categories | description | detail | remediation | metadata | cache_key
------+-------+-------+--------+------------+-------------+--------+-------------+----------+-----------
(0 rows)

rollback to savepoint b;
rollback;
4 changes: 3 additions & 1 deletion test/expected/queries_are_unionable.out
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ begin;
union all
select * from lint."0028_anon_security_definer_function_executable"
union all
select * from lint."0029_authenticated_security_definer_function_executable";
select * from lint."0029_authenticated_security_definer_function_executable"
union all
select * from lint."0030_autovacuum_disabled";
name | title | level | facing | categories | description | detail | remediation | metadata | cache_key
------+-------+-------+--------+------------+-------------+--------+-------------+----------+-----------
(0 rows)
Expand Down
29 changes: 29 additions & 0 deletions test/sql/0030_autovacuum_disabled.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
begin;
set local search_path = '';

-- BASELINE: no user tables, expect 0 rows
select * from lint."0030_autovacuum_disabled";

savepoint a;

-- NEGATIVE: autovacuum_enabled=true explicitly set — must not fire
create table public.active_table (id int);
alter table public.active_table set (autovacuum_enabled = true);
select * from lint."0030_autovacuum_disabled";

rollback to savepoint a;

savepoint b;

-- POSITIVE: autovacuum_enabled=false explicitly set — must fire
create table public.orders (id int);
alter table public.orders set (autovacuum_enabled = false);
select name, detail, cache_key from lint."0030_autovacuum_disabled";

-- RESOLUTION: reset the storage parameter to re-enable autovacuum
alter table public.orders reset (autovacuum_enabled);
select * from lint."0030_autovacuum_disabled";

rollback to savepoint b;

rollback;
4 changes: 3 additions & 1 deletion test/sql/queries_are_unionable.sql
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ begin;
union all
select * from lint."0028_anon_security_definer_function_executable"
union all
select * from lint."0029_authenticated_security_definer_function_executable";
select * from lint."0029_authenticated_security_definer_function_executable"
union all
select * from lint."0030_autovacuum_disabled";

rollback;
Loading