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": EndpointLimit(10, 60),
|
||||||
"/api/listing_geojson/stream": EndpointLimit(10, 60),
|
"/api/listing_geojson/stream": EndpointLimit(10, 60),
|
||||||
"/api/refresh_listings": EndpointLimit(3, 300),
|
"/api/refresh_listings": EndpointLimit(3, 300),
|
||||||
"/api/task_status": EndpointLimit(60, 60),
|
"/api/task_status": EndpointLimit(200, 60),
|
||||||
"/api/tasks_for_user": EndpointLimit(30, 60),
|
"/api/tasks_for_user": EndpointLimit(60, 60),
|
||||||
"/api/cancel_task": EndpointLimit(10, 60),
|
"/api/cancel_task": EndpointLimit(10, 60),
|
||||||
"/api/clear_all_tasks": EndpointLimit(5, 60),
|
"/api/clear_all_tasks": EndpointLimit(5, 60),
|
||||||
"/api/get_districts": EndpointLimit(20, 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": _parse_limit("RATE_LIMIT_GEOJSON", 10, 60),
|
||||||
"/api/listing_geojson/stream": _parse_limit("RATE_LIMIT_GEOJSON_STREAM", 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/refresh_listings": _parse_limit("RATE_LIMIT_REFRESH", 3, 300),
|
||||||
"/api/task_status": _parse_limit("RATE_LIMIT_TASK_STATUS", 60, 60),
|
"/api/task_status": _parse_limit("RATE_LIMIT_TASK_STATUS", 200, 60),
|
||||||
"/api/tasks_for_user": _parse_limit("RATE_LIMIT_TASKS_FOR_USER", 30, 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/cancel_task": _parse_limit("RATE_LIMIT_CANCEL_TASK", 10, 60),
|
||||||
"/api/clear_all_tasks": _parse_limit("RATE_LIMIT_CLEAR_TASKS", 5, 60),
|
"/api/clear_all_tasks": _parse_limit("RATE_LIMIT_CLEAR_TASKS", 5, 60),
|
||||||
"/api/get_districts": _parse_limit("RATE_LIMIT_DISTRICTS", 20, 60),
|
"/api/get_districts": _parse_limit("RATE_LIMIT_DISTRICTS", 20, 60),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import type { AuthUser } from '@/auth/types';
|
import type { AuthUser } from '@/auth/types';
|
||||||
import type { TaskState, TaskStatusResponse, WSMessage } from '@/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';
|
import { fetchTasksForUser, fetchTaskStatus } from '@/services';
|
||||||
|
|
||||||
const KEEPALIVE_MS = 30_000;
|
const KEEPALIVE_MS = 30_000;
|
||||||
const MAX_RECONNECT_DELAY_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 {
|
function wsUrl(token: string): string {
|
||||||
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
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;
|
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 fetchAndPoll = async () => {
|
||||||
const u = userRef.current;
|
const u = userRef.current;
|
||||||
|
|
@ -157,7 +165,7 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchAndPoll();
|
fetchAndPoll();
|
||||||
pollingTimer.current = setInterval(fetchAndPoll, POLLING_INTERVALS.TASK_STATUS_MS);
|
pollingTimer.current = setInterval(fetchAndPoll, intervalMs);
|
||||||
}, []); // No reactive deps — reads from refs
|
}, []); // No reactive deps — reads from refs
|
||||||
|
|
||||||
// ---- WebSocket connection ----
|
// ---- WebSocket connection ----
|
||||||
|
|
@ -173,6 +181,9 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
reconnectAttempt.current = 0;
|
reconnectAttempt.current = 0;
|
||||||
|
|
||||||
|
// WS is primary — slow down polling to safety-net rate
|
||||||
|
startPolling(POLL_SLOW_MS);
|
||||||
|
|
||||||
// Start keepalive pings
|
// Start keepalive pings
|
||||||
keepaliveTimer.current = setInterval(() => {
|
keepaliveTimer.current = setInterval(() => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
|
@ -219,8 +230,8 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn {
|
||||||
keepaliveTimer.current = null;
|
keepaliveTimer.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to polling while WS is down
|
// WS is down — poll at fast rate as primary update source
|
||||||
startPolling();
|
startPolling(POLL_FAST_MS);
|
||||||
|
|
||||||
// Exponential backoff reconnect
|
// Exponential backoff reconnect
|
||||||
const delay = Math.min(
|
const delay = Math.min(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue