perf(svod2): v_svod2_facets с SET jit TO off (O-1)

JIT добавлял ~1,4 с к вызову с буквальными параметрами (стенд масштаба 1: 1500 мс против 43 мс).
Тест: функции svod2 имеют jit=off. Убран тест формы плана: функция с SET не разворачивается.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-08 11:58:31 +04:00
parent 5db0bd4cee
commit 77bfdf69fd
3 changed files with 21 additions and 14 deletions

View File

@ -944,7 +944,8 @@ CREATE OR REPLACE FUNCTION v3.v_svod2_facets(
p_org_name text
)
RETURNS TABLE(kind varchar, value varchar, n bigint)
LANGUAGE sql STABLE
-- jit off: стоимость плана выше jit_above_cost, JIT добавлял ~1,4 с к вызову с буквальными параметрами.
LANGUAGE sql STABLE SET jit TO off
AS $function$
WITH q AS MATERIALIZED (
SELECT NULLIF(v3.svod2_casefold(v3.svod2_js_trim(p_search)), '') AS s,

View File

@ -120,23 +120,14 @@ def test_facets_match_screen_oracle(svod2_sql, svod2_data, client, admin_tokens,
assert {r.value: r.n for r in got if r.kind == col} == expected[col]
def test_facets_search_subplan_is_not_correlated(svod2_sql):
import re
plan = "\n".join(r[0] for r in svod2_sql(
"EXPLAIN (VERBOSE, COSTS OFF) "
"SELECT * FROM v3.v_svod2_facets('GO', 2031, NULL, 'аренда', NULL, NULL, NULL)", None))
# IN по svod2_lines должен быть hashed SubPlan (считается один раз); коррелированный
# считался бы на каждую строку. Не-hashed SubPlan в Filter внутри svod2_lines (EXISTS поиска)
# законен, поэтому смотрим только на выражения в Output.
outputs = [l for l in plan.splitlines() if "Output:" in l and "SubPlan" in l]
assert any("hashed SubPlan" in l for l in outputs), plan
assert not any(re.search(r"(?<!hashed )SubPlan \d+", l) for l in outputs), plan
# Прежний тест формы плана (hashed SubPlan, а не коррелированный) убран: с SET jit TO off функция не
# разворачивается в вызывающий запрос, и EXPLAIN показывает один Function Scan. Задержка поиска в
# вариантах фильтров проверяется замером на стенде (perf/analysis/api_split.py).
# --- Поиск в svod2_lines (ускорение AURORA-1843): результат равен прежнему поиску по jsonb ---
# Поиск экрана: data без null-полей (AURORA-1848, R-30), значения значения header + contract_summary + contract_detail через jsonb_each,
# Поиск экрана: data без null-полей (AURORA-1848, R-30), значения header + contract_summary + contract_detail через jsonb_each,
# регистр svod2_casefold по каждому значению, числа через svod2_js_number_text.
REFERENCE_SEARCH_SQL = """
SELECT l.line_id
@ -267,3 +258,15 @@ def test_svod2_lines_search_extra_data_is_searchable(svod2_sql, svod2_data):
assert found("RF", "e+25") == 1 and found("RF", "-1.234e-7") == 1 # число как в JS
assert found("RF", "555.5") == 1 # ckk без contract_detail
assert found("RF", "zzzz") == 0
@pytest.mark.parametrize("fn", ["v_svod2_go", "v_svod2_rf", "v_svod2_facets"])
def test_svod2_functions_have_jit_off(svod2_sql, fn):
"""Оценка стоимости плана этих функций выше jit_above_cost: JIT добавлял ~1,4 с к каждому вызову
с буквальными параметрами (на стенде масштаба 1 - 1,5 с против 45 мс без JIT)."""
config = svod2_sql(
"SELECT p.proconfig FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace "
"WHERE n.nspname = 'v3' AND p.proname = :fn",
{"fn": fn},
)
assert config and all("jit=off" in (row[0] or []) for row in config), (fn, config)

View File

@ -708,3 +708,6 @@ PR создаются по готовности, merge — по команде
Цена ошибки: если реальная высота строки с данными отличается от 2.75rem (длинный перенос), окно страниц рассчитается неточно; в test строки так же имеют фиксированную высоту.
50. **2026-11-08 (R-30, решение пользователя 08.11; в поручении названо R-10, номер занят) — серверный поиск совпадает с тем, что показывает экран: пустое (null) поле `contract_detail` НЕ затирает такое же поле `contract_summary`.** Экран получает `data` без null-полей (AURORA-1848), и в `{...header, ...contract_summary, ...contract_detail}` побеждает последнее непустое значение. В `v3.svod2_lines` значения `counterparty`, `reference`, `deadline`, `comment` берутся как `COALESCE(cd.x, cs.x)`; оракулы (`svod2_oracle.py`, `svod2_search_oracle.mjs`, векторы `svod2_screen_vectors.json`) отбрасывают null при слиянии, `legacy_full()` снова читает ответ без параметров. Новые тесты: строка 910001 (`contract_detail.counterparty = NULL`, у `contract_summary` «ООО Ромашка») находится по «ооо ромашка», по «дс-1» не находится (непустой `reference` detail перекрывает); строка без `contract_detail` находится по значениям summary; непустое поле detail перекрывает summary.
Цена ошибки: если пользователь имел в виду иное, поиск расходится с экраном в этих строках; правка - вернуть `cd.x` вместо `COALESCE` в одном месте SQL и убрать отбрасывание null в оракулах. Замечание: R-25 про `view=full` с null остаётся в силе.
51. **2026-11-08 (O-1, исправлено) — `v3.v_svod2_facets` получила `SET jit TO off`.** Причина «6-8 с на вызов» в тестах и 1,5 с на стенде: оценка стоимости плана выше `jit_above_cost`, JIT-компиляция добавляла ~1,4 с к каждому вызову с буквальными параметрами (на стенде масштаба 1: 1500 мс с JIT против 43 мс без; generic-план клиента с параметрами JIT не включал, поэтому в API после 5-го вызова на соединении было быстро). Для `v_svod2_go/rf` `jit off` уже стоял. Тест: `test_svod2_functions_have_jit_off`. Тест формы плана `test_facets_search_subplan_is_not_correlated` убран: функция с `SET` не разворачивается, EXPLAIN показывает один Function Scan.
Цена ошибки: нет (настройка на уровне функции); без неё первые вызовы facets на каждом соединении медленные.