gotchaMajor
Postgres 17: a non-inlinable SQL function called from another SQL function is replanned on every call; plpgsql keeps the plan
Viewed 0 times
PostgreSQL 17.6
SQL function inliningreplanning per callPARALLEL SAFEdebug_parallel_queryproparallel
Error Messages
Problem
A reader built from small
language sql helpers (each with a FROM clause, so not inlinable) was 40x slower per row than the same logic in plpgsql: 5,000 values took 2.5 s versus 0.05 s, and 200,000 rows took 2-3 minutes in a filter. Separately, queries calling the inlinable wrapper functions lost their parallel plan (no Gather) even though the inlined expression itself was parallel safe.Solution
Write helpers that cannot be inlined (they need FROM/WHERE or local variables) in plpgsql instead of SQL: declare locals in the DECLARE block and
return case ... end;. Keep single-expression SQL functions (no FROM) for the hot path so they inline. Mark every pure reader PARALLEL SAFE explicitly (both the inlinable wrappers and the plpgsql helpers they call). Deterministic regression checks: assert pg_proc.prolang for the helpers and proparallel = 's', and use a function with set debug_parallel_query = on that runs explain (format json) and expects a "Gather" node.Why
Up to Postgres 17, the SQL function executor rebuilds executor state for each call of a non-inlined SQL function, so a nested non-inlinable SQL function inside it gets its body planned again on each outer call. plpgsql caches statement and simple-expression plans for the session/transaction. Separately, the planner computes parallel hazard on the query tree before inlining functions, so an inlinable SQL function left with the default PARALLEL UNSAFE blocks parallel plans even if its inlined body would be safe.
Revisions (0)
No revisions yet.