From cc39ca3084a56a5abb39954e32b5e045524bdf46 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:25:07 +0100 Subject: [PATCH] Fix stats query storm: stabilize time params to 10s granularity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PerformanceTab and RouteHeader computed new Date().toISOString() on every render, producing unique millisecond timestamps that busted the React Query cache key — causing continuous refetches (every few ms instead of 10s). Round timestamps to 10-second boundaries with useMemo so the query key stays stable between renders. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/pages/routes/PerformanceTab.tsx | 12 ++++++++++-- ui/src/pages/routes/RouteHeader.tsx | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/routes/PerformanceTab.tsx b/ui/src/pages/routes/PerformanceTab.tsx index 34faa170..bb9ea624 100644 --- a/ui/src/pages/routes/PerformanceTab.tsx +++ b/ui/src/pages/routes/PerformanceTab.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { useExecutionStats, useStatsTimeseries } from '../../api/queries/executions'; import { StatCard } from '../../components/shared/StatCard'; import { ThroughputChart } from '../../components/charts/ThroughputChart'; @@ -18,9 +19,16 @@ function pctChange(current: number, previous: number): { text: string; direction return { text: `${arrow} ${Math.abs(pct).toFixed(1)}% vs yesterday`, direction: pct > 0 ? 'up' : 'down' }; } +/** Round epoch-ms down to the nearest 10 s so the query key stays stable between renders. */ +function stableIso(epochMs: number): string { + return new Date(Math.floor(epochMs / 10_000) * 10_000).toISOString(); +} + export function PerformanceTab({ group, routeId }: PerformanceTabProps) { - const timeFrom = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); - const timeTo = new Date().toISOString(); + const [timeFrom, timeTo] = useMemo(() => { + const now = Date.now(); + return [stableIso(now - 24 * 60 * 60 * 1000), stableIso(now)]; + }, [Math.floor(Date.now() / 10_000)]); // Use scoped stats/timeseries via group+routeId query params const { data: stats } = useExecutionStats(timeFrom, timeTo, routeId, group); diff --git a/ui/src/pages/routes/RouteHeader.tsx b/ui/src/pages/routes/RouteHeader.tsx index 1c1da077..a4ebab1e 100644 --- a/ui/src/pages/routes/RouteHeader.tsx +++ b/ui/src/pages/routes/RouteHeader.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import type { DiagramLayout } from '../../api/types'; import { useExecutionStats } from '../../api/queries/executions'; import styles from './RoutePage.module.css'; @@ -10,7 +11,10 @@ interface RouteHeaderProps { export function RouteHeader({ group, routeId, layout }: RouteHeaderProps) { const nodeCount = layout?.nodes?.length ?? 0; - const timeFrom = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + const timeFrom = useMemo( + () => new Date(Math.floor(Date.now() / 10_000) * 10_000 - 24 * 60 * 60 * 1000).toISOString(), + [Math.floor(Date.now() / 10_000)], + ); const { data: stats } = useExecutionStats(timeFrom, undefined, routeId, group); const successRate = stats && stats.totalCount > 0