Perf ROOT CAUSE: rolling faktor se počítal per řádek — hoist do proměnné (canonical PV fn 4.5s→~0.45s)
All checks were successful
CI and deploy / deploy (push) Successful in 46s
CI and deploy / migration-check (push) Successful in 23s

Skutečná příčina 600k buffers: CTE factor/factor_raw (single-ref) PG inlinuje
do projekce with_factor → fn_pv_forecast_correction_factor (48 ms / 1.9k
buffers) se vyhodnocovala ~300× per výstupní slot. Plan cache s tím neměl nic
společného (dřívější count(*) měření projekci zahodilo, proto vycházelo
0.42 s). Faktor se teď počítá jednou do v_rolling_factor a vkládá jako
literál (13. argument format).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-12 17:48:08 +02:00
parent 8554cd1bc1
commit 1406796a62

View File

@@ -31,7 +31,21 @@ set work_mem = '64MB'
as $fn$ as $fn$
declare declare
v_result jsonb; v_result jsonb;
v_rolling_factor numeric;
begin begin
-- Rolling faktor JEDNOU do proměnné: jako inlinovaná CTE (single-ref) se
-- fn_pv_forecast_correction_factor vyhodnocovala per výstupní řádek
-- (~300× ≈ 570k buffers / ~4 s — skutečná příčina pomalosti, ne plan cache).
v_rolling_factor := coalesce(
(ems.fn_pv_forecast_correction_factor(
p_site_id,
(p_now - (p_factor_window_h::text || ' hours')::interval)::timestamptz,
p_now,
p_factor_min_clamp,
p_factor_max_clamp
)->>'correction_factor')::numeric,
1.0
);
-- PG 18 cachuje plán SQL/plpgsql statementů s parametry → generický plán -- PG 18 cachuje plán SQL/plpgsql statementů s parametry → generický plán
-- (4.59 s / 600k buffers); force_custom_plan na funkci nepomohl. Execute -- (4.59 s / 600k buffers); force_custom_plan na funkci nepomohl. Execute
-- s literály vynutí čerstvý plán dle skutečných hodnot: 0.4 s / 34k buffers. -- s literály vynutí čerstvý plán dle skutečných hodnot: 0.4 s / 34k buffers.
@@ -70,20 +84,6 @@ begin
from slot_spine s from slot_spine s
cross join tz t cross join tz t
), ),
factor_raw as (
select ems.fn_pv_forecast_correction_factor(
%1$s,
(%4$L::timestamptz - (%9$s::text || ' hours')::interval)::timestamptz,
%4$L::timestamptz,
%10$s,
%11$s
) as j
),
factor as (
select
coalesce((j->>'correction_factor')::numeric, 1.0::numeric) as rolling_factor
from factor_raw
),
profile as ( profile as (
select ems.fn_pv_forecast_delta_profile_cached( select ems.fn_pv_forecast_delta_profile_cached(
%1$s, %1$s,
@@ -194,18 +194,17 @@ begin
ab.pv_b_forecast_raw_w, ab.pv_b_forecast_raw_w,
ab.pv_a_forecast_delta_w, ab.pv_a_forecast_delta_w,
ab.pv_b_forecast_delta_w, ab.pv_b_forecast_delta_w,
f.rolling_factor, %13$s::numeric as rolling_factor,
case case
when ab.interval_start < b.now_slot then 1.0::numeric when ab.interval_start < b.now_slot then 1.0::numeric
when %12$s <= 0 then f.rolling_factor when %12$s <= 0 then %13$s::numeric
else else
case case
when ((extract(epoch from (ab.interval_start - b.now_slot)) / 900)::int) >= %12$s then 1.0::numeric when ((extract(epoch from (ab.interval_start - b.now_slot)) / 900)::int) >= %12$s then 1.0::numeric
else (1.0::numeric + (f.rolling_factor - 1.0::numeric) * (1.0::numeric - ((extract(epoch from (ab.interval_start - b.now_slot)) / 900)::numeric / %12$s::numeric))) else (1.0::numeric + (%13$s::numeric - 1.0::numeric) * (1.0::numeric - ((extract(epoch from (ab.interval_start - b.now_slot)) / 900)::numeric / %12$s::numeric)))
end end
end as rolling_effective_factor end as rolling_effective_factor
from fc_ab ab from fc_ab ab
cross join factor f
cross join bounds b cross join bounds b
) )
select coalesce( select coalesce(
@@ -242,7 +241,8 @@ $q$,
p_factor_window_h, p_factor_window_h,
p_factor_min_clamp, p_factor_min_clamp,
p_factor_max_clamp, p_factor_max_clamp,
p_decay_slots p_decay_slots,
v_rolling_factor
) into v_result; ) into v_result;
return coalesce(v_result, '[]'::jsonb); return coalesce(v_result, '[]'::jsonb);
end; end;