From 8554cd1bc16ac5f1ac6b303410d1b52418ddfa9a Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Fri, 12 Jun 2026 15:54:44 +0200 Subject: [PATCH] =?UTF-8?q?Perf:=20canonical=20PV=20fn=20p=C5=99es=20plpgs?= =?UTF-8?q?ql=20execute=20format=20=E2=80=94=20vynucen=C3=BD=20pl=C3=A1n?= =?UTF-8?q?=20dle=20skute=C4=8Dn=C3=BDch=20hodnot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- ...n_forecast_pv_slots_range_canonical_ab.sql | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/db/routines/R__088_fn_forecast_pv_slots_range_canonical_ab.sql b/db/routines/R__088_fn_forecast_pv_slots_range_canonical_ab.sql index c2379b8..d777ef7 100644 --- a/db/routines/R__088_fn_forecast_pv_slots_range_canonical_ab.sql +++ b/db/routines/R__088_fn_forecast_pv_slots_range_canonical_ab.sql @@ -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