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
|
p_decay_slots int default 16
|
||||||
)
|
)
|
||||||
returns jsonb
|
returns jsonb
|
||||||
language sql
|
language plpgsql
|
||||||
stable
|
stable
|
||||||
set work_mem = '64MB'
|
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$
|
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 (
|
with tz as (
|
||||||
select coalesce(nullif(trim(s.timezone), ''), 'Europe/Prague') as tz_name
|
select coalesce(nullif(trim(s.timezone), ''), 'Europe/Prague') as tz_name
|
||||||
from ems.site s
|
from ems.site s
|
||||||
where s.id = p_site_id
|
where s.id = %1$s
|
||||||
),
|
),
|
||||||
bounds as (
|
bounds as (
|
||||||
select
|
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
|
case
|
||||||
when p_to <= p_from then date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') + interval '15 minutes'
|
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 p_to > p_from + interval '60 days' then date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') + interval '60 days'
|
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', p_to, timestamptz '1970-01-01T00:00:00Z')
|
else date_bin(interval '15 minutes', %3$L::timestamptz, timestamptz '1970-01-01T00:00:00Z')
|
||||||
end as ts_to,
|
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 (
|
slot_spine as (
|
||||||
select gs as interval_start
|
select gs as interval_start
|
||||||
@@ -68,11 +72,11 @@ as $fn$
|
|||||||
),
|
),
|
||||||
factor_raw as (
|
factor_raw as (
|
||||||
select ems.fn_pv_forecast_correction_factor(
|
select ems.fn_pv_forecast_correction_factor(
|
||||||
p_site_id,
|
%1$s,
|
||||||
(p_now - (p_factor_window_h::text || ' hours')::interval)::timestamptz,
|
(%4$L::timestamptz - (%9$s::text || ' hours')::interval)::timestamptz,
|
||||||
p_now,
|
%4$L::timestamptz,
|
||||||
p_factor_min_clamp,
|
%10$s,
|
||||||
p_factor_max_clamp
|
%11$s
|
||||||
) as j
|
) as j
|
||||||
),
|
),
|
||||||
factor as (
|
factor as (
|
||||||
@@ -82,11 +86,11 @@ as $fn$
|
|||||||
),
|
),
|
||||||
profile as (
|
profile as (
|
||||||
select ems.fn_pv_forecast_delta_profile_cached(
|
select ems.fn_pv_forecast_delta_profile_cached(
|
||||||
p_site_id,
|
%1$s,
|
||||||
p_delta_data_from,
|
%5$L::timestamptz,
|
||||||
p_delta_data_to,
|
%6$L::timestamptz,
|
||||||
p_half_life_days,
|
%7$s,
|
||||||
p_threshold_w
|
%8$s
|
||||||
) as j
|
) as j
|
||||||
),
|
),
|
||||||
delta_by_array as (
|
delta_by_array as (
|
||||||
@@ -117,16 +121,16 @@ as $fn$
|
|||||||
on fpi.interval_start >= b.ts_from
|
on fpi.interval_start >= b.ts_from
|
||||||
and fpi.interval_start < b.ts_to
|
and fpi.interval_start < b.ts_to
|
||||||
and fpi.pv_array_id in (
|
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
|
inner join ems.forecast_pv_run fpr
|
||||||
on fpr.id = fpi.run_id
|
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.pv_array_id = fpi.pv_array_id
|
||||||
and fpr.status = 'ok'
|
and fpr.status = 'ok'
|
||||||
inner join ems.asset_pv_array apa
|
inner join ems.asset_pv_array apa
|
||||||
on apa.id = fpr.pv_array_id
|
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
|
order by fpi.interval_start, fpr.pv_array_id, fpr.created_at desc
|
||||||
),
|
),
|
||||||
fc_with_sod as (
|
fc_with_sod as (
|
||||||
@@ -193,11 +197,11 @@ as $fn$
|
|||||||
f.rolling_factor,
|
f.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 p_decay_slots <= 0 then f.rolling_factor
|
when %12$s <= 0 then f.rolling_factor
|
||||||
else
|
else
|
||||||
case
|
case
|
||||||
when ((extract(epoch from (ab.interval_start - b.now_slot)) / 900)::int) >= p_decay_slots 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 / p_decay_slots::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
|
||||||
end as rolling_effective_factor
|
end as rolling_effective_factor
|
||||||
from fc_ab ab
|
from fc_ab ab
|
||||||
@@ -225,7 +229,23 @@ as $fn$
|
|||||||
),
|
),
|
||||||
'[]'::jsonb
|
'[]'::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$;
|
$fn$;
|
||||||
|
|
||||||
comment on function ems.fn_forecast_pv_slots_range_canonical_ab is
|
comment on function ems.fn_forecast_pv_slots_range_canonical_ab is
|
||||||
|
|||||||
Reference in New Issue
Block a user