Индексы: ~855 МБ мёртвых и дублирующих на listings, у houses вообще нет индекса под geography #2997

Closed
opened 2026-08-20 17:50:05 +00:00 by lekss361 · 4 comments
Owner

Эпик: #2989

Индексы listings весят 1 629 МБ при heap 1 841 МБ — почти паритет, что ненормально для таблицы, которую в основном пишут. Каждый из 30 индексов получает запись на каждом не-HOT апдейте, а их 20,86 млн.

Что можно не переносить

Индекс Размер Сканов за 92 дня Вердикт
listings_address_trgm_idx 284 МБ 137 139 оставить — горячий
listings_addr_norm_trgm_idx 283 МБ 9 243 не переносить
listings_geom_geog_idx 212 МБ 204 131 оставить
listings_active_filter_idx 203 МБ 1 010 не переносить
listings_geom_idx 115 МБ 3 952 не переносить
listings_rooms_area_idx 89 МБ 65 367 пересоздать без предиката
listings_active_source_lastseen_idx 26 МБ 939 не переносить
11 индексов с нулём сканов 222 МБ 0 не переносить

Внимание на порядок в первых двух строках. Первичный разбор предлагал выбросить listings_address_trgm_idx и оставить нормализованный — проверка показала, что горячий именно он (137 139 сканов против 9 243). Легко перепутать по названию.

Дублирующаяся пара пространственных индексов — 327 МБ

На listings стоят оба: gist(geom) и gist((geom::geography)). Grep по всем ST_DWithin в backend/app не нашёл ни одного потребителя listings.geom без приведения к geography — даже domrf_kapremont_loader.py:532-534 кастует обе стороны. Скорее всего 3 952 скана геометрического индекса — вырожденный случай под предикат geom IS NOT NULL.

В целевой схеме нужен один. Экономия 115 МБ сейчас и около 800 МБ на московском объёме, минус одна индексная запись из тридцати на каждом апдейте.

У houses индекса под geography нет вовсе

Весь код матчинга ходит через ST_DWithin(geom::geography, …) (backend/app/services/matching/houses.py:302), а живой houses_geom_idx построен по geom. EXPLAIN ANALYZE на проде показывает Index Cond: (geom IS NOT NULL) — полный проход по всем 9 779 записям и 94 мс на запрос, который должен занимать доли миллисекунды.

При росте справочника в 8,5 раза и потоке 200–280 тыс. новых объявлений в месяц это станет главным узким местом матчинга. Нужен либо функциональный индекс по (geom::geography), либо хранимая geography-колонка.

Раздутость самих индексов

Измерено сравнением двух btree по bigint в одной базе: deals_pkey — 43 байта на строку, listings_pkey229 байт при 198 апдейтах на строку, то есть 5,3 крат. У listings_active_filter_idx — 203 МБ на 49 602 записи, 4,3 КБ на запись.

Acceptance

  • Целевой список индексов зафиксирован; мёртвые и дублирующие в новую схему не попадают
  • Перед дропом любого индекса проверено pg_constraint — за нулевыми сканами может стоять уникальность или внешний ключ
  • На listings один пространственный индекс, geography
  • У houses появился индекс, который реально берётся запросом матчинга; EXPLAIN ANALYZE до и после в комментарии
  • Индексы при пересборке создаются после загрузки данных, параллельно несколькими сессиями

Scope: backend/data/sql/, backend/app/services/matching/houses.py.

