fix(gps): остановлен 429-шторм — throttle SSE gps_asset_moved (3 сек/asset) и клиентских инвалидаций (60 сек), polling позиций 60 сек, статусов 5 мин

Каждая точка ingest (~1/сек) broadcast'илась SSE всем клиентам орг., клиент
на каждое событие делал invalidateQueries без throttle → 2+ GET/сек → исчерпание
per-user бакета apiLimiter (1500/15мин) → все API устройства 429 (формы не грузились).
Модель теперь как у нормальных трекеров: точки пишутся в БД, карта читает по интервалу.
+4 unit-теста троттла (server/gps/sse-throttle.ts)
This commit is contained in:
2026-09-17 21:59:34 +03:00
parent e4f9a1992a
commit c1d5b1cbee
4 changed files with 133 additions and 33 deletions

View File

@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { shouldPublishGpsAssetMoved } from '../server/gps/sse-throttle';
describe('shouldPublishGpsAssetMoved', () => {
it('первое событие объекта публикуется сразу', () => {
expect(shouldPublishGpsAssetMoved(1, 10_000)).toBe(true);
});
it('события чаще 3 секунд подряд пропускаются', () => {
const assetId = 2;
expect(shouldPublishGpsAssetMoved(assetId, 100_000)).toBe(true);
expect(shouldPublishGpsAssetMoved(assetId, 101_000)).toBe(false);
expect(shouldPublishGpsAssetMoved(assetId, 102_999)).toBe(false);
});
it('после окна в 3 секунды событие снова публикуется', () => {
const assetId = 3;
expect(shouldPublishGpsAssetMoved(assetId, 200_000)).toBe(true);
expect(shouldPublishGpsAssetMoved(assetId, 203_000)).toBe(true);
expect(shouldPublishGpsAssetMoved(assetId, 204_000)).toBe(false);
});
it('throttle независим между объектами', () => {
const now = 300_000;
expect(shouldPublishGpsAssetMoved(4, now)).toBe(true);
expect(shouldPublishGpsAssetMoved(5, now)).toBe(true);
expect(shouldPublishGpsAssetMoved(4, now + 1_000)).toBe(false);
expect(shouldPublishGpsAssetMoved(5, now + 1_000)).toBe(false);
});
});