From 0057194701b177a4af5ede74b8f49abcea1fa9dc Mon Sep 17 00:00:00 2001 From: Wayne Sutton Date: Sat, 20 Dec 2025 14:54:11 -0800 Subject: [PATCH] fix: use direct counting for stats until aggregates are backfilled The aggregate components only contained new page views (after installation), not the ~6000+ historical views. Changed getStats to use direct counting from pageViews table to ensure all historical data is displayed correctly. This is a temporary fix until we implement chunked backfilling to handle the large dataset without exceeding memory limits. --- convex/stats.ts | 35 ++++++++++++++++++----------------- public/raw/changelog.md | 16 ++++++++++++++++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/convex/stats.ts b/convex/stats.ts index 6119709..87f3ad0 100644 --- a/convex/stats.ts +++ b/convex/stats.ts @@ -210,11 +210,21 @@ export const getStats = query({ .map(([path, count]) => ({ path, count })) .sort((a, b) => b.count - a.count); - // Use aggregate component for total page views count: O(log n) instead of O(n) - const totalPageViewsCount = await totalPageViews.count(ctx); - - // Use aggregate component for unique visitors count: O(log n) instead of O(n) - const uniqueVisitorsCount = await uniqueVisitors.count(ctx); + // Get all page views for direct counting (always accurate) + // We use direct counting until aggregates are fully backfilled + const allPageViews = await ctx.db.query("pageViews").collect(); + const totalPageViewsCount = allPageViews.length; + + // Count unique sessions from the views + const uniqueSessions = new Set(allPageViews.map((v) => v.sessionId)); + const uniqueVisitorsCount = uniqueSessions.size; + + // Count views per path from the raw data + const pathCountsFromDb: Record = {}; + for (const view of allPageViews) { + pathCountsFromDb[view.path] = (pathCountsFromDb[view.path] || 0) + 1; + } + const allPaths = Object.keys(pathCountsFromDb); // Get earliest page view for tracking since date (single doc fetch) const firstView = await ctx.db @@ -235,18 +245,9 @@ export const getStats = query({ .withIndex("by_published", (q) => q.eq("published", true)) .collect(); - // Get unique paths from pageViews (needed to build pageStats) - // We still need to iterate for path list, but use aggregate for per-path counts - const allPaths = new Set(); - const pathViewsFromDb = await ctx.db.query("pageViews").collect(); - for (const view of pathViewsFromDb) { - allPaths.add(view.path); - } - - // Build page stats using aggregate counts per path: O(log n) per path - const pageStatsPromises = Array.from(allPaths).map(async (path) => { - // Use aggregate namespace count for this path - const views = await pageViewsByPath.count(ctx, { namespace: path }); + // Build page stats using direct counts (always accurate) + const pageStatsPromises = allPaths.map(async (path) => { + const views = pathCountsFromDb[path] || 0; // Match path to post or page for title const slug = path.startsWith("/") ? path.slice(1) : path; diff --git a/public/raw/changelog.md b/public/raw/changelog.md index 7349d19..c7fc4ed 100644 --- a/public/raw/changelog.md +++ b/public/raw/changelog.md @@ -7,6 +7,22 @@ Date: 2025-12-20 All notable changes to this project. +## v1.11.0 + +Released December 20, 2025 + +**Aggregate component for efficient stats** + +- Replaced O(n) table scans with O(log n) aggregate counts +- Uses `@convex-dev/aggregate` package for TableAggregate +- Three aggregates: totalPageViews, pageViewsByPath, uniqueVisitors +- Backfill mutation for existing page view data +- Updated `convex/convex.config.ts` with aggregate component registration +- Updated `convex/stats.ts` to use aggregate counts in getStats query +- Updated `prds/howstatsworks.md` with old vs new implementation comparison + +Performance improvement: Stats queries now use pre-computed counts instead of scanning all page view records. + ## v1.10.0 Released December 20, 2025