Старый idempotency-маркер — Product с Article LIKE 'DEMO-%'. После того как «Демо-магазин» на стенде наполнили реальным каталогом из МойСклада (артикулы не DEMO-*), маркер срабатывал false и QuickLoginDemoSeeder при каждом рестарте стенда пытался залить фиктивный каталог заново — падая на unique-индексе IX_product_barcodes_OrganizationId_Code (23505). В БД ничего лишнего не попадало (транзакция откатывалась), но лог stage-api получал 9 ERR в сутки. Фикс: SeedAsync и PeekAsync считают «уже сидировано» при любом Product в орге. Поведение из UI (POST /api/admin/seed-demo) не меняется для свежих org'ов. Прочие стартовые сидеры (OpenIddictClientSeeder, SystemReferenceSeeder, DevDataSeeder) и HangfireJobsConfigurator — уже идемпотентны, аудит в docs/seeder-noise-progress.md. Integration-тест: - SeedDemo_second_call_on_populated_org_returns_already_seeded - SeedDemo_on_org_with_imported_catalog_skips_without_duplicate_barcode Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3.8 KiB
Seeder noise cleanup — прогресс
Контекст: при каждом старте food-market-stage-api в логах появлялись ERR
от QuickLoginDemoSeeder → DemoTenantSeeder.SeedAsync — повторная попытка
залить фиктивный DEMO-каталог в «Демо-магазин», падающая на unique-индексе
IX_product_barcodes_OrganizationId_Code (23505). В БД ничего лишнего не
попадало (транзакция откатывалась), но лог был зашумлён ~9 строк/сутки.
Корневая причина: idempotency-маркер DemoTenantSeeder — наличие Product
с Article LIKE 'DEMO-%'. После того как «Демо-магазин» наполнили настоящим
каталогом из МойСклада (у товаров есть MoySkladExternalId, а артикулы
не начинаются с DEMO-), маркер срабатывал false → сидер шёл заново,
генерировал новые Product'ы с теми же EAN-13 (seed 200_000_000_001…), и
упирался в unique по (OrganizationId, Code) на первом же SaveChangesAsync.
Чеклист
- 1.
DemoTenantSeeder.SeedAsyncничего не делает, если в организации уже есть любые товары (а не толькоDEMO-*). Демо-магазин с реальным каталогом из МойСклада не трогаем. - 2. Остальные стартовые сидеры/джобы проверены — нет повторных вставок.
- 3. Integration-тест: повторный запуск сидера на организации с товарами не пишет и не падает.
- 4.
~/deploy-stage.sh, логи stage-api после старта — 0 строкERR/fail.
Что меняем
- В
DemoTenantSeeder.SeedAsyncidempotency check расширяется: сейчас «уже сидировано» = Product сArticle LIKE 'DEMO-%'. Новая логика — «уже сидировано» = любой Product в орге (_db.Products.AnyAsync(p ⇒ p.OrganizationId == orgId)). При таком раскладеCountExistingAsyncвернёт реальные count'ы,AlreadySeeded = true, никаких вставок. - Поведение из UI (
POST /api/admin/seed-demo) не меняется для свежих org'ов. Для орги с товарами возвращаем summary без вставок — ровно как и раньше, только маркер стал шире. YearDemoSeederуже и так защищёнhasActivityгардом (Supply или RetailSale) — к нему претензий нет.
Какие сидеры/джобы проверены
| Hosted service | Повторные вставки при каждом старте? |
|---|---|
OpenIddictClientSeeder |
нет — проверяет FindByClientIdAsync + update |
SystemReferenceSeeder |
нет — все вставки через AnyAsync / FirstOrDefault guard |
DevDataSeeder |
нет — все вставки идемпотентны (роли/юзеры/единицы/org/stores) |
QuickLoginDemoSeeder |
один путь (TryFillCatalogAsync) чинится в задаче — он здесь корень шума |
HangfireJobsConfigurator |
нет — AddOrUpdate по cron-id |
HangfireGlobalFilterRegistrar |
нет — pure в-процесс конфиг |
InvoiceWorker |
polling-воркер, не сидер |
ReferencePriceRefreshJob |
polling-воркер, не сидер |