fix(tradein/search): снять STORED-generated с listings.tsv, не пересчитывать на каждом UPDATE #3007
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3007
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/listings-tsv-drop-generated"
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?
Первый из трёх шагов по write-amplification в
listings(#2992, эпик #2989). Миграция268_listings_tsv_drop_generated.sql.Что чинит
listings.tsv—GENERATED ALWAYS AS (...) STORED. Stored-generated колонка пересчитывается на каждомUPDATEстроки, независимо от того, менялись лиdescription/address. Результат — свежий inline-датум, а не переиспользованный TOAST-указатель, поэтому строка ещё и перетостивается заново: около 5 ГБ оборота TOAST за 91 день на одной этой колонке, плюс работа GIN-индекса на каждый апдейт.При 20,86 млн апдейтов за окно это заметная доля общего write-amplification, и правкой
SET-списка в апсерте она не лечится — генерация срабатывает независимо от того, что перечислено вSET.Посылка задачи не подтвердилась, и это к лучшему
Ставя задачу, я исходил из того, что сначала придётся переопределить
listings_search_mv, чтобы она считалаto_tsvectorприREFRESH, а не тянула хранимую колонку. Проверка это опровергла.search_query.py:125(tsv @@ plainto_tsquery) обращается к витрине, а не к таблице:build_search_queryсобираетSELECT ... FROM listings_search_mv. А сама витрина никогда не читалаlistings.tsv— сверено байт-в-байт сpg_get_viewdefна проде и со всеми тремя миграциями, когда-либо её создававшими (050, 094, 261;grep 'l\.tsv\b'по всем SQL даёт ноль совпадений). Витрина считает свой tsvector сама:Значит поиск развязан с
listings.tsvполностью, витрину трогать не нужно, и объём правки меньше запланированного.Индекс действительно мёртв
listings_tsv_idx, 116 МБ.idx_scan = 0, причёмpg_stat_database.stats_resetдля этой БД пуст — то есть ноль сканов за всю историю базы, а не с последнего сброса счётчиков.pg_constraint.conindidпо нему пуст — ни одинUNIQUE/PK/EXCLUDEза ним не стоит. Дропается этой же миграцией.Почему нет rewrite
ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION— операция чисто каталожная. По исходникам PostgreSQL 16 (ATExecDropExpression,tablecmds.c, REL_16_STABLE) она снимаетattgeneratedи удаляет строкуpg_attrdef, а флагtab->rewrite, по которомуATRewriteTablesрешает делать проход по страницам, не выставляется.ACCESS EXCLUSIVEдержится ровно на время правки системного каталога.Форма
DROP EXPRESSION IF EXISTSштатная и нативно идемпотентна — при повторном прогоне выдаётNOTICE, а не ошибку. Синтаксис сверен с\h ALTER TABLEна самом проде (PG 16.4).Колонку намеренно оставляем
Снимается только генерация.
DROP COLUMN— отдельная операция со своим риском, ломающая любойSELECT *поlistings, и в эту задачу не входит. После миграцииtsv— обычная замороженная tsvector-колонка без единого читателя. Поведение задокументировано черезCOMMENT ON COLUMN, чтобы следующий, кто на неё наткнётся, не принял замороженные значения за рабочие.Критерии приёмки
Записаны в шапке миграции до применения:
268_listings_tsv_drop_generated.sqlв_schema_migrationslistings.tsvсуществует,is_generated = 'NEVER'(былоALWAYS),attgenerated = ''listings_tsv_idxотсутствуетlistings_search_mvне изменилось байт-в-байт/api/v1/searchсdescription_queryвозвращает результатыlistingsзаметно ниже базовых ~5 ГБ/91 деньПроверено
scripts/check-migration-lock-timeout.py— зелёный.pytest tests/test_migration_numbering.py— номер 268 свободен относительноforgejo/main.pytest tests/test_2857_search_mv_placeholder_columns.py— витрина не тронута. Прод только читался,SELECT-ами.Refs #2992, #2989