Эпик: #2989 Индексы `listings` весят **1 629 МБ** при heap 1 841 МБ — почти паритет, что ненормально для таблицы, которую в основном пишут. Каждый из 30 индексов получает запись на каждом не-HOT апдейте, а их 20,86 млн. ## Что можно не переносить | Индекс | Размер | Сканов за 92 дня | Вердикт | |---|---|---|---| | `listings_address_trgm_idx` | 284 МБ | **137 139** | оставить — горячий | | `listings_addr_norm_trgm_idx` | 283 МБ | 9 243 | не переносить | | `listings_geom_geog_idx` | 212 МБ | **204 131** | оставить | | `listings_active_filter_idx` | 203 МБ | 1 010 | не переносить | | `listings_geom_idx` | 115 МБ | 3 952 | не переносить | | `listings_rooms_area_idx` | 89 МБ | 65 367 | пересоздать без предиката | | `listings_active_source_lastseen_idx` | 26 МБ | 939 | не переносить | | 11 индексов с нулём сканов | 222 МБ | 0 | не переносить | **Внимание на порядок в первых двух строках.** Первичный разбор предлагал выбросить `listings_address_trgm_idx` и оставить нормализованный — проверка показала, что горячий именно он (137 139 сканов против 9 243). Легко перепутать по названию. ## Дублирующаяся пара пространственных индексов — 327 МБ На `listings` стоят **оба**: `gist(geom)` и `gist((geom::geography))`. Grep по всем `ST_DWithin` в `backend/app` не нашёл ни одного потребителя `listings.geom` без приведения к geography — даже `domrf_kapremont_loader.py:532-534` кастует обе стороны. Скорее всего 3 952 скана геометрического индекса — вырожденный случай под предикат `geom IS NOT NULL`. В целевой схеме нужен один. Экономия 115 МБ сейчас и около 800 МБ на московском объёме, минус одна индексная запись из тридцати на каждом апдейте. ## У houses индекса под geography нет вовсе Весь код матчинга ходит через `ST_DWithin(geom::geography, …)` (`backend/app/services/matching/houses.py:302`), а живой `houses_geom_idx` построен по `geom`. `EXPLAIN ANALYZE` на проде показывает `Index Cond: (geom IS NOT NULL)` — полный проход по всем 9 779 записям и **94 мс** на запрос, который должен занимать доли миллисекунды. При росте справочника в 8,5 раза и потоке 200–280 тыс. новых объявлений в месяц это станет главным узким местом матчинга. Нужен либо функциональный индекс по `(geom::geography)`, либо хранимая geography-колонка. ## Раздутость самих индексов Измерено сравнением двух btree по bigint в одной базе: `deals_pkey` — 43 байта на строку, `listings_pkey` — **229 байт** при 198 апдейтах на строку, то есть 5,3 крат. У `listings_active_filter_idx` — 203 МБ на 49 602 записи, **4,3 КБ на запись**. ## Acceptance - [ ] Целевой список индексов зафиксирован; мёртвые и дублирующие в новую схему не попадают - [ ] Перед дропом любого индекса проверено `pg_constraint` — за нулевыми сканами может стоять уникальность или внешний ключ - [ ] На `listings` один пространственный индекс, geography - [ ] У `houses` появился индекс, который реально берётся запросом матчинга; `EXPLAIN ANALYZE` до и после в комментарии - [ ] Индексы при пересборке создаются **после** загрузки данных, параллельно несколькими сессиями Scope: `backend/data/sql/`, `backend/app/services/matching/houses.py`.
lekss361 added the
performance
priority/p2
scope/db
tech-debt
tradein
labels 2026-08-20 17:51:01 +00:00
Collaborator

