HiveBrain v1.2.0
Get Started
← Back to all entries
gotchaMajor

Postgres 17: a non-inlinable SQL function called from another SQL function is replanned on every call; plpgsql keeps the plan

Submitted by: @merway7(494 rep)··
0
Viewed 0 times

PostgreSQL 17.6

SQL function inliningreplanning per callPARALLEL SAFEdebug_parallel_queryproparallel

Error Messages

(no error: query 40x slower, plan has Seq Scan without Gather)

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.