Perf: canonical PV fn přes plpgsql execute format — vynucený plán dle skutečných hodnot
set plan_cache_mode=force_custom_plan na SQL funkci v PG 18 nezabral (stále generický plán, 4.5-9 s / 600k buffers). plpgsql EXECUTE s literály = vždy čerstvý plán: tělo s konstantami změřeno 0.42 s / 34k buffers. Signatura, chování i výstup beze změny. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,27 +25,31 @@ create or replace function ems.fn_forecast_pv_slots_range_canonical_ab(
|
||||
p_decay_slots int default 16
|
||||
)
|
||||
returns jsonb
|
||||
language sql
|
||||
language plpgsql
|
||||
stable
|
||||
set work_mem = '64MB'
|
||||
-- PG 18 cachuje plány SQL funkcí → generický plán bez znalosti hodnot parametrů
|
||||
-- (4.5 s / 607k buffers); s custom planem dle skutečných hodnot 0.4 s / 34k.
|
||||
set plan_cache_mode = force_custom_plan
|
||||
as $fn$
|
||||
declare
|
||||
v_result jsonb;
|
||||
begin
|
||||
-- PG 18 cachuje plán SQL/plpgsql statementů s parametry → generický plán
|
||||
-- (4.5–9 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.
|
||||
execute format($q$
|
||||
with tz as (
|
||||
select coalesce(nullif(trim(s.timezone), ''), 'Europe/Prague') as tz_name
|
||||
from ems.site s
|
||||
where s.id = p_site_id
|
||||
where s.id = %1$s
|
||||
),
|
||||
bounds as (
|
||||
select
|
||||
date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') as ts_from,
|
||||
date_bin(interval '15 minutes', %2$L::timestamptz, timestamptz '1970-01-01T00:00:00Z') as ts_from,
|
||||
case
|
||||
when p_to <= p_from then date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') + interval '15 minutes'
|
||||
when p_to > p_from + interval '60 days' then date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') + interval '60 days'
|
||||
else date_bin(interval '15 minutes', p_to, timestamptz '1970-01-01T00:00:00Z')
|
||||
when %3$L::timestamptz <= %2$L::timestamptz then date_bin(interval '15 minutes', %2$L::timestamptz, timestamptz '1970-01-01T00:00:00Z') + interval '15 minutes'
|
||||
when %3$L::timestamptz > %2$L::timestamptz + interval '60 days' then date_bin(interval '15 minutes', %2$L::timestamptz, timestamptz '1970-01-01T00:00:00Z') + interval '60 days'
|
||||
else date_bin(interval '15 minutes', %3$L::timestamptz, timestamptz '1970-01-01T00:00:00Z')
|
||||
end as ts_to,
|
||||
date_bin(interval '15 minutes', p_now, timestamptz '1970-01-01T00:00:00Z') as now_slot
|
||||
date_bin(interval '15 minutes', %4$L::timestamptz, timestamptz '1970-01-01T00:00:00Z') as now_slot
|
||||
),
|
||||
slot_spine as (
|
||||
select gs as interval_start
|
||||
@@ -68,11 +72,11 @@ as $fn$
|
||||
),
|
||||
factor_raw as (
|
||||
select 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
|
||||
%1$s,
|
||||
(%4$L::timestamptz - (%9$s::text || ' hours')::interval)::timestamptz,
|
||||
%4$L::timestamptz,
|
||||
%10$s,
|
||||
%11$s
|
||||
) as j
|
||||
),
|
||||
factor as (
|
||||
@@ -82,11 +86,11 @@ as $fn$
|
||||
),
|
||||
profile as (
|
||||
select ems.fn_pv_forecast_delta_profile_cached(
|
||||
p_site_id,
|
||||
p_delta_data_from,
|
||||
p_delta_data_to,
|
||||
p_half_life_days,
|
||||
p_threshold_w
|
||||
%1$s,
|
||||
%5$L::timestamptz,
|
||||
%6$L::timestamptz,
|
||||
%7$s,
|
||||
%8$s
|
||||
) as j
|
||||
),
|
||||
delta_by_array as (
|
||||
@@ -117,16 +121,16 @@ as $fn$
|
||||
on fpi.interval_start >= b.ts_from
|
||||
and fpi.interval_start < b.ts_to
|
||||
and fpi.pv_array_id in (
|
||||
select apa0.id from ems.asset_pv_array apa0 where apa0.site_id = p_site_id
|
||||
select apa0.id from ems.asset_pv_array apa0 where apa0.site_id = %1$s
|
||||
)
|
||||
inner join ems.forecast_pv_run fpr
|
||||
on fpr.id = fpi.run_id
|
||||
and fpr.site_id = p_site_id
|
||||
and fpr.site_id = %1$s
|
||||
and fpr.pv_array_id = fpi.pv_array_id
|
||||
and fpr.status = 'ok'
|
||||
inner join ems.asset_pv_array apa
|
||||
on apa.id = fpr.pv_array_id
|
||||
and apa.site_id = p_site_id
|
||||
and apa.site_id = %1$s
|
||||
order by fpi.interval_start, fpr.pv_array_id, fpr.created_at desc
|
||||
),
|
||||
fc_with_sod as (
|
||||
@@ -193,11 +197,11 @@ as $fn$
|
||||
f.rolling_factor,
|
||||
case
|
||||
when ab.interval_start < b.now_slot then 1.0::numeric
|
||||
when p_decay_slots <= 0 then f.rolling_factor
|
||||
when %12$s <= 0 then f.rolling_factor
|
||||
else
|
||||
case
|
||||
when ((extract(epoch from (ab.interval_start - b.now_slot)) / 900)::int) >= p_decay_slots 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 / p_decay_slots::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)))
|
||||
end
|
||||
end as rolling_effective_factor
|
||||
from fc_ab ab
|
||||
@@ -225,7 +229,23 @@ as $fn$
|
||||
),
|
||||
'[]'::jsonb
|
||||
)
|
||||
from with_factor w;
|
||||
from with_factor w
|
||||
$q$,
|
||||
p_site_id,
|
||||
p_from,
|
||||
p_to,
|
||||
p_now,
|
||||
p_delta_data_from,
|
||||
p_delta_data_to,
|
||||
p_half_life_days,
|
||||
p_threshold_w,
|
||||
p_factor_window_h,
|
||||
p_factor_min_clamp,
|
||||
p_factor_max_clamp,
|
||||
p_decay_slots
|
||||
) into v_result;
|
||||
return coalesce(v_result, '[]'::jsonb);
|
||||
end;
|
||||
$fn$;
|
||||
|
||||
comment on function ems.fn_forecast_pv_slots_range_canonical_ab is
|
||||
|
||||
Reference in New Issue
Block a user