Skip to content

Instantly share code, notes, and snippets.

@sebawebber
Last active July 16, 2026 01:51
Show Gist options
  • Select an option

  • Save sebawebber/1eccdea3f5e484b8ae6590121d27d7d9 to your computer and use it in GitHub Desktop.

Select an option

Save sebawebber/1eccdea3f5e484b8ae6590121d27d7d9 to your computer and use it in GitHub Desktop.
PostgreSQL 18.4 reltuples Infinity reproducer

PostgreSQL 18.4: pg_restore_relation_stats() accepts reltuples = Infinity and writes non-finite catalog stats

PostgreSQL version

  • PostgreSQL 18.4 (Docker image postgres:18.4)

Platform

Docker on Linux/macOS (tested via a single container repro script)

Description

pg_restore_relation_stats() accepts Infinity::real for the reltuples argument and writes that value into pg_class.reltuples.

After that, planner estimates become pathological (very large row estimates), until a later ANALYZE rewrites reltuples to a finite value.

The function currently guards reltuples < -1.0, but does not reject non-finite float values (Infinity, -Infinity, NaN).

This reproducer is about the validation gap itself (non-finite catalog statistics are accepted).

It intentionally injects non-finite input through pg_restore_relation_stats() to prove acceptance/persistence of invalid stats values. It does not prove that this is the only ingress path by which production environments may reach the same state.

Steps to reproduce

  1. Start PostgreSQL 18.4 in Docker.
  2. Create a regular table and run ANALYZE to establish finite baseline stats.
  3. Execute:
    • SELECT pg_restore_relation_stats('schemaname','public','relname','t','reltuples','Infinity'::real);
  4. Confirm:
    • SELECT reltuples::text FROM pg_class WHERE oid = 'public.t'::regclass;
  5. Inspect planner output (EXPLAIN SELECT * FROM t;).
  6. Run ANALYZE t; and confirm reltuples returns to finite.

Actual output

From the attached repro.sh:

  • pg_restore_relation_stats(...) returns t
  • pg_class.reltuples becomes Infinity
  • Planner row estimate for a plain table scan becomes extremely large (clamped high estimate)
  • Running ANALYZE restores finite reltuples

Expected output

pg_restore_relation_stats() should reject non-finite values for reltuples (at least Infinity, -Infinity, NaN) with a warning/error similar to existing range validation.

Configuration

No non-default server configuration required for this reproducer.

Reproducer files

  • repro.sh - one-command Docker reproducer
  • script.log - generated by repro.sh (psql output)
  • postgres.log - generated by repro.sh (container logs)
