Agregace v aplikaci nad API s row-capem tiše počítá jen z prvních N řádků
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Admin dashboard zobrazuje graf návštěvnosti po dnech a top stránky. Čísla vypadají věrohodně (nejsou nulová), ale neodpovídají realitě — okno se ~1,6M záznamy ukazuje řádově stovky. Žádná chyba v lozích; bug běží měsíce bez povšimnutí, odhalí ho až porovnání s nezávislým zdrojem (GA, přímý SQL COUNT).
Root cause
Sekce “Root cause”Endpoint táhl raw řádky a agregoval v aplikaci:
// špatně: PostgREST tiše vrátí max 1000 řádků (max-rows default)const { data: rows } = await db.from('page_views') .select('created_at') .gte('created_at', thirtyDaysAgo);const dayCounts = groupByDay(rows); // agreguje jen prvních 1000 řádkůPostgREST (a řada dalších API vrstev) má implicitní limit počtu vrácených řádků a překročení nesignalizuje chybou — prostě vrátí prvních N. Aplikační agregace pak mlčky počítá ze zlomku dat. Stejná past: každý .select() bez explicitní paginace použitý jako podklad pro součty, žebříčky nebo migrační/opravné skripty.
Fix
Sekce “Fix”Agregovat v DB a přenášet jen výsledek — pro těžká okna navíc denní rollup:
-- rollup uzavřených dnů (cron, idempotentní ON CONFLICT DO UPDATE)CREATE TABLE page_views_daily (day date PRIMARY KEY, views bigint, unique_ips bigint, top_paths jsonb);
-- dnešek živě, jedna SQL funkce = žádný row-capCREATE FUNCTION page_views_live_today() RETURNS TABLE(views bigint, unique_ips bigint, top_paths jsonb) ...Endpoint pak čte pár desítek rollup řádků + jeden živý agregát. Bonus: rollup přežije retenční mazání raw tabulky.
Jak odhalit
Sekce “Jak odhalit”- Porovnat dashboard s nezávislým zdrojem (GA4, přímý
SELECT count(*)). - Grep na
select(+ následnou JS agregaci (for/reducenad výsledkem) nad tabulkami s > tisíci řádky. - V PostgREST nastavit
max-rowsvědomě a vždy používat.range()u výčtů.