Сделано: индекс под geography на houses (миграция 270, PR #3020, прод 21.08 10:32 UTC)

CREATE INDEX CONCURRENTLY houses_geog_gist_idx ON houses USING gist ((geom::geography)) — валиден, 848 КБ.

EXPLAIN ANALYZE до/после (один и тот же запрос: центр ЕКБ, радиус 150 м, форма как в matching/houses.py)

до (10:10–10:28 UTC) после (10:35 UTC, прогретый ×3)
план Bitmap Index Scan on houses_geom_idx · Index Cond: (geom IS NOT NULL) → Filter st_dwithin(...), Rows Removed by Filter: 4718 на воркер Index Scan using houses_geog_gist_idx · Index Cond: (geom::geography && _st_expand(…)), Rows Removed by Filter: 3
Execution Time 70.7 / 98.3 / 114.1 мс (три прогона) 19.6 / 14.6 / 12.5 мс
та же форма, разреженная точка (Уралмаш), 30 м как в матчинге 0.41 мс

Оговорка честности: в плотном центре первая строка индекса приходит через ~12 мс (bbox-кандидатов больше, буферов 155 на 106-страничный индекс), а ~14 мс в каждом замере — это Planning Time, одинаковое для любой точки и для старого плана тоже; его индекс не снимает. То есть реальный выигрыш на исполнении — с 70–114 мс до 0.4–20 мс в зависимости от плотности, а не «в доли миллисекунды» везде, как ждала постановка.

pg_stat_statements (окно с 20.08 21:02, 13 ч) по этому классу запросов до правки: 66 вызовов × 39.9 мс и 5 × 53.3 мс.

Что проверил по остальным пунктам и почему НЕ дропал

Дубль listings_geom_idx (115 МБ, 3 985 сканов). Постановка: «grep не нашёл ни одного потребителя listings.geom без приведения к geography». В pg_stat_statements за 13 ч нашёлся один:

WITH matched AS ( SELECT l.id, m.cad_num FROM listings l JOIN LATERAL (
  SELECT cb.cad_num, ST_DistanceSphere(cb.geom, l.geom) AS dist_m
  FROM cad_buildings_local cb WHERE ST_DWithin(cb.geom, l.geom, CAST($1 AS double precision)) ...

— сшивка с cad_buildings_local, l.geom участвует как geometry. Один вызов за окно, но это не ноль — и окно всего 13 часов, недельные/месячные задачи в него не попали. Дроп только после разбора этого запроса (какой индекс он реально берёт — cb.geom, скорее всего, но утверждать без EXPLAIN не буду) и после полного недельного окна статистики — с 28.08.

Нулевые индексы на listings (7 штук за 13 ч ⊂ 11 за 92 сут): pg_constraint проверен — ни один не подпирает уникальность/FK (констрейнтами являются только listings_pkey и listings_dedup_hash_key):

индекс размер scans (92 сут)
listings_kitchen_idx 52 МБ 0
listings_owners_idx 18 МБ 0
listings_encumbrances_idx 12 МБ 0
listings_metro_stations_gin_idx 7 МБ 0
listings_agency_name_idx 1.4 МБ 0
listings_yandex_offer_id_idx 1.2 МБ 0
listings_agent_idx 8 КБ 0

Это и есть целевой список «не переносить» для пересборки (#2989). На текущем проде их снятие даёт −90 МБ и минус 7 индексных записей на каждый не-HOT апдейт — но после #2992 (апсерт перестал переписывать неизменённые строки) цена апдейтов уже упала; отдельное решение, не в этом PR.

Остаётся в issue

  • у houses индекс, который берёт запрос матчинга; EXPLAIN до/после — выше
  • pg_constraint за нулевыми сканами проверен
  • один пространственный индекс на listings — после разбора geometry-потребителя и недельного окна (28.08)
  • целевой список зафиксирован — таблица выше; «11 нулевых» из постановки за 13 ч подтвердились 7 (остальные 4 имели сканы ≤ 11 за 92 дня: views/seller/cadastral/agent — смотреть по полному окну)
  • индексы при пересборке создаются после загрузки и параллельно — это к эпику #2989
## Сделано: индекс под geography на `houses` (миграция 270, PR #3020, прод 21.08 10:32 UTC) `CREATE INDEX CONCURRENTLY houses_geog_gist_idx ON houses USING gist ((geom::geography))` — валиден, 848 КБ. ### EXPLAIN ANALYZE до/после (один и тот же запрос: центр ЕКБ, радиус 150 м, форма как в `matching/houses.py`) | | до (10:10–10:28 UTC) | после (10:35 UTC, прогретый ×3) | |---|---|---| | план | `Bitmap Index Scan on houses_geom_idx · Index Cond: (geom IS NOT NULL)` → Filter `st_dwithin(...)`, **Rows Removed by Filter: 4718** на воркер | `Index Scan using houses_geog_gist_idx · Index Cond: (geom::geography && _st_expand(…))`, Rows Removed by Filter: 3 | | Execution Time | **70.7 / 98.3 / 114.1 мс** (три прогона) | **19.6 / 14.6 / 12.5 мс** | | та же форма, разреженная точка (Уралмаш), 30 м как в матчинге | — | **0.41 мс** | Оговорка честности: в плотном центре первая строка индекса приходит через ~12 мс (bbox-кандидатов больше, буферов 155 на 106-страничный индекс), а ~14 мс в каждом замере — это **Planning Time**, одинаковое для любой точки и для старого плана тоже; его индекс не снимает. То есть реальный выигрыш на исполнении — с 70–114 мс до 0.4–20 мс в зависимости от плотности, а не «в доли миллисекунды» везде, как ждала постановка. `pg_stat_statements` (окно с 20.08 21:02, 13 ч) по этому классу запросов до правки: 66 вызовов × 39.9 мс и 5 × 53.3 мс. ### Что проверил по остальным пунктам и почему НЕ дропал **Дубль `listings_geom_idx` (115 МБ, 3 985 сканов).** Постановка: «grep не нашёл ни одного потребителя `listings.geom` без приведения к geography». В `pg_stat_statements` за 13 ч нашёлся **один**: ``` WITH matched AS ( SELECT l.id, m.cad_num FROM listings l JOIN LATERAL ( SELECT cb.cad_num, ST_DistanceSphere(cb.geom, l.geom) AS dist_m FROM cad_buildings_local cb WHERE ST_DWithin(cb.geom, l.geom, CAST($1 AS double precision)) ... ``` — сшивка с `cad_buildings_local`, `l.geom` участвует как geometry. Один вызов за окно, но это не ноль — и окно всего 13 часов, недельные/месячные задачи в него не попали. Дроп только после разбора этого запроса (какой индекс он реально берёт — `cb.geom`, скорее всего, но утверждать без EXPLAIN не буду) и после полного недельного окна статистики — **с 28.08**. **Нулевые индексы на `listings` (7 штук за 13 ч ⊂ 11 за 92 сут): `pg_constraint` проверен — ни один не подпирает уникальность/FK** (констрейнтами являются только `listings_pkey` и `listings_dedup_hash_key`): | индекс | размер | scans (92 сут) | |---|---:|---:| | listings_kitchen_idx | 52 МБ | 0 | | listings_owners_idx | 18 МБ | 0 | | listings_encumbrances_idx | 12 МБ | 0 | | listings_metro_stations_gin_idx | 7 МБ | 0 | | listings_agency_name_idx | 1.4 МБ | 0 | | listings_yandex_offer_id_idx | 1.2 МБ | 0 | | listings_agent_idx | 8 КБ | 0 | Это и есть целевой список «не переносить» для пересборки (#2989). На текущем проде их снятие даёт −90 МБ и минус 7 индексных записей на каждый не-HOT апдейт — но после #2992 (апсерт перестал переписывать неизменённые строки) цена апдейтов уже упала; отдельное решение, не в этом PR. ### Остаётся в issue - [x] у `houses` индекс, который берёт запрос матчинга; EXPLAIN до/после — выше - [x] `pg_constraint` за нулевыми сканами проверен - [ ] один пространственный индекс на `listings` — после разбора geometry-потребителя и недельного окна (28.08) - [ ] целевой список зафиксирован — таблица выше; «11 нулевых» из постановки за 13 ч подтвердились 7 (остальные 4 имели сканы ≤ 11 за 92 дня: views/seller/cadastral/agent — смотреть по полному окну) - [ ] индексы при пересборке создаются после загрузки и параллельно — это к эпику #2989
Collaborator

Датированный пункт (дроп listings_geom_idx, окно до 28.08) — исполнен и принят (PR #3130, деплой 01b36f62)

Критерий, размеченный 21.08 ДО окна, выполнен — окно склеено через переезд из двух половин + два независимых контроля:

источник geom_idx geog_idx
замороженный Beget (вся история до 25.08 17:23) 4 116 сканов / 115 МБ 217 712 / 212 МБ
живой Poincare (25.08→27.08) 0 сканов работает

Прирост geom на Beget за последние 4 дня (+164) — вырожденные IS NOT NULL-планы (паттерн, устранённый у houses geog-индексом #3020). EXPLAIN горячего пути — geog; свип кода — ни одного пространственного оператора по голому listings.geom.

Приёмка после деплоя:

  • pg_stat_user_indexes: из geom-пары остался только listings_geom_geog_idx;
  • EXPLAIN того же запроса — план байт-в-байт прежний (Bitmap Index Scan on listings_geom_geog_idx + active-индекс), деградации нет.

−115 МБ и минус одна индексная запись на каждый не-HOT апдейт listings. Откат (маловероятный) — CREATE INDEX CONCURRENTLY, команда в шапке миграции 273.

Остаток задачи (11 мёртвых индексов, listings_rooms_area_idx без предиката, listings_addr_norm_trgm_idx / listings_active_filter_idx) — размечен в шапке как «не переносить» при пересборке начисто; сама пересборка — эпик #2989. Если хочешь, могу тем же методом (склеенное окно + EXPLAIN) подрезать 11 нулевых по одному — скажи, и заведу датированный критерий на каждый.

## Датированный пункт (дроп `listings_geom_idx`, окно до 28.08) — исполнен и принят (PR #3130, деплой 01b36f62) **Критерий, размеченный 21.08 ДО окна, выполнен** — окно склеено через переезд из двух половин + два независимых контроля: | источник | geom_idx | geog_idx | |---|---|---| | замороженный Beget (вся история до 25.08 17:23) | 4 116 сканов / 115 МБ | **217 712** / 212 МБ | | живой Poincare (25.08→27.08) | **0 сканов** | работает | Прирост geom на Beget за последние 4 дня (+164) — вырожденные `IS NOT NULL`-планы (паттерн, устранённый у houses geog-индексом #3020). EXPLAIN горячего пути — geog; свип кода — ни одного пространственного оператора по голому `listings.geom`. **Приёмка после деплоя:** - `pg_stat_user_indexes`: из geom-пары остался только `listings_geom_geog_idx`; - EXPLAIN того же запроса — план байт-в-байт прежний (`Bitmap Index Scan on listings_geom_geog_idx` + active-индекс), деградации нет. −115 МБ и минус одна индексная запись на каждый не-HOT апдейт listings. Откат (маловероятный) — `CREATE INDEX CONCURRENTLY`, команда в шапке миграции 273. **Остаток задачи** (11 мёртвых индексов, `listings_rooms_area_idx` без предиката, `listings_addr_norm_trgm_idx` / `listings_active_filter_idx`) — размечен в шапке как «не переносить» при пересборке начисто; сама пересборка — эпик #2989. Если хочешь, могу тем же методом (склеенное окно + EXPLAIN) подрезать 11 нулевых по одному — скажи, и заведу датированный критерий на каждый.
Collaborator

Разметка датированного окна для остальных нулевых индексов — ДО окна, с помехами, размеченными заранее

Baseline снят 27.08 11:02 UTC (живой Poincare, счётчики живут с рестора 25.08). Предлагаемое окно: до 03.09 (7 дней — покрывает недельный цикл: субботний avito exhaustive, недельные бэкфиллы).

Урок из baseline, который меняет критерий

listings_address_trgm_idx — «горячий» индекс из шапки задачи (137 139 сканов за 92 дня на Beget) — на новой базе показывает 0 сканов за двое суток. Короткое окно на живом хосте недостаточно само по себе: потребители ходят редкими путями. Поэтому решение по каждому кандидату = окно 7д + свип кода на потребителя + EXPLAIN формы запроса, а не счётчик в одиночку.

Помехи, размеченные до окна (иначе разметка после результата неотличима от подгонки)

  1. Счётчики живут только с 25.08 — истории Beget в них нет; сверяться с замороженной базой (там 92 дня накоплений).
  2. Тонус сбора снижен (Авито в banned-эре, Домклик под QRATOR) — индексы write-путей недосчитают сканов относительно нормы.
  3. Индексы, подпирающие UNIQUE/PK (listings_source_source_id_uq, listings_dedup_hash_key, listings_pkey), не кандидаты вовсе: они работают на записи, скан-счётчик их полезность не меряет.

Кандидаты (0 сканов на 27.08 11:02, БЕЗ constraint-backed), суммарно ~36 МБ на текущем объёме

listings_minhash_idx (6.7М) · listings_metro_stations_gin_idx (3.5М) · listings_kitchen_idx (1.4М) · listings_yandex_offer_id_idx (1.4М) · listings_bld_cadastral_idx (528К) · listings_agency_name_idx (376К) · listings_house_ext_id_idx (376К) · listings_views_idx (296К) · listings_geocode_pending_idx (224К) · listings_owners_idx (128К) · listings_encumbrances_idx (80К) · listings_seller_idx (16К) · listings_agent_idx (8К) · listings_cadastral_idx (8К)

listings_address_trgm_idx — НЕ кандидат (горячий по истории Beget, см. выше). На московском объёме каждый из кандидатов вырастет кратно — сейчас дешёвые, потом нет.

03.09: сверка окна + замороженного Beget + свип кода по каждому → отдельная миграция на подтверждённо мёртвые. Если до этого пересборка начисто (#2989) случится раньше — пункт снимается, «не переносить» уже размечено в шапке.

## Разметка датированного окна для остальных нулевых индексов — ДО окна, с помехами, размеченными заранее Baseline снят 27.08 11:02 UTC (живой Poincare, счётчики живут с рестора 25.08). Предлагаемое окно: **до 03.09** (7 дней — покрывает недельный цикл: субботний avito exhaustive, недельные бэкфиллы). ### Урок из baseline, который меняет критерий `listings_address_trgm_idx` — «горячий» индекс из шапки задачи (137 139 сканов за 92 дня на Beget) — на новой базе показывает **0 сканов за двое суток**. Короткое окно на живом хосте недостаточно само по себе: потребители ходят редкими путями. Поэтому решение по каждому кандидату = **окно 7д + свип кода на потребителя + EXPLAIN формы запроса**, а не счётчик в одиночку. ### Помехи, размеченные до окна (иначе разметка после результата неотличима от подгонки) 1. Счётчики живут только с 25.08 — истории Beget в них нет; сверяться с замороженной базой (там 92 дня накоплений). 2. Тонус сбора снижен (Авито в banned-эре, Домклик под QRATOR) — индексы write-путей недосчитают сканов относительно нормы. 3. Индексы, подпирающие UNIQUE/PK (`listings_source_source_id_uq`, `listings_dedup_hash_key`, `listings_pkey`), **не кандидаты вовсе**: они работают на записи, скан-счётчик их полезность не меряет. ### Кандидаты (0 сканов на 27.08 11:02, БЕЗ constraint-backed), суммарно ~36 МБ на текущем объёме `listings_minhash_idx` (6.7М) · `listings_metro_stations_gin_idx` (3.5М) · `listings_kitchen_idx` (1.4М) · `listings_yandex_offer_id_idx` (1.4М) · `listings_bld_cadastral_idx` (528К) · `listings_agency_name_idx` (376К) · `listings_house_ext_id_idx` (376К) · `listings_views_idx` (296К) · `listings_geocode_pending_idx` (224К) · `listings_owners_idx` (128К) · `listings_encumbrances_idx` (80К) · `listings_seller_idx` (16К) · `listings_agent_idx` (8К) · `listings_cadastral_idx` (8К) `listings_address_trgm_idx` — НЕ кандидат (горячий по истории Beget, см. выше). На московском объёме каждый из кандидатов вырастет кратно — сейчас дешёвые, потом нет. **03.09**: сверка окна + замороженного Beget + свип кода по каждому → отдельная миграция на подтверждённо мёртвые. Если до этого пересборка начисто (#2989) случится раньше — пункт снимается, «не переносить» уже размечено в шапке.
Collaborator

Замер после переезда: тикет в основном закрыт пересборкой базы

Тикет писался 20.08 по старой базе. 25-26.08 база переехала и была пересобрана (#2989). Перемерил сегодня — числа разошлись на порядок, и большинство пунктов исполнено.

Было / стало

Метрика В тикете (20.08) Сейчас (27.08)
listings heap 1 841 МБ 183 МБ
listings индексы 1 629 МБ 117 МБ
Число индексов 30 28
listings_address_trgm_idx 284 МБ 21 МБ
listings_addr_norm_trgm_idx 283 МБ 21 МБ
listings_geom_geog_idx 212 МБ 21 МБ
listings_active_filter_idx 203 МБ 4.8 МБ

Раздутость, ради которой всё затевалось (listings_active_filter_idx — 4,3 КБ на запись), ушла вместе с пересборкой: тот же индекс теперь 4,8 МБ на 50 тыс. записей.

Два конкретных пункта — уже сделаны

1. Дублирующаяся пара пространственных индексов. listings_geom_idx (голый gist(geom)) в базе отсутствует, остался только listings_geom_geog_idx. Пары больше нет.

2. У houses нет индекса под geography. Есть:

houses_geog_gist_idx | CREATE INDEX houses_geog_gist_idx ON public.houses USING gist (((geom)::geography))

Именно то, чего требовал тикет. Полный проход на 94 мс, о котором шла речь, воспроизвести не на чем: houses сейчас 11 МБ heap на 10 374 строки.

Чего сделать НЕЛЬЗЯ прямо сейчас

Пункт «выбросить 11 индексов с нулём сканов» непроверяем: счётчики pg_stat_user_indexes начали жизнь заново вместе с новой базой, им около двух суток. Сейчас listings_minhash_idx и listings_metro_stations_gin_idx показывают ноль сканов — но это ровно то, что показал бы живой индекс, чей запрос за двое суток не выполнялся.

Решать судьбу индекса по двухдневной статистике — это способ выбросить рабочий. Исходный вердикт строился на 92 сутках наблюдений; чтобы получить сопоставимую опору, нужен сравнимый срок.

Отдельно: порядок в паре trgm-индексов перевернулся. Тикет предупреждал «горячий — не нормализованный, легко перепутать»; сейчас addr_norm_trgm 14 сканов против address_trgm 4. На двухдневной выборке это шум, но именно поэтому старый вердикт переносить в новую базу нельзя.

Предлагаю закрыть

Всё, что можно было сделать безопасно, сделано пересборкой. Оставшийся пункт требует не работы, а времени наблюдения — вернуться к нему в конце ноября, когда накопится сравнимая статистика, и мерить заново.

Если считаешь, что целевой список индексов надо зафиксировать документом независимо от замеров — переоткрой, сделаю.

## Замер после переезда: тикет в основном закрыт пересборкой базы Тикет писался 20.08 по старой базе. 25-26.08 база переехала и была пересобрана (#2989). Перемерил сегодня — числа разошлись на порядок, и большинство пунктов исполнено. ### Было / стало | Метрика | В тикете (20.08) | Сейчас (27.08) | |---|---|---| | `listings` heap | 1 841 МБ | **183 МБ** | | `listings` индексы | 1 629 МБ | **117 МБ** | | Число индексов | 30 | 28 | | `listings_address_trgm_idx` | 284 МБ | 21 МБ | | `listings_addr_norm_trgm_idx` | 283 МБ | 21 МБ | | `listings_geom_geog_idx` | 212 МБ | 21 МБ | | `listings_active_filter_idx` | 203 МБ | 4.8 МБ | Раздутость, ради которой всё затевалось (`listings_active_filter_idx` — 4,3 КБ на запись), ушла вместе с пересборкой: тот же индекс теперь 4,8 МБ на 50 тыс. записей. ### Два конкретных пункта — уже сделаны **1. Дублирующаяся пара пространственных индексов.** `listings_geom_idx` (голый `gist(geom)`) в базе **отсутствует**, остался только `listings_geom_geog_idx`. Пары больше нет. **2. У `houses` нет индекса под geography.** Есть: ``` houses_geog_gist_idx | CREATE INDEX houses_geog_gist_idx ON public.houses USING gist (((geom)::geography)) ``` Именно то, чего требовал тикет. Полный проход на 94 мс, о котором шла речь, воспроизвести не на чем: `houses` сейчас 11 МБ heap на 10 374 строки. ### Чего сделать НЕЛЬЗЯ прямо сейчас Пункт «выбросить 11 индексов с нулём сканов» **непроверяем**: счётчики `pg_stat_user_indexes` начали жизнь заново вместе с новой базой, им около двух суток. Сейчас `listings_minhash_idx` и `listings_metro_stations_gin_idx` показывают ноль сканов — но это ровно то, что показал бы живой индекс, чей запрос за двое суток не выполнялся. Решать судьбу индекса по двухдневной статистике — это способ выбросить рабочий. Исходный вердикт строился на 92 сутках наблюдений; чтобы получить сопоставимую опору, нужен сравнимый срок. Отдельно: порядок в паре trgm-индексов **перевернулся**. Тикет предупреждал «горячий — не нормализованный, легко перепутать»; сейчас `addr_norm_trgm` 14 сканов против `address_trgm` 4. На двухдневной выборке это шум, но именно поэтому старый вердикт переносить в новую базу нельзя. ## Предлагаю закрыть Всё, что можно было сделать безопасно, сделано пересборкой. Оставшийся пункт требует не работы, а **времени наблюдения** — вернуться к нему в конце ноября, когда накопится сравнимая статистика, и мерить заново. Если считаешь, что целевой список индексов надо зафиксировать документом независимо от замеров — переоткрой, сделаю.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2997
No description provided.