fix(ptica): фильтр класса в velocity ссылался на алиас, которого нет в CTE (#2464-G) #2865
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2865
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2464-velocity-class-filter"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что
compute_velocityсобирает фильтр класса какAND COALESCE(o.obj_class, o.obj_class_fallback) = :obj_classи подставляет еговнутрь CTE
latest_obj, гдеFROM domrf_kn_objects— без алиаса. Алиасoпоявляется только во внешнем
SELECT ... FROM latest_obj o.Прод-EXPLAIN 13.08:
Насколько это горит
Не горит. Ветка мёртвая: единственный вызывающий —
analyze_parcel(
parcels.py:3797) —obj_classне передаёт, значитclass_filterвсегда пустойи в прод уходит валидный запрос. Так что это не «ломает сейчас», а «сломается
у первого, кто включит».
И сломается тихо: исключение ловит
except Exceptionвнутриcompute_velocity,функция возвращает
None, вызывающий получаетvelocity_data = None— блок темпапродаж просто исчезает из отчёта, оставив одну строку в логе.
Как
AND COALESCE(obj_class, obj_class_fallback) = :obj_class.Колонки внутри CTE неквалифицированы,
domrf_kn_objects— единственный FROM._COMPETITORS_SQL_TMPLс{class_filter}—ровно затем, чтобы тест мог прогнать EXPLAIN по обеим подстановкам, а не
только по той, что сегодня исполняется. Шаблон байт-в-байт прежний.
Проверка — реальная, не текстовая
tests/integration/test_analyze_parcels_sql.pyделает EXPLAIN против живой прод-схемы.Прогнал через SSH-туннель с
TEST_DATABASE_URL, обе стороны:То есть падает именно новая параметризация
with_class_filter, а три контрольныхпроверки зелёные с обеих сторон.
⚠️ В CI этот тест скипается —
requires_test_dbбезTEST_DATABASE_URL. Прогонвыше сделан вручную против прод-PG; в пайплайне регрессию поймать нечем, пока
у CI нет базы.
pytest tests/test_velocity.py tests/integration/test_analyze_parcels_sql.py— 29 passed, 4 skippedpytest -m integrationчерез туннель — 4 passedruff check— cleanПобочная находка, оставлена как комментарий
Правка делает ветку работоспособной — и первый, кто ей воспользуется, упрётся
в следующую ловушку: сравнение точное и регистрозависимое, а в проде классы
с заглавной и словарь шире ожидаемого (замер 13.08,
region_cd = 66):Менять сравнение не стал — вызывающих нет, и любое «улучшение» было бы догадкой
о намерении. Написал это прямо в коде рядом с фильтром.
Шум в диффе
Один хунк в тесте — не мой: pre-commit пинит ruff v0.7.4, в
backend/.venv0.15.12(#2864).
Refs #2464
Первый прогон
backend-testsпокрасился, но не на тестах. Достал лог задания(
actions/runs/7493/jobs/1):То есть шаг с pytest прошёл целиком, гейт покрытия пройден, а красным job стал
после него — в одном из двух
if: always()-шагов (Coverage summary/Снести тестовый Postgres), причём ни один из них не напечатал ни строки.Локально полный сьют на этой же ветке: 4622 passed, 48 skipped.
Что я НЕ утверждаю: что это «просто флак». Причина шага не видна, вывода нет.
Что известно точно — падение не в тестах и не в покрытии. Перезапускаю прогон
(close/reopen), чтобы отличить воспроизводимое от разового. Если покраснеет так же —
заведу отдельную задачу на этот участок пайплайна.
Соседний сигнал: на PR #2868 в то же окно упал
openapi-codegen-check(2m20s, наустановке). Проверил отдельно — схема
app.openapi()на той ветке байт-в-байтсовпадает с main-эквивалентом, то есть правка тут ни при чём. Три прогона CI и сборка
деплоя одновременно на трёх раннерах одного VPS — похоже на нехватку ресурсов, но это
гипотеза, а не диагноз.
Причина найдена, и это не «флак» и не нехватка ЦПУ, как я предположил выше.
Диск на VPS был заполнен на 94%. Лог упавшего деплоя main:
Один дефицит объясняет все три сегодняшних покраснения: этот
backend-tests(упал ПОСЛЕуспешных тестов, в шаге без вывода),
openapi-codegen-checkна #2868 и обе сборки деплоя.Накопитель — buildx-билдеры: каждый прогон CI создаёт новый и не убирает, на хосте их
скопилось 20 штук возрастом до двух месяцев, суммарно ~19 ГБ в
_state-томах.Вычистил кэш в восьми старых (
buildctl prune --all, только регенерируемый кэш, ничегоне удалял): 132G → 117G использовано, свободно 13G → 28G. Разбор и план корневой
починки — #2869.
Прогон перезапущен уже на разгруженном хосте.
Поправка к предыдущему комментарию: дефицит диска эту красноту не объясняет.
Перезапуск прошёл уже на разгруженном хосте (28 ГБ свободно) и упал точно так же:
Диск объяснил падение деплоя и
openapi-codegen-check— это подтвердилось (после чисткиоба зелёные). А вот про этот job я поспешил и записал его в ту же корзину. Он
воспроизводится.
Что проверил дальше и что этим исключил:
базой: ветка отрезана от старого main, до мержа #2863, поэтому в ней нет
test_price_avg_has_sanity_boundsи есть мои два новых (скипаются без БД).Сверил
pytest --collect-onlyна обеих ветках — расхождение ровно эти три теста,ничего не исчезло.
vps-runner-2в 11:38 отработал зелёным (run 7491).прогоном: там после pytest идут
Post Cache uv packages/Post Set up Python,здесь — сразу
Skipping ... due to 'success()', то есть job уже НЕ success к моментуpost-фазы. Оба шага
if: always()в обоих прогонах не печатают ничего, так что пологу их различить нельзя — act не выводит баннеры для обычных
run:-шагов.Итог: падение после успешного pytest, в невидимом шаге, воспроизводимо на этой ветке
и не воспроизводится на трёх других за то же окно. Обновил ветку от base (в ней теперь
все три сегодняшних мержа) — если покраснеет и на свежей базе, заведу отдельную задачу
на диагностируемость этого участка: сейчас шаги
if: always()молчат, и отличить«упал шаг покрытия» от «упала уборка контейнера» нечем.