Skip to content

Fix stalled MAU and bandwidth dashboard refresh#1856

Merged
riderx merged 4 commits into
mainfrom
codex/fix-dashboard-stats-cron-activity-v2
Mar 25, 2026
Merged

Fix stalled MAU and bandwidth dashboard refresh#1856
riderx merged 4 commits into
mainfrom
codex/fix-dashboard-stats-cron-activity-v2

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 25, 2026

Summary

  • queue cron_stat_app directly from live MAU and bandwidth events so active apps keep refreshing dashboard usage
  • add a single follow-up migration on top of main that hardens cron queue locking and excludes deleted apps from the periodic refresh sweep
  • keep the regression coverage for apps with fresh usage but no recent daily_mau, plus a test that /stats only leaves one pending refresh job per app

Root cause

process_cron_stats_jobs() only re-enqueued apps that had a recent daily_mau row or a recently created version. After MAU changed to count first-seen devices once per billing period, active apps could stop producing fresh daily_mau rows even while still generating bandwidth and stats activity. Once that happened, cron_stat_app stopped running for those apps, so daily_bandwidth and dashboard usage charts stopped updating.

Migration note

This PR intentionally uses one new migration file only: 20260325043000_harden_cron_stats_queue_followup.sql. It does not rewrite the already-pushed 20260324181219_fix_process_cron_stats_activity.sql migration that now exists on main.

Testing

  • bun run supabase:db:reset
  • bun scripts/supabase-worktree.ts db lint -s public --fail-on warning
  • bunx eslint supabase/functions/_backend/utils/stats.ts tests/process-cron-stats-jobs.test.ts
  • bun run typecheck
  • bun run supabase:with-env -- bunx vitest run tests/process-cron-stats-jobs.test.ts tests/cron_stat_app.test.ts tests/cron_stat_integration.test.ts

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability and deduplication of scheduled statistics jobs to prevent duplicate processing.
  • Chores

    • Hardened scheduling and security around cron/stat job enqueueing and processing.
    • Optimized concurrent tracking of bandwidth and cron-stat tasks for faster completion.
    • Cloudflare worker startup now recognizes and validates additional DB URL environment variables.
  • Tests

    • Updated tests to use endpoint helpers and run concurrently for faster, more robust test runs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Schedules cron-enqueue and bandwidth-tracking tasks concurrently in the TypeScript backend; adds/overwrites PL/pgSQL functions to dedupe and batch enqueue cron_stat_app jobs with advisory locks; updates tests to use getEndpointUrl() and run two cases concurrently.

Changes

Cohort / File(s) Summary
TypeScript scheduling
supabase/functions/_backend/utils/stats.ts
Refactored createStatsBandwidth to precompute a trackingJob promise and run enqueue + tracking in parallel via Promise.all, returning undefined.
DB cron-stat functions / migration
supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql
Adds/updates queue_cron_stat_app_for_app(p_app_id, p_org_id) (SECURITY DEFINER, empty search_path) to resolve owner org, acquire/release session advisory locks, and dedupe pgmq.send for cron_stat_app. Adds process_cron_stats_jobs() to scan active apps (30-day window across app_versions, daily_mau, device_usage, bandwidth_usage) and call the enqueue function. Sets ownership and grants for service_role.
Tests
tests/process-cron-stats-jobs.test.ts
Replaced hardcoded plugin URL with getEndpointUrl('/stats') and converted two tests to it.concurrent(...) to allow parallel execution.
Dev tooling script
scripts/start-cloudflare-workers.sh
Extracts DB_URL from supabase status -o env, adds MAIN_SUPABASE_DB_URL/SUPABASE_DB_URL env handling and validation, and includes them when generating the temporary env file.

Sequence Diagram(s)

sequenceDiagram
    participant TS as createStatsBandwidth (TS)
    participant BG as backgroundTask runner
    participant Q as queueCronStatApp job
    participant T as bandwidth tracking

    TS->>BG: backgroundTask(async task)
    BG->>Q: start queue cron stat promise
    BG->>T: start trackingJob promise
    BG->>BG: await Promise.all([Q, T])
    BG-->>TS: resolve (undefined)
