fix(tradein/matching): listing_sources датируется построчно, а не стартом транзакции (#2731) #2743

Merged
bot-backend merged 1 commit from fix/2731-listing-sources-clock into main 2026-08-06 16:35:30 +00:00
Collaborator

Что

Вторая половина #2731 (первая — PR #2742). _upsert_listing_source вызывается ПОСТРОЧНО из save_listings (hook _link_listing_to_house), а транзакция batch'а коммитится один раз в конце — значит NOW() == transaction_timestamp() давал одну метку на весь вызов.

Прод-замер 2026-08-06, listing_sources по часам (строк / различных меток):

11:00  219 / 1     10:00  235 / 1     09:00  297 / 1
08:00  150 / 1     07:00  148 / 1     06:00  130 / 1

last_seen_at и last_scraped_at схлопнуты одинаково и равны друг другу у 100% строк.

Почему это НЕ отдельная правка, а обязательное продолжение #2742

Сегодня listings.last_seen_at = listing_sources.last_seen_at у 2407 пар из 2407 за сутки — ровно потому, что обе колонки берут одну транзакционную метку. Если оставить эту половину на NOW(), то после #2742 первая колонка станет построчной, а вторая останется замороженной на старте batch'а: расхождение выросло бы с миллисекунд (честная разница двух соседних записей) до длительности прогона — то есть на месте одного дефекта появился бы другой.

Потребителя, сравнивающего эти две колонки между таблицами, в коде нет (проверено grep'ом) — но систематически смещённая колонка хуже честно разъехавшейся на миллисекунды.

Согласованность внутри строки

matched_at / last_seen_at / last_scraped_at пишутся ОДНИМ statement'ом → statement_timestamp() даёт им одинаковое значение, их равенство сохранено. clock_timestamp() развёл бы их на микросекунды (прод: clock_timestamp() = clock_timestamp() → false).

Test plan

  • tests/test_2731_listing_sources_timestamps.py — писатель, отработавший дольше секунды, оставляет РАЗЛИЧНЫЕ метки; три отметки одного statement'а совпадают
  • Фальсификация: на старом коде (NOW()) тест на различие меток падает (все три метки равны 0.0)
  • Полный прогон backend-сьюта: 3860 passed, 10 skipped
  • Мержить ПОСЛЕ #2742 и с паузой (rapid-merge trap: два tradein-мержа подряд → test-job cancelled → деплой на старом образе)

Refs #2731

## Что Вторая половина #2731 (первая — PR #2742). `_upsert_listing_source` вызывается ПОСТРОЧНО из `save_listings` (hook `_link_listing_to_house`), а транзакция batch'а коммитится один раз в конце — значит `NOW()` == `transaction_timestamp()` давал одну метку на весь вызов. Прод-замер 2026-08-06, `listing_sources` по часам (строк / различных меток): ``` 11:00 219 / 1 10:00 235 / 1 09:00 297 / 1 08:00 150 / 1 07:00 148 / 1 06:00 130 / 1 ``` `last_seen_at` и `last_scraped_at` схлопнуты одинаково и равны друг другу у 100% строк. ## Почему это НЕ отдельная правка, а обязательное продолжение #2742 Сегодня `listings.last_seen_at = listing_sources.last_seen_at` у **2407 пар из 2407** за сутки — ровно потому, что обе колонки берут одну транзакционную метку. Если оставить эту половину на `NOW()`, то после #2742 первая колонка станет построчной, а вторая останется замороженной на старте batch'а: расхождение выросло бы с миллисекунд (честная разница двух соседних записей) до длительности прогона — то есть на месте одного дефекта появился бы другой. Потребителя, сравнивающего эти две колонки между таблицами, в коде нет (проверено grep'ом) — но систематически смещённая колонка хуже честно разъехавшейся на миллисекунды. ## Согласованность внутри строки `matched_at` / `last_seen_at` / `last_scraped_at` пишутся ОДНИМ statement'ом → `statement_timestamp()` даёт им одинаковое значение, их равенство сохранено. `clock_timestamp()` развёл бы их на микросекунды (прод: `clock_timestamp() = clock_timestamp()` → false). ## Test plan - [x] `tests/test_2731_listing_sources_timestamps.py` — писатель, отработавший дольше секунды, оставляет РАЗЛИЧНЫЕ метки; три отметки одного statement'а совпадают - [x] Фальсификация: на старом коде (`NOW()`) тест на различие меток падает (все три метки равны 0.0) - [x] Полный прогон backend-сьюта: 3860 passed, 10 skipped - [ ] Мержить ПОСЛЕ #2742 и с паузой (rapid-merge trap: два tradein-мержа подряд → `test`-job cancelled → деплой на старом образе) Refs #2731
bot-backend added 1 commit 2026-08-06 16:18:17 +00:00
fix(tradein/matching): listing_sources датируется построчно, а не стартом транзакции (#2731)
All checks were successful
CI / changes (pull_request) Successful in 14s
CI Trade-In / changes (pull_request) Successful in 14s
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m53s
20b777f0b9
Вторая половина #2731. `_upsert_listing_source` вызывается ПОСТРОЧНО из save_listings
(hook _link_listing_to_house), транзакция batch'а коммитится один раз в конце, поэтому
NOW() == transaction_timestamp() давал одну метку на весь вызов: прод 2026-08-06 по
часам — 219 строк / 1 метка, 235/1, 297/1, 150/1 и так каждый час.

Чинится вместе с listings.scraped_at/last_seen_at, а не отдельно. Сегодня
listings.last_seen_at = listing_sources.last_seen_at у 100% пар (2407 из 2407 за сутки)
именно потому, что обе берут одну транзакционную метку. Починить только listings —
значит оставить вторую замороженной на старте batch'а и вырастить расхождение с
миллисекунд (честная разница двух соседних записей) до длительности прогона.

matched_at / last_seen_at / last_scraped_at пишутся ОДНИМ statement'ом, поэтому
statement_timestamp() даёт им одинаковое значение — их равенство сохраняется.
clock_timestamp() развёл бы их на микросекунды.

Refs #2731
bot-backend merged commit 9f51c98ff4 into main 2026-08-06 16:35:30 +00:00
bot-backend deleted branch fix/2731-listing-sources-clock 2026-08-06 16:35:30 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#2743
No description provided.