On Fri, 18 Jul 2025 at 12:54, Craig Ringer
<craig.rin...@enterprisedb.com> wrote:
> [...]
> I recently ran into an issue where a monitoring component was calling
> pg_catalog.pg_database_size() at a ~15s interval on a large
> schema-sharded database. It took so long that the query was timing out
> before the next 15s scrape interval would start.

Fast-follow on this, because I thought to do a local experiment on performance.

I suspect that there's more at work with the reported issue than just
the cost of statting all the files, because even if I create a 100,000
table db with at least 2 indexes per table (and all the auxiliary
forks etc that will result in)

I created 100,000 tables to fake up a DB that looks like a
schema-sharded one, then tested timing on pg_database_size().

test=# select count(1) from pg_class;
 count
-------
 40496
(1 row)

and now

test=# select pg_database_size(current_database());
 pg_database_size
------------------
        660013571
(1 row)

Time: 75.614 ms

... it still takes hardly any time at all.

If I drop my disk cache on my workstation:

echo 3 > /proc/sys/vm/drop_caches

then repeating pg_database_size()  only takes 254 ms the first time,
then back to 74ms.

In other words, even if the DB has many millions of tables, there's no
way that pg_database_size() could reasonably take minutes to run and
contribute meaningfully to a timeout of a monitoring or scrape
process.

Not unless there's something (or several somethings) else badly wrong
on the DB instance - extreme inode cache thrashing, excessive block
device read-ahead, unreasonably high block device I/O latencies, etc.

I still think it's worth mentioning pg_database_size() needing to stat
every file in the docs, but it's clear there's more going on in the
particular case I'm seeing than that alone. I'll try to report back if
I learn anything interesting that explains the rest of the performance
issues.


FYI the crude method used to create the schema since I couldn't be
bothered scripting something sensible up was:

turn fsync off (never do this unless you're happy to completely
destroy all data in your postgres instance), set
max_locks_per_transaction=1000 and restart Pg

then

DO LANGUAGE plpgsql
$$
BEGIN FOR i IN 1..10000 LOOP
  EXECUTE format('CREATE TABLE %I(x serial primary key, y integer
unique)', 'padding_table_b_'||i);
  EXECUTE format('INSERT INTO %I(y) VALUES (0)', 'padding_table_b_'||i);
  IF i % 100 = 0 THEN RAISE NOTICE 'up to: %', i; END IF;
END LOOP;
END;
$$;


--
Craig Ringer
EnterpriseDB


Reply via email to