Loading
sequenceDiagram
    participant Proc as process_cron_stats_jobs()
    participant Sources as usage tables (app_versions/daily_mau/device/bandwidth)
    participant Enq as queue_cron_stat_app_for_app()
    participant Lock as advisory lock
    participant Queue as pgmq.q_cron_stat_app

    Proc->>Sources: scan active app_ids (30-day window)
    Proc->>Enq: call per app_id with owner_org
    Enq->>Lock: acquire session advisory lock (from p_app_id)
    Enq->>Queue: check for existing queued payload with appId
    alt no existing job
        Enq->>Queue: pgmq.send(cron_stat_app payload)
    end
    Enq->>Lock: release advisory lock
    Enq-->>Proc: return
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code with nimble paws,
Locks held tight without a pause,
Jobs now dance in parallel light,
Queues deduped, everything takes flight —
Tiny rabbit, big deploy applause! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main issue: fixing stalled MAU and bandwidth dashboard refresh, which aligns with the primary objective of queuing cron_stat_app jobs from live events.
Description check ✅ Passed The description includes a summary of changes, root cause analysis, migration notes, and comprehensive testing steps. However, it lacks explicit test plan formatting and screenshots section as specified in the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-dashboard-stats-cron-activity-v2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 SQLFluff (4.0.4)
supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql

User Error: No dialect was specified. You must configure a dialect or specify one on the command line using --dialect after the command. Available dialects:
ansi, athena, bigquery, clickhouse, databricks, db2, doris, duckdb, exasol, flink, greenplum, hive, impala, mariadb, materialize, mysql, oracle, postgres, redshift, snowflake, soql, sparksql, sqlite, starrocks, teradata, trino, tsql, vertica


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql (1)

25-37: Consider adding a comment on the LIMIT 1 clause.

The query uses LIMIT 1 without ORDER BY, which is technically non-deterministic. While the business constraint that app_id should exist in at most one of apps or deleted_apps makes this safe, a brief comment would clarify the intent for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`
around lines 25 - 37, The SELECT that assigns v_org_id uses LIMIT 1 without
ORDER BY which is non-deterministic; add a concise comment above the SELECT/LEFT
JOIN block explaining that this is intentional and safe because the business
constraint guarantees p_app_id (requested_app.app_id) can exist in at most one
of public.apps or public.deleted_apps, so enforcing LIMIT 1 is merely defensive
and not relying on ordering — reference the variables/tables involved (v_org_id,
p_app_id/requested_app, public.apps, public.deleted_apps, owner_org, LIMIT 1) in
the comment so future maintainers understand the rationale.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`:
- Around line 92-134: The process_cron_stats_jobs() function is left executable
by PUBLIC; revoke and re-grant explicit execute permissions to lock it down: add
a REVOKE ALL ON FUNCTION public.process_cron_stats_jobs() FROM PUBLIC; then
GRANT EXECUTE ON FUNCTION public.process_cron_stats_jobs() TO service_role; (or
TO postgres if you prefer the superuser-only approach) so only the cron/service
role can invoke public.process_cron_stats_jobs().

---

Nitpick comments:
In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`:
- Around line 25-37: The SELECT that assigns v_org_id uses LIMIT 1 without ORDER
BY which is non-deterministic; add a concise comment above the SELECT/LEFT JOIN
block explaining that this is intentional and safe because the business
constraint guarantees p_app_id (requested_app.app_id) can exist in at most one
of public.apps or public.deleted_apps, so enforcing LIMIT 1 is merely defensive
and not relying on ordering — reference the variables/tables involved (v_org_id,
p_app_id/requested_app, public.apps, public.deleted_apps, owner_org, LIMIT 1) in
the comment so future maintainers understand the rationale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4b5f3b3-044e-4c54-9200-fce103608fc6

📥 Commits

Reviewing files that changed from the base of the PR and between b8fde47 and cc3f66f.

📒 Files selected for processing (3)
  • supabase/functions/_backend/utils/stats.ts
  • supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql
  • tests/process-cron-stats-jobs.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql (1)

99-121: Drop the inner DISTINCTs from active_apps.

Each branch is already combined with UNION, so the per-branch DISTINCT adds extra hash/sort work across four 30-day scans without changing the result.

Suggested change
-      SELECT DISTINCT av.app_id
+      SELECT av.app_id
       FROM public.app_versions av
       WHERE av.created_at >= pg_catalog.now() - INTERVAL '30 days'

       UNION

-      SELECT DISTINCT dm.app_id
+      SELECT dm.app_id
       FROM public.daily_mau dm
       WHERE dm.date >= pg_catalog.now() - INTERVAL '30 days' AND dm.mau > 0

       UNION

-      SELECT DISTINCT du.app_id
+      SELECT du.app_id
       FROM public.device_usage du
       WHERE du.timestamp >= pg_catalog.now() - INTERVAL '30 days'

       UNION

-      SELECT DISTINCT bu.app_id
+      SELECT bu.app_id
       FROM public.bandwidth_usage bu
       WHERE bu.timestamp >= pg_catalog.now() - INTERVAL '30 days'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`
