perf(movements): считать total по базовой таблице без join (287k движений: count 1.1с→65мс)
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions

На реальном объёме перенесённого магазина список движений отдавал >2.5с из-за COUNT по
3-table join. Count теперь по stock_movements с фильтрами (join не меняет число строк).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-09 21:27:37 +05:00
parent b04d3f0d0c
commit 6a7806ff08

View file

@ -239,6 +239,16 @@ public record MovementRow(
[FromQuery] string? order = null,
CancellationToken ct = default)
{
// COUNT считаем по одной таблице (без join на products/stores) — на больших объёмах
// (сотни тысяч движений) count по 3-way join был узким местом (~1.1с vs ~65мс).
// Join не меняет число строк: у каждого движения ровно один товар и один склад.
var countQ = _db.StockMovements.AsQueryable();
if (storeId.HasValue) countQ = countQ.Where(m => m.StoreId == storeId.Value);
if (productId.HasValue) countQ = countQ.Where(m => m.ProductId == productId.Value);
if (from.HasValue) countQ = countQ.Where(m => m.OccurredAt >= from.Value);
if (to.HasValue) countQ = countQ.Where(m => m.OccurredAt < to.Value);
var total = await countQ.CountAsync(ct);
var q = from m in _db.StockMovements
join p in _db.Products on m.ProductId equals p.Id
join st in _db.Stores on m.StoreId equals st.Id
@ -248,8 +258,6 @@ public record MovementRow(
if (productId.HasValue) q = q.Where(x => x.m.ProductId == productId.Value);
if (from.HasValue) q = q.Where(x => x.m.OccurredAt >= from.Value);
if (to.HasValue) q = q.Where(x => x.m.OccurredAt < to.Value);
var total = await q.CountAsync(ct);
var desc = string.Equals(order, "desc", StringComparison.OrdinalIgnoreCase);
q = (sort, desc) switch
{