#!/usr/bin/env bash
set -euo pipefail
PG_IMAGE="${PG_IMAGE:-postgres:18.4}"
CONTAINER="${CONTAINER:-pg18-reltuples-infinity-$$}"
DB_USER="${DB_USER:-postgres}"
DB_NAME="${DB_NAME:-postgres}"
DB_PASSWORD="${DB_PASSWORD:-postgres}"
HOST_PORT="${HOST_PORT:-55432}"
cleanup() {
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
}
trap cleanup EXIT
echo "[1/5] Starting container $CONTAINER ($PG_IMAGE)"
docker run -d \
--name "$CONTAINER" \
-e POSTGRES_USER="$DB_USER" \
-e POSTGRES_PASSWORD="$DB_PASSWORD" \
-e POSTGRES_DB="$DB_NAME" \
-p "$HOST_PORT:5432" \
"$PG_IMAGE" >/dev/null
echo "[2/5] Waiting for PostgreSQL readiness"
for _ in $(seq 1 60); do
if docker exec "$CONTAINER" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then
break
fi
sleep 1
done
docker exec "$CONTAINER" pg_isready -U "$DB_USER" -d "$DB_NAME"
echo "[3/5] Running SQL reproducer (output -> script.log)"
docker exec -i "$CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 <<'SQL' | tee script.log
\set ON_ERROR_STOP on
\pset pager off
\echo '== server version =='
SHOW server_version;
\echo '== baseline finite stats =='
DROP TABLE IF EXISTS t;
CREATE TABLE t AS SELECT g AS id FROM generate_series(1, 100000) AS g;
ANALYZE t;
SELECT reltuples::text AS baseline_reltuples, relpages
FROM pg_class
WHERE oid = 'public.t'::regclass;
\echo '== inject Infinity into reltuples via pg_restore_relation_stats =='
SELECT pg_restore_relation_stats(
'schemaname', 'public',
'relname', 't',
'reltuples', 'Infinity'::real
) AS restore_ok;
SELECT reltuples::text AS reltuples_after_restore, relpages
FROM pg_class
WHERE oid = 'public.t'::regclass;
\echo '== planner estimate after Infinity =='
EXPLAIN SELECT * FROM t;
\echo '== analyze rewrites reltuples to finite again =='
ANALYZE t;
SELECT reltuples::text AS reltuples_after_analyze, relpages
FROM pg_class
WHERE oid = 'public.t'::regclass;
SQL
echo "[4/5] Capturing postgres logs (output -> postgres.log)"
docker logs "$CONTAINER" > postgres.log 2>&1
echo "[5/5] Done"
echo "Artifacts:"
echo " - script.log"
echo " - postgres.log"
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are enabled.
fixing permissions on existing directory /var/lib/postgresql/18/docker ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
syncing data to disk ... ok
Success. You can now start the database server using:
pg_ctl -D /var/lib/postgresql/18/docker -l logfile start
waiting for server to start....2026-07-16 01:39:30.062 UTC [65] LOG: starting PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
2026-07-16 01:39:30.063 UTC [65] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-07-16 01:39:30.067 UTC [71] LOG: database system was shut down at 2026-07-16 01:39:29 UTC
2026-07-16 01:39:30.068 UTC [65] LOG: database system is ready to accept connections
done
server started
/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
waiting for server to shut down....2026-07-16 01:39:30.174 UTC [65] LOG: received fast shutdown request
2026-07-16 01:39:30.175 UTC [65] LOG: aborting any active transactions
2026-07-16 01:39:30.176 UTC [65] LOG: background worker "logical replication launcher" (PID 74) exited with exit code 1
2026-07-16 01:39:30.177 UTC [69] LOG: shutting down
2026-07-16 01:39:30.177 UTC [69] LOG: checkpoint starting: shutdown immediate
2026-07-16 01:39:30.183 UTC [69] LOG: checkpoint complete: wrote 0 buffers (0.0%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.002 s, total=0.007 s; sync files=2, longest=0.001 s, average=0.001 s; distance=0 kB, estimate=0 kB; lsn=0/175F8E8, redo lsn=0/175F8E8
2026-07-16 01:39:30.188 UTC [65] LOG: database system is shut down
done
server stopped
PostgreSQL init process complete; ready for start up.
2026-07-16 01:39:30.292 UTC [1] LOG: starting PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
2026-07-16 01:39:30.292 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2026-07-16 01:39:30.292 UTC [1] LOG: listening on IPv6 address "::", port 5432
2026-07-16 01:39:30.296 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-07-16 01:39:30.300 UTC [85] LOG: database system was shut down at 2026-07-16 01:39:30 UTC
2026-07-16 01:39:30.303 UTC [1] LOG: database system is ready to accept connections
Pager usage is off.
== server version ==
server_version
-------------------------------
18.4 (Debian 18.4-1.pgdg13+1)
(1 row)
== baseline finite stats ==
DROP TABLE
SELECT 100000
ANALYZE
baseline_reltuples | relpages
--------------------+----------
100000 | 448
(1 row)
== inject Infinity into reltuples via pg_restore_relation_stats ==
restore_ok
------------
t
(1 row)
reltuples_after_restore | relpages
-------------------------+----------
Infinity | 448
(1 row)
== planner estimate after Infinity ==
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------
Seq Scan on t (cost=0.00..Infinity rows=10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104 width=4)
(1 row)
== analyze rewrites reltuples to finite again ==
ANALYZE
reltuples_after_analyze | relpages
-------------------------+----------
100000 | 448
(1 row)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment