Витрина поиска отдаёт четыре поля-заглушки: NULL прописан в самом определении, читателей у трёх — ноль #2857

Closed
opened 2026-08-13 06:50:14 +00:00 by bot-backend · 2 comments
Collaborator

Витрина поиска обещает четыре поля и всегда отдаёт пустоту

Найдено при разборе колонок со стопроцентным NULL (эпик #2674). В отличие от остальных, эти четыре — не потеря данных и не мёртвый писатель: они вписаны в определение самой витрины литералом.

pg_matviews.definition для listings_search_mv:

NULL::text                     AS district,
NULL::integer                  AS distance_to_metro_m,
NULL::timestamp with time zone AS last_price_change,
NULL::integer                  AS photos_count,

Это заглушки: имена колонок зарезервировали, реализацию не подключили. Витрина — 45 310 строк, все четыре колонки пусты у всех.

Кто это читает

колонка упоминаний в коде доезжает до API видно на экране
distance_to_metro_m 0 нет нет
photos_count 0 нет нет
last_price_change 0 нет нет
district 10 да нет

У первых трёх читателей нет вообще нигде — ни в бэкенде, ни на фронте.

district — отдельный случай, и он дальше всех прошёл: services/search_query.py:138 тянет его из витрины, schemas/search_response.py:44 объявляет district: str | None, то есть API его отдаёт. Всегда null.

Ловушка при подсчёте, на которой я едва не ошибся: десять упоминаний district в коде — это почти целиком другое понятие, уровень подбора аналогов (analog_tier: Literal["same_building", "micro_radius", "district", "city"]). Одноимённая строка-литерал, ничего общего с районом объявления. Считать их читателями поля было бы завышением вдесятеро.

Какой это вид нуля

Ни один из трёх привычных. Не «уже починено», не «заблокировано выше по потоку», не «неприменимо к предметной области»: район у объявления существует и вычислим, метро тоже.

Это четвёртый вид — обещание в контракте без реализации. Опаснее мёртвого кода тем, что мёртвый код не виден снаружи, а поле в схеме ответа читается как «данные есть, просто у этого объекта пусто».

Что предлагается

  • Три колонки без единого читателя — убрать из витрины. Чистое удаление, снаружи не видно.
  • district — решение: либо подключить (район вычислим из адреса и координат, механизм уже есть в геокодере), либо убрать из витрины и из схемы ответа. Оставлять как есть — значит держать в контракте поле, которое никогда не заполнится.

Правка дешёвая, но задевает контракт API, поэтому выношу решением, а не молча.

Проверять после: pg_stats.null_frac по listings_search_mv — колонок со значением 1.0 должно стать меньше на число удалённых. И ответ поиска не должен содержать удалённых ключей.

Связано: #2674 (эпик), #2855.

## Витрина поиска обещает четыре поля и всегда отдаёт пустоту Найдено при разборе колонок со стопроцентным `NULL` (эпик #2674). В отличие от остальных, эти четыре — **не потеря данных и не мёртвый писатель**: они вписаны в определение самой витрины литералом. `pg_matviews.definition` для `listings_search_mv`: ```sql NULL::text AS district, NULL::integer AS distance_to_metro_m, NULL::timestamp with time zone AS last_price_change, NULL::integer AS photos_count, ``` Это заглушки: имена колонок зарезервировали, реализацию не подключили. Витрина — 45 310 строк, все четыре колонки пусты у всех. ## Кто это читает | колонка | упоминаний в коде | доезжает до API | видно на экране | |---|---:|---|---| | `distance_to_metro_m` | **0** | нет | нет | | `photos_count` | **0** | нет | нет | | `last_price_change` | **0** | нет | нет | | `district` | 10 | **да** | **нет** | У первых трёх читателей нет **вообще нигде** — ни в бэкенде, ни на фронте. `district` — отдельный случай, и он дальше всех прошёл: `services/search_query.py:138` тянет его из витрины, `schemas/search_response.py:44` объявляет `district: str | None`, то есть API его **отдаёт**. Всегда `null`. **Ловушка при подсчёте, на которой я едва не ошибся:** десять упоминаний `district` в коде — это почти целиком **другое понятие**, уровень подбора аналогов (`analog_tier: Literal["same_building", "micro_radius", "district", "city"]`). Одноимённая строка-литерал, ничего общего с районом объявления. Считать их читателями поля было бы завышением вдесятеро. ## Какой это вид нуля Ни один из трёх привычных. Не «уже починено», не «заблокировано выше по потоку», не «неприменимо к предметной области»: район у объявления существует и вычислим, метро тоже. Это четвёртый вид — **обещание в контракте без реализации**. Опаснее мёртвого кода тем, что мёртвый код не виден снаружи, а поле в схеме ответа читается как «данные есть, просто у этого объекта пусто». ## Что предлагается - **Три колонки без единого читателя** — убрать из витрины. Чистое удаление, снаружи не видно. - **`district`** — решение: либо подключить (район вычислим из адреса и координат, механизм уже есть в геокодере), либо убрать из витрины **и из схемы ответа**. Оставлять как есть — значит держать в контракте поле, которое никогда не заполнится. Правка дешёвая, но задевает контракт API, поэтому выношу решением, а не молча. **Проверять после:** `pg_stats.null_frac` по `listings_search_mv` — колонок со значением 1.0 должно стать меньше на число удалённых. И ответ поиска не должен содержать удалённых ключей. Связано: #2674 (эпик), #2855.
Author
Collaborator

Три колонки без читателей сняты в PR #2858 (миграция 261). district не тронут — он в схеме ответа API, решение по нему остаётся за владельцем.

Что уточнилось по ходу:

  • зависимых объектов у витрины на проде ноль, но грантов на ней тоже ноль (relacl — только владелец), так что ловушка C3 здесь не срабатывает; блок восстановления ACL всё равно в миграции, потому что слепок, снятый руками сегодня, к деплою протухает молча;
  • колонок со null_frac = 1.0 в самой витрине не 41, а 5: три снимаемых, district и cadastral_number (последняя живая, просто площадки её не отдают). 41 — это по другим объектам эпика.
Три колонки без читателей сняты в PR #2858 (миграция 261). `district` не тронут — он в схеме ответа API, решение по нему остаётся за владельцем. Что уточнилось по ходу: - зависимых объектов у витрины на проде ноль, но грантов на ней тоже ноль (`relacl` — только владелец), так что ловушка C3 здесь не срабатывает; блок восстановления ACL всё равно в миграции, потому что слепок, снятый руками сегодня, к деплою протухает молча; - колонок со `null_frac = 1.0` в самой витрине не 41, а 5: три снимаемых, `district` и `cadastral_number` (последняя живая, просто площадки её не отдают). 41 — это по другим объектам эпика.
lekss361 added the
priority/p3
scope/backend
scope/db
site-finder
tech-debt
labels 2026-08-16 10:25:26 +00:00
Owner

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю.

Четыре NULL-литерала (district, distance_to_metro_m, last_price_change, photos_count) убраны из определения витрины. tests/test_2857_search_mv_placeholder_columns.py.

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю. Четыре NULL-литерала (district, distance_to_metro_m, last_price_change, photos_count) убраны из определения витрины. tests/test_2857_search_mv_placeholder_columns.py.
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#2857
No description provided.