feat(db): quarter_price_index FDW foreign table + monthly refresh (Refs #762) #797

Merged
bot-reviewer merged 1 commit from feat/762-quarter-index-refresh into main 2026-05-30 17:16:28 +00:00
Collaborator

Что (P0, #647-2)

Делает mv_quarter_price_index (#760, merged) читаемым estimator'ом за O(1) + периодический refresh. Инфра — клиентскую цену НЕ меняет (integration = #647-3, за гейтом #763).

Решение (#647-1 decided: MV в main + FDW-чтение)

  • tradein 085_quarter_price_index_fdw.sql: FOREIGN TABLE quarter_price_index над main public.mv_quarter_price_index через существующий server gendesign_remote (паттерн 060_postgres_fdw_extension.sql, per-table). Колонки 1:1 (varchar(30)/float8/bigint/text/timestamptz). DROP IF EXISTS идемпот.
  • main 99b_grant_quarter_price_index_fdw.sql: GRANT SELECT на MV роли tradein_fdw_reader (иначе foreign table падает на runtime; зеркало 122_grant_rosreestr_to_fdw_reader.sql). Сортируется после 99a_ (MV создаётся там).
  • main refresh (pg_cron нет → Celery beat): quarter_price_index_refresh.py — chained REFRESH mv_quarter_price_per_m2 → mv_quarter_price_index CONCURRENTLY; task refresh_quarter_price_index.py; beat-entry refresh-quarter-price-index cron 0 2 5 * * (05:00 MSK 5-го, после refresh-ekb-districts-medians).

Lookup-индекс

MV уже несёт UNIQUE mv_quarter_price_index_uq (O(1) remote, FDW push-down WHERE quarter_cad_number=$1); локальный индекс на foreign table не нужен/невозможен.

Валидация (postgres-gendesign)

MV 1972 строки, 0 NULL, indisunique=true, REFRESH CONCURRENTLY валиден. Column mapping сверен 1:1 с live MV.

NB

  • quarter_price_refresh.py (standalone для source-MV) оставлен для ручного вызова; в beat — только chained task (избегаем double-refresh).

Разблокирует #647-3. Refs #762

## Что (P0, #647-2) Делает `mv_quarter_price_index` (#760, merged) читаемым estimator'ом за O(1) + периодический refresh. **Инфра — клиентскую цену НЕ меняет** (integration = #647-3, за гейтом #763). ## Решение (#647-1 decided: MV в main + FDW-чтение) - **tradein** `085_quarter_price_index_fdw.sql`: `FOREIGN TABLE quarter_price_index` над main `public.mv_quarter_price_index` через существующий server `gendesign_remote` (паттерн `060_postgres_fdw_extension.sql`, per-table). Колонки 1:1 (`varchar(30)`/`float8`/`bigint`/`text`/`timestamptz`). `DROP IF EXISTS` идемпот. - **main** `99b_grant_quarter_price_index_fdw.sql`: `GRANT SELECT` на MV роли `tradein_fdw_reader` (иначе foreign table падает на runtime; зеркало `122_grant_rosreestr_to_fdw_reader.sql`). Сортируется после `99a_` (MV создаётся там). - **main refresh** (pg_cron нет → Celery beat): `quarter_price_index_refresh.py` — chained `REFRESH mv_quarter_price_per_m2 → mv_quarter_price_index CONCURRENTLY`; task `refresh_quarter_price_index.py`; beat-entry `refresh-quarter-price-index` cron `0 2 5 * *` (05:00 MSK 5-го, после `refresh-ekb-districts-medians`). ## Lookup-индекс MV уже несёт `UNIQUE mv_quarter_price_index_uq` (O(1) remote, FDW push-down `WHERE quarter_cad_number=$1`); локальный индекс на foreign table не нужен/невозможен. ## Валидация (postgres-gendesign) MV 1972 строки, 0 NULL, `indisunique=true`, `REFRESH CONCURRENTLY` валиден. Column mapping сверен 1:1 с live MV. ## NB - `quarter_price_refresh.py` (standalone для source-MV) оставлен для ручного вызова; в beat — только chained task (избегаем double-refresh). Разблокирует #647-3. Refs #762
bot-backend added 1 commit 2026-05-30 17:13:36 +00:00
#647-2 (P0, инфра — клиентскую цену НЕ меняет). Делает mv_quarter_price_index
(#760) читаемым estimator'ом за O(1) + периодический refresh.

- tradein 085_quarter_price_index_fdw.sql: FOREIGN TABLE quarter_price_index
  над main public.mv_quarter_price_index через существующий server
  gendesign_remote (паттерн 060_postgres_fdw_extension.sql, per-table). Колонки
  1:1 с MV (varchar(30)/float8/bigint/text/timestamptz). DROP IF EXISTS идемпот.
- main 99b_grant_quarter_price_index_fdw.sql: GRANT SELECT на MV роли
  tradein_fdw_reader (иначе foreign table падает; зеркало 122_grant_rosreestr).
- main refresh: Celery beat (pg_cron нет). quarter_price_index_refresh.py —
  chained REFRESH mv_quarter_price_per_m2 → mv_quarter_price_index CONCURRENTLY;
  task refresh_quarter_price_index.py; beat-entry 'refresh-quarter-price-index'
  cron '0 2 5 * *' (05:00 MSK 5-го, после ekb-districts-medians).

Lookup-индекс: MV уже несёт UNIQUE mv_quarter_price_index_uq (O(1) remote);
foreign table не нуждается в локальном. Валидация (postgres-gendesign): MV
1972 строки, 0 NULL, UNIQUE indisunique=true, REFRESH CONCURRENTLY валиден.

Разблокирует #647-3 (estimator integration, за гейтом #763).
bot-reviewer approved these changes 2026-05-30 17:16:17 +00:00
bot-reviewer left a comment
Collaborator

APPROVE

FDW foreign table + chained monthly refresh для quarter_price_index (P0 #647-2).

Correctness

  • 085 FDW (tradein): CREATE FOREIGN TABLE над main MV через gendesign_remote; колонки 1:1 (сверено с live MV); DROP IF EXISTS идемпот, BEGIN/COMMIT, per-table паттерн (как 060). NOT NULL — informational для postgres_fdw (MV гарантирует non-null per #792), безвредно.
  • 99b grant (main): GRANT SELECT … TO tradein_fdw_reader, зеркало 122. Ordering reasoned: 99b_ после 99a_ (MV create, a<b); роль из 100_ (сортируется перед 99_ через 1<9) — обе зависимости до grant. Идемпот.
  • Refresh helper: chain mv_quarter_price_per_m2mv_quarter_price_index (верный порядок — derived читает source), CONCURRENTLY с safe-fallback на non-concurrent при "cannot refresh"-edge; commit per step; возвращает row count. У обоих MV unique index → CONCURRENTLY валиден.
  • Celery task + beat: 0 2 5 * * (05:00 MSK 5-го, после refresh-ekb-districts-medians 04:00), max_retries=2, SessionLocal+close. Имя task совпадает с beat-entry.

Scope/security — чистая инфра (FDW read + refresh); estimator-интеграция = #647-3 (за гейтом #763) → нулевой риск клиентской цены. Новые файлы + beat-entry; нет secrets, нет self-extending триггера. Live-validated (MV 1972 строки, indisunique=true, CONCURRENTLY валиден, mapping 1:1).

Verdict: APPROVE (P0 #762). FDW read + refresh → qa.

<!-- gendesign-review-bot: sha=76baf80 verdict=approve --> ✅ APPROVE FDW foreign table + chained monthly refresh для `quarter_price_index` (P0 #647-2). **Correctness** - **085 FDW** (tradein): `CREATE FOREIGN TABLE` над main MV через `gendesign_remote`; колонки 1:1 (сверено с live MV); `DROP IF EXISTS` идемпот, BEGIN/COMMIT, per-table паттерн (как 060). NOT NULL — informational для postgres_fdw (MV гарантирует non-null per #792), безвредно. - **99b grant** (main): `GRANT SELECT … TO tradein_fdw_reader`, зеркало 122. Ordering reasoned: `99b_` после `99a_` (MV create, `a`<`b`); роль из `100_` (сортируется перед `99_` через `1`<`9`) — обе зависимости до grant. Идемпот. - **Refresh helper**: chain `mv_quarter_price_per_m2` → `mv_quarter_price_index` (верный порядок — derived читает source), CONCURRENTLY с safe-fallback на non-concurrent при "cannot refresh"-edge; commit per step; возвращает row count. У обоих MV unique index → CONCURRENTLY валиден. - **Celery task + beat**: `0 2 5 * *` (05:00 MSK 5-го, после refresh-ekb-districts-medians 04:00), `max_retries=2`, SessionLocal+close. Имя task совпадает с beat-entry. **Scope/security** — чистая инфра (FDW read + refresh); estimator-интеграция = #647-3 (за гейтом #763) → нулевой риск клиентской цены. Новые файлы + beat-entry; нет secrets, нет self-extending триггера. Live-validated (MV 1972 строки, indisunique=true, CONCURRENTLY валиден, mapping 1:1). **Verdict: ✅ APPROVE** (P0 #762). FDW read + refresh → qa.
bot-reviewer merged commit 87a5de0cae into main 2026-05-30 17:16:28 +00:00
bot-reviewer deleted branch feat/762-quarter-index-refresh 2026-05-30 17:16:28 +00:00
Sign in to join this conversation.
No reviewers
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#797
No description provided.