DEV Community

Roberto Luna
Roberto Luna

Posted on

Optimizing TVView’s Auto‑Refresh to Preserve Neon Compute Headroom

Optimizing TVView’s Auto‑Refresh to Preserve Neon Compute Headroom


TL;DR:

I increased the /tv dashboard refresh interval from 30 s to 5 min and from 5 min to 15 min to prevent unnecessary Neon compute usage, which caused a scale‑to‑zero failure during a recent incident. The change is a single constant swap in src/features/tv/TVDashboard.tsx, but it had a measurable impact on cost and reliability.


The Problem

During a recent Neon incident the /tv dashboard was polling the database every 30 seconds. Neon’s autoscaling policy interprets sustained high query rates as a signal that the database must stay warm, preventing it from scaling to zero. This caused:

  • Higher compute costs: Neon kept a compute instance alive for longer than needed.
  • Potential throttling: The database was hitting its per‑second request quota.
  • Unnecessary latency: Clients were receiving stale data more often than necessary.

The symptom was a spike in Neon’s billing and a warning in the logs about “scale‑to‑zero prevented due to high activity”.


What I Tried First

Initially I tried to cache the results on the client side using react-query’s stale‑while‑revalidate strategy. I added a staleTime of 5 min and set refetchInterval to 30 s. However, this still triggered a database query every 30 s because react-query would automatically refetch regardless of cache status. The logs still showed high query counts.

Next, I tried disabling the auto‑refresh entirely and letting users click a “Refresh” button. While this eliminated the background load, it was a poor user experience; the dashboard looked stale, and users complained.

Finally, I considered moving the polling logic to a server‑side cron job that would write fresh data to a cache store. That would have required a new infrastructure component and increased complexity, so I decided to keep the client‑side polling but reduce its frequency.


The Implementation

The core change was a single constant in src/features/tv/TVDashboard.tsx. The file originally defined:

// src/features/tv/TVDashboard.tsx
const REFRESH_MS = 30_000; // 30 s — data only changes on manual sync, no need to poll faster
Enter fullscreen mode Exit fullscreen mode

I updated it twice:

  1. First bump (30 s → 5 min) – to stop the aggressive polling that prevented Neon from scaling to zero:
// src/features/tv/TVDashboard.tsx
const REFRESH_MS = 300_000; // 5 min — data only changes on manual sync, no need to poll faster
Enter fullscreen mode Exit fullscreen mode
  1. Second bump (5 min → 15 min) – to give Neon even more headroom after the incident:
// src/features/tv/TVDashboard.tsx
const REFRESH_MS = 900_000; // 15 min — data on
Enter fullscreen mode Exit fullscreen mode

The useClock hook and the useEffect that sets up the interval were already wired to use REFRESH_MS:

useEffect(() => {
  const interval = setInterval(() => {
    setClock(Date.now());
  }, REFRESH_MS);

  return () => clearInterval(interval);
}, []);
Enter fullscreen mode Exit fullscreen mode

Because the hook only updates a local clock, the dashboard re‑renders every REFRESH_MS but does not trigger a new API call unless the user initiates a manual sync or the data becomes stale.

Full Diff Highlights

Commit f5c490c4 — perf: bump /tv refresh to 15min (from 5min)
  [modified] src/features/tv/TVDashboard.tsx (+1/-1)
    @@ -42,7 +42,7 @@ interface DashboardData {
   };
 }

-const REFRESH_MS = 300_000; // 5 min — data only changes on manual sync, no need to poll faster
+const REFRESH_MS = 900_000; // 15 min — data on

Commit 45ffb778 — perf: slow /tv auto-refresh from 30s to 5min
  [modified] src/features/tv/TVDashboard.tsx (+1/-1)
    @@ -42,7 +42,7 @@ interface DashboardData {
   };
 }

-const REFRESH_MS = 30_000;
+const REFRESH_MS = 300_000; // 5 min — data only changes on manual sync, no need to poll faster
Enter fullscreen mode Exit fullscreen mode

The rest of the file remained untouched. No additional dependencies were added, and the change is fully backward‑compatible.


Key Takeaway

When a database’s autoscaling policy is sensitive to query frequency, a single constant change in the client‑side polling interval can dramatically reduce compute costs and improve reliability.

Instead of adding complex caching or server‑side jobs, evaluate the natural refresh cadence of your data and align it with the database’s scaling behavior. A 5‑minute interval can be enough for most dashboards that only change on manual sync.


What's Next

  1. Add a visual indicator for the last refresh time so users know the data is recent.
  2. Implement exponential backoff for manual sync failures, to avoid hammering Neon if the database is under heavy load.
  3. Automate telemetry: log the number of refreshes per hour to a monitoring dashboard, so we can spot regressions early.

These steps will keep the dashboard responsive, cost‑effective, and robust against future scaling incidents.


vibecoding #buildinpublic #nextjs #neon #reactquery #typescript #performance



Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.

Repo: zaerohell/tvview · 2026-08-03

#playadev #buildinpublic

Top comments (0)