perf: otimizar dashboard com indexes, cache e consolidação de queries (v1.3.0)

- Adicionar indexes compostos em lancamentos para queries frequentes
- Eliminar ~20 JOINs com pagadores via helper cacheado getAdminPagadorId()
- Consolidar queries: income-expense-balance (12→1), payment-status (2→1), categories (4→2)
- Adicionar cache cross-request via unstable_cache com tag-based invalidation
- Limitar scan de métricas a 24 meses
- Deduplicar auth session por request via React.cache()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-02-06 12:24:15 +00:00
parent 21fac52e28
commit 6f5c41a4cf
45 changed files with 3589 additions and 1219 deletions

View File

@@ -2,6 +2,7 @@ import {
and,
asc,
eq,
gte,
ilike,
isNull,
lte,
@@ -10,15 +11,16 @@ import {
or,
sum,
} from "drizzle-orm";
import { contas, lancamentos, pagadores } from "@/db/schema";
import { contas, lancamentos } from "@/db/schema";
import {
ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
INITIAL_BALANCE_NOTE,
} from "@/lib/accounts/constants";
import { db } from "@/lib/db";
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
import { getAdminPagadorId } from "@/lib/pagadores/get-admin-id";
import { safeToNumber } from "@/lib/utils/number";
import {
addMonthsToPeriod,
buildPeriodRange,
comparePeriods,
getPreviousPeriod,
@@ -80,6 +82,21 @@ export async function fetchDashboardCardMetrics(
): Promise<DashboardCardMetrics> {
const previousPeriod = getPreviousPeriod(period);
const adminPagadorId = await getAdminPagadorId(userId);
if (!adminPagadorId) {
return {
period,
previousPeriod,
receitas: { current: 0, previous: 0 },
despesas: { current: 0, previous: 0 },
balanco: { current: 0, previous: 0 },
previsto: { current: 0, previous: 0 },
};
}
// Limitar scan histórico a 24 meses para evitar scans progressivamente mais lentos
const startPeriod = addMonthsToPeriod(period, -24);
const rows = await db
.select({
period: lancamentos.period,
@@ -87,13 +104,13 @@ export async function fetchDashboardCardMetrics(
totalAmount: sum(lancamentos.amount).as("total"),
})
.from(lancamentos)
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
.leftJoin(contas, eq(lancamentos.contaId, contas.id))
.where(
and(
eq(lancamentos.userId, userId),
eq(lancamentos.pagadorId, adminPagadorId),
gte(lancamentos.period, startPeriod),
lte(lancamentos.period, period),
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
ne(lancamentos.transactionType, TRANSFERENCIA),
or(
isNull(lancamentos.note),
@@ -129,12 +146,12 @@ export async function fetchDashboardCardMetrics(
const earliestPeriod =
periodTotals.size > 0 ? Array.from(periodTotals.keys()).sort()[0] : period;
const startPeriod =
const startRangePeriod =
comparePeriods(earliestPeriod, previousPeriod) <= 0
? earliestPeriod
: previousPeriod;
const periodRange = buildPeriodRange(startPeriod, period);
const periodRange = buildPeriodRange(startRangePeriod, period);
const forecastByPeriod = new Map<string, number>();
let runningForecast = 0;