perf(qbittorrent): rid incremental sync + shared cache + backoff (stop hanging qBittorrent)

The app was saturating qBittorrent's single-threaded web server and causing
its own Web UI (and the reverse proxy) to hang/504: each of the 3 qBittorrent
widgets fetched /sync/maindata independently, every call was a FULL snapshot
(no rid), and polling was aggressive (5s for speed). For large torrent lists
each snapshot is heavy, so the server queued and Traefik timed out.

QbittorrentClient.maindata now:
- Uses the incremental rid protocol: the first call is a full_update;
  subsequent calls send the last rid and get a small diff that is merged into
  a cached snapshot (full_update replaces; partial_update merges server_state,
  torrents {added/None-removed/..._removed}, categories, tags, trackers).
  Payloads shrink dramatically for large libraries.
- Serves a short-TTL (3s) cached snapshot under a lock, so concurrent widget
  polls collapse onto a single HTTP fetch instead of N.
- Backs off exponentially (capped 30s) on repeated failure, serving the last
  good snapshot when available, so a struggling qBittorrent isn't hammered
  further. Returns a shallow race-safe copy of the snapshot per call.

Also slow the speed widget poll from 5s -> 15s (backend widget-kind +
frontend registry) for ~3x fewer calls.

Tests: rid full+partial merge, cache collapses within-TTL calls, backoff
skips the network after failure and serves stale. 393/393 backend + 180/180
frontend tests pass; ruff + tsc + ESLint clean.
This commit is contained in:
Developer
2026-07-12 12:20:05 +00:00
parent 7e4222ef00
commit ba01ad7c0c
10 changed files with 230 additions and 23 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
dir: frontend/src/integrations
## role
Central integration layer that maps backend service types to frontend React components, navigation entries, and configuration schemas.
Frontend integration layer that maps service types and widgets to their React components, configuration schemas, and navigation entries.
## parent
index: frontend/src/.pi-map.index.md
map: frontend/src/.pi-map.md
+5 -5
View File
@@ -4,15 +4,15 @@ dir: frontend/src/integrations
index: frontend/src/integrations/.pi-map.index.md
## role
Central integration layer that maps backend service types to frontend React components, navigation entries, and configuration schemas.
Frontend integration layer that maps service types and widgets to their React components, configuration schemas, and navigation entries.
## files
- navEntries.ts | Defines a static mapping of service types to navigation entries and provides a filter function to return only entries for currently configured services. | exp: NavEntry, SERVICE_TYPE_NAV_ENTRIES, func:configuredNavEntries(configuredTypes: Set<string>) → NavEntry[], call:SERVICE_TYPE_NAV_ENTRIES.filter, call:configuredTypes.has | dep: lucide-react
- registry.test.ts | Tests the service and widget registry, verifying service registrations, widget bindings, configuration schemas, and widget resolution logic. | dep: vitest, ./registry, ../types
- registry.ts | Provides a frontend registry that maps service types and built-in widget kinds to their corresponding React components, metadata, and configuration schemas. | exp: WidgetComponentProps, ServiceWidgetBinding, ServiceBinding, SERVICE_REGISTRY, BUILTIN_WIDGETS, ResolvedWidget, func:getServiceBinding(serviceType: string) → ServiceBinding | undefined, func:getBuiltinBinding(kind: string) → ServiceWidgetBinding | undefined, func:resolveWidget(widget: WidgetInstance, services: ServiceInstance[]) → ResolvedWidget | undefined, call:services.find, call:getServiceBinding, call:binding?.widgets.find, call:getBuiltinBinding, func:enrichServiceTypes(types: ServiceTypeInfo[]) → ServiceTypeInfo[] | dep: react, ../widgets/AlertmanagerAlertsWidget, ../widgets/BackupsWidget, ../widgets/MetricChartWidget, ../widgets/MetricGaugeWidget, ../widgets/MetricMeanWidget, ../widgets/JellyfinWidget, ../widgets/JellyfinNowPlayingWidget, ../widgets/PrometheusMetricWidget, ../widgets/QbittorrentActiveTorrentsWidget, ../widgets/QbittorrentSpeedWidget, ../widgets/QbittorrentTotalsWidget, ../widgets/SshTaskWidget, ../widgets/StaticWidget, ../types, various widget components, internal types module
- registry.test.ts | Tests the service and widget registry, validating correct registration of service types, widget bindings, configuration schemas, and widget resolution behavior. | dep: vitest, ./registry, ../types
- registry.ts | Provides a frontend registry that maps service types and built-in widgets to their React components, config schemas, and metadata, with a resolver function for widget instances. | exp: WidgetComponentProps, ServiceWidgetBinding, ServiceBinding, SERVICE_REGISTRY, BUILTIN_WIDGETS, ResolvedWidget, func:getServiceBinding(serviceType: string) → ServiceBinding | undefined, func:getBuiltinBinding(kind: string) → ServiceWidgetBinding | undefined, func:resolveWidget(widget: WidgetInstance, services: ServiceInstance[]) → ResolvedWidget | undefined, call:services.find, call:getServiceBinding, call:binding?.widgets.find, call:getBuiltinBinding, func:enrichServiceTypes(types: ServiceTypeInfo[]) → ServiceTypeInfo[] | dep: react, ../widgets/AlertmanagerAlertsWidget, ../widgets/BackupsWidget, ../widgets/MetricChartWidget, ../widgets/MetricGaugeWidget, ../widgets/MetricMeanWidget, ../widgets/JellyfinWidget, ../widgets/JellyfinNowPlayingWidget, ../widgets/PrometheusMetricWidget, ../widgets/QbittorrentActiveTorrentsWidget, ../widgets/QbittorrentSpeedWidget, ../widgets/QbittorrentTotalsWidget, ../widgets/SshTaskWidget, ../widgets/StaticWidget, ../types, AlertmanagerAlertsWidget, BackupsWidget, MetricChartWidget, MetricGaugeWidget, MetricMeanWidget, JellyfinWidget, JellyfinNowPlayingWidget, PrometheusMetricWidget, QbittorrentActiveTorrentsWidget, QbittorrentSpeedWidget, QbittorrentTotalsWidget, SshTaskWidget, StaticWidget
## arch
Registry pattern with static navigation mappings, component resolution logic, and test-validated service/widget bindings.
Registry pattern with static mappings and a resolver function, centralizing widget/component resolution and service configuration metadata for the UI.
## tags
service, widgets, widget, binding, nav, entries, registry, types
service, widgets, widget, binding, nav, entries, types, registry
## symbols
- configuredNavEntries
- getServiceBinding
+7 -2
View File
@@ -119,7 +119,12 @@ export const SERVICE_REGISTRY: Record<string, ServiceBinding> = {
name: "Chart",
description: "Multi-series line chart from a PromQL range query.",
refreshIntervalMs: 60_000,
defaultConfig: { promql: "", window: "1h", unit: "none", scale: "auto" },
defaultConfig: {
promql: "",
window: "1h",
unit: "none",
scale: "auto",
},
configSchema: {
type: "object",
properties: {
@@ -213,7 +218,7 @@ export const SERVICE_REGISTRY: Record<string, ServiceBinding> = {
kind: "speed",
name: "Speed chart",
description: "Live download/upload speed over a short window.",
refreshIntervalMs: 5_000,
refreshIntervalMs: 15_000,
defaultConfig: { unit: "bytes_per_sec", scale: "auto" },
configSchema: {
type: "object",