Reduce task polling frequency and raise rate limits to prevent 429s
With 8+ active tasks, polling every 5s generates ~96 task_status requests/min, exceeding the 60/60s rate limit. Two fixes: - Adaptive polling: 30s when WebSocket is connected (safety net), 5s only when WebSocket is down (primary source) - Raise task_status rate limit to 200/60s and tasks_for_user to 60/60s to handle burst scenarios (page reloads, WS reconnects)
This commit is contained in:
parent
791b5a9d55
commit
3616e678ac
2 changed files with 21 additions and 10 deletions
|
|
@ -27,8 +27,8 @@ class RateLimitConfig:
|
|||
"/api/listing_geojson": EndpointLimit(10, 60),
|
||||
"/api/listing_geojson/stream": EndpointLimit(10, 60),
|
||||
"/api/refresh_listings": EndpointLimit(3, 300),
|
||||
"/api/task_status": EndpointLimit(60, 60),
|
||||
"/api/tasks_for_user": EndpointLimit(30, 60),
|
||||
"/api/task_status": EndpointLimit(200, 60),
|
||||
"/api/tasks_for_user": EndpointLimit(60, 60),
|
||||
"/api/cancel_task": EndpointLimit(10, 60),
|
||||
"/api/clear_all_tasks": EndpointLimit(5, 60),
|
||||
"/api/get_districts": EndpointLimit(20, 60),
|
||||
|
|
@ -87,8 +87,8 @@ class RateLimitConfig:
|
|||
"/api/listing_geojson": _parse_limit("RATE_LIMIT_GEOJSON", 10, 60),
|
||||
"/api/listing_geojson/stream": _parse_limit("RATE_LIMIT_GEOJSON_STREAM", 10, 60),
|
||||
"/api/refresh_listings": _parse_limit("RATE_LIMIT_REFRESH", 3, 300),
|
||||
"/api/task_status": _parse_limit("RATE_LIMIT_TASK_STATUS", 60, 60),
|
||||
"/api/tasks_for_user": _parse_limit("RATE_LIMIT_TASKS_FOR_USER", 30, 60),
|
||||
"/api/task_status": _parse_limit("RATE_LIMIT_TASK_STATUS", 200, 60),
|
||||
"/api/tasks_for_user": _parse_limit("RATE_LIMIT_TASKS_FOR_USER", 60, 60),
|
||||
"/api/cancel_task": _parse_limit("RATE_LIMIT_CANCEL_TASK", 10, 60),
|
||||
"/api/clear_all_tasks": _parse_limit("RATE_LIMIT_CLEAR_TASKS", 5, 60),
|
||||
"/api/get_districts": _parse_limit("RATE_LIMIT_DISTRICTS", 20, 60),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { AuthUser } from '@/auth/types';
|
||||
import type { TaskState, TaskStatusResponse, WSMessage } from '@/types';
|
||||
import { WS_TASKS_PATH, POLLING_INTERVALS } from '@/constants';
|
||||
import { WS_TASKS_PATH } from '@/constants';
|
||||
import { fetchTasksForUser, fetchTaskStatus } from '@/services';
|
||||
|
||||
const KEEPALIVE_MS = 30_000;
|
||||
const MAX_RECONNECT_DELAY_MS = 30_000;
|
||||
const POLL_FAST_MS = 5_000; // WS down — polling is the primary update source
|
||||
const POLL_SLOW_MS = 30_000; // WS up — polling is just a safety net
|
||||
|
||||
function wsUrl(token: string): string {
|
||||
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
|
|
@ -116,9 +118,15 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
|||
}
|
||||
}, []);
|
||||
|
||||
const startPolling = useCallback(() => {
|
||||
const startPolling = useCallback((intervalMs: number = POLL_SLOW_MS) => {
|
||||
const currentUser = userRef.current;
|
||||
if (!currentUser || pollingTimer.current) return;
|
||||
if (!currentUser) return;
|
||||
|
||||
// Stop existing polling before (re)starting with the requested interval
|
||||
if (pollingTimer.current) {
|
||||
clearInterval(pollingTimer.current);
|
||||
pollingTimer.current = null;
|
||||
}
|
||||
|
||||
const fetchAndPoll = async () => {
|
||||
const u = userRef.current;
|
||||
|
|
@ -157,7 +165,7 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
|||
};
|
||||
|
||||
fetchAndPoll();
|
||||
pollingTimer.current = setInterval(fetchAndPoll, POLLING_INTERVALS.TASK_STATUS_MS);
|
||||
pollingTimer.current = setInterval(fetchAndPoll, intervalMs);
|
||||
}, []); // No reactive deps — reads from refs
|
||||
|
||||
// ---- WebSocket connection ----
|
||||
|
|
@ -173,6 +181,9 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
|||
setIsConnected(true);
|
||||
reconnectAttempt.current = 0;
|
||||
|
||||
// WS is primary — slow down polling to safety-net rate
|
||||
startPolling(POLL_SLOW_MS);
|
||||
|
||||
// Start keepalive pings
|
||||
keepaliveTimer.current = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
|
|
@ -219,8 +230,8 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
|||
keepaliveTimer.current = null;
|
||||
}
|
||||
|
||||
// Fallback to polling while WS is down
|
||||
startPolling();
|
||||
// WS is down — poll at fast rate as primary update source
|
||||
startPolling(POLL_FAST_MS);
|
||||
|
||||
// Exponential backoff reconnect
|
||||
const delay = Math.min(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue