add: log schema cache queries' timings

This adds a new log line that shows each schema cache query time individually, only on
`log-level=debug`. Like so:

```
$ PGRST_LOG_LEVEL=debug postgrest-with-pg-17 -f test/spec/fixtures/load.sql postgrest-run

....
10/Apr/2026:21:48:45 -0500: Schema cache queried in 192.2 milliseconds
10/Apr/2026:21:48:45 -0500: tables: 72.027 ms, keydeps: 20.118 ms, rels: 6.189 ms, funcs: 35.010 ms, comprels: 4.319 ms, dreps: 1.614 ms, mhandlers: 7.419 ms, tzones: 43.025 ms
```

This helps debug specific schema cache queries being slow like on
https://github.com/PostgREST/postgrest/issues/4613#issuecomment-4210191065 and
https://github.com/PostgREST/postgrest/issues/3046#issuecomment-3469059948.
It also closes https://github.com/PostgREST/postgrest/issues/3215,
which main motivation was to find out which query is slow.

Implementation details
---------------------

To time each query inside a transaction in pure SQL, we do:

```sql
-- start timer
select set_config('pgrst.tmp_x', clock_timestamp()::text, false);
-- run the query
select <query>
-- end timer
select set_config('pgrst.tmp_x', (clock_timestamp() - current_setting('pgrst.tmp_x', false)::timestamptz)::text, false);

-- .... repeated for every query

-- at the end we capture all the timings with
select extract('milliseconds' from current_setting('pgrst.tmp_x', false)::interval), extract(..;
```

Considerations
--------------

Only added this on `log-level=debug` because while the queries are fast
and the data is valuable, it triples the amount of queries we run during schema cache refresh,
which could be troublesome on slow networks. It's possible to reduce the
amount of queries by starting and stopping timers in one statement, but
this would still double the amount of queries and makes the code messy,
doesn't seem worth it.

Also it would pollute pg_stat_statements, it's only required to debug certain
extreme cases anyway.
This commit is contained in:
steve-chavez
2026-04-16 13:10:24 -05:00
committed by Steve Chavez
parent 6af77360d3
commit bcc8998e5e
7 changed files with 141 additions and 18 deletions
+32
View File
@@ -1269,6 +1269,38 @@ def test_schema_cache_load_sleep_logs(defaultenv):
assert 1000 < observed_ms < 2000
@pytest.mark.parametrize("timezone_enabled", ["true", "false"])
@pytest.mark.parametrize("level", ["crit", "error", "warn", "info", "debug"])
def test_schema_cache_query_timings_log(level, timezone_enabled, defaultenv):
"Schema cache query timings should be logged on log-level=debug."
env = {
**defaultenv,
"PGRST_LOG_LEVEL": level,
# when this is disabled, it should log 0 for tzones
"PGRST_DB_TIMEZONE_ENABLED": timezone_enabled,
}
# here we also capture the tzones: <value> ms
log_pattern = re.compile(
r".+: tables: [\d.]+ ms, keydeps: [\d.]+ ms, rels: [\d.]+ ms, funcs: [\d.]+ ms, comprels: [\d.]+ ms, dreps: [\d.]+ ms, mhandlers: [\d.]+ ms, tzones: ([\d.]+) ms"
)
with run(env=env, no_startup_stdout=False) as postgrest:
output = drain_stdout(postgrest)
timing_matches = [
match for line in output if (match := log_pattern.match(line))
]
if level == "debug":
assert len(timing_matches) == 1
if timezone_enabled == "false":
assert float(timing_matches[0].group(1)) == 0
else:
assert float(timing_matches[0].group(1)) > 0
else:
assert not timing_matches
@pytest.mark.parametrize("dburi_type", ["no_params", "no_params_qmark", "with_params"])
def test_get_pgrst_version_with_uri_connection_string(dburi_type, dburi, defaultenv):
"The fallback_application_name should be added to the db-uri if it has a URI format"