around lines 99 - 121, In the CTE active_apps, remove the inner DISTINCT
keywords from each SELECT so they read plain SELECT av.app_id, SELECT dm.app_id,
SELECT du.app_id, and SELECT bu.app_id respectively; keep the UNIONs as-is and
ensure the referenced tables (app_versions, daily_mau, device_usage,
bandwidth_usage) and their WHERE clauses remain unchanged so the CTE still
deduplicates via UNION rather than per-branch DISTINCT.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`:
- Around line 102-120: Replace the unqualified NOW() calls with fully qualified
pg_catalog.now() in the four WHERE predicates: the av.created_at >= ... clause,
dm.date >= ... in the daily_mau query, du.timestamp >= ... in the device_usage
query, and bu.timestamp >= ... in the bandwidth_usage query so that each
predicate uses pg_catalog.now() - INTERVAL '30 days' (keeping the same interval
and aliases av, dm, du, bu intact).

---

Nitpick comments:
In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`:
- Around line 99-121: In the CTE active_apps, remove the inner DISTINCT keywords
from each SELECT so they read plain SELECT av.app_id, SELECT dm.app_id, SELECT
du.app_id, and SELECT bu.app_id respectively; keep the UNIONs as-is and ensure
the referenced tables (app_versions, daily_mau, device_usage, bandwidth_usage)
and their WHERE clauses remain unchanged so the CTE still deduplicates via UNION
rather than per-branch DISTINCT.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10682604-240c-4741-b79b-34d8724adc7a

📥 Commits

Reviewing files that changed from the base of the PR and between cc3f66f and e3a8878.

📒 Files selected for processing (1)
  • supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql

Comment thread supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql (1)

122-127: Redundant DISTINCT after UNION.

The UNION operator already eliminates duplicates, so the outer SELECT DISTINCT on line 122 is redundant. It won't cause incorrect behavior, but removing it clarifies intent and marginally reduces planning overhead.

♻️ Proposed simplification
     )
-    SELECT DISTINCT
+    SELECT
       active_apps.app_id,
       a.owner_org
     FROM active_apps
     INNER JOIN public.apps a ON a.app_id = active_apps.app_id
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`
around lines 122 - 127, Remove the redundant DISTINCT in the outer SELECT that
follows the UNION: locate the query selecting from active_apps and joining
public.apps (the SELECT DISTINCT returning active_apps.app_id and a.owner_org)
and drop the DISTINCT since UNION already deduplicates; keep the same join
(INNER JOIN public.apps a ON a.app_id = active_apps.app_id) and returned columns
(app_id, owner_org) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql`:
- Around line 122-127: Remove the redundant DISTINCT in the outer SELECT that
follows the UNION: locate the query selecting from active_apps and joining
public.apps (the SELECT DISTINCT returning active_apps.app_id and a.owner_org)
and drop the DISTINCT since UNION already deduplicates; keep the same join
(INNER JOIN public.apps a ON a.app_id = active_apps.app_id) and returned columns
(app_id, owner_org) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9ca9898-85d6-4ab4-9951-75b7de7a4021

📥 Commits

Reviewing files that changed from the base of the PR and between e3a8878 and 4dc1016.

📒 Files selected for processing (2)
  • scripts/start-cloudflare-workers.sh
  • supabase/migrations/20260325043000_harden_cron_stats_queue_followup.sql

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 39e0953 into main Mar 25, 2026
15 checks passed
@riderx riderx deleted the codex/fix-dashboard-stats-cron-activity-v2 branch March 25, 2026 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant