Skip to content

Performance: only plan the dynamic severity branches when they are used - #3081

Open
TheBeast85 wants to merge 1 commit into
greenbone:mainfrom
TheBeast85:perf/report-severity-planning
Open

Performance: only plan the dynamic severity branches when they are used#3081
TheBeast85 wants to merge 1 commit into
greenbone:mainfrom
TheBeast85:perf/report-severity-planning

Conversation

@TheBeast85

Copy link
Copy Markdown

What

  • Split report_severity into report_severity_cached, report_severity_dynamic and a report_severity that is a coalesce of the two.
  • Mark the severity functions STABLE.

Why

report_severity was one function body, and that body was planned again on every call: 1479 ms of planning across 5047 calls against 30 ms of execution, measured on the severity aggregate with pg_stat_statements.track_planning on.

Almost every call is answered by the cached value in report_counts, a single index lookup. The four dynamic branches underneath scan the results table and are hardly ever evaluated, yet every call paid to plan them. A function body is only planned when the function is invoked, and coalesce stops at the first non null argument at execution time, so moving the expensive branches into their own function means they are only planned when there is no cached value. They stop appearing in pg_stat_statements altogether.

STABLE alone did not help, and neither did removing the CTE: task_severity calls report_severity from its own SQL body and is itself not inlinable, so every row creates a fresh call context.

The severity aggregate goes from 0.351 s to 0.098 s. Severity values and the aggregate are identical character for character with the reference taken before the change.

References

Split out of #3067 as requested.

Checklist

  • Tested manually against a Community Edition stack

report_severity was one function body, and that body was planned again on
every call.  Measured on the severity aggregate with
pg_stat_statements.track_planning on, that was 1479 ms of planning across
5047 calls against 30 ms of execution.

Marking it STABLE and taking out its CTE did not help: task_severity calls
report_severity from its own SQL body and task_severity is itself not
inlinable, its body has a subquery with FROM, so every row creates a fresh
call context.

What costs the planning is not the common case.  Almost every call is
answered by the cached value in report_counts, a single index lookup, while
the four dynamic branches underneath scan the results table and are hardly
ever evaluated.

A function body is only planned when the function is invoked, and coalesce
stops at the first non null argument at execution time.  Split into
report_severity_cached, report_severity_dynamic and a report_severity that
is a coalesce of the two, the expensive body is no longer planned at all; it
stops appearing in pg_stat_statements.  The severity functions are marked
STABLE, which is also what lets report_severity be inlined.

The severity aggregate goes from 0.351 s to 0.098 s.  Severity values and
the aggregate are identical character for character with the reference taken
before the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant