Skip to content

perf(dashboard): replace pause/jobs/pool polling with push events#231

Merged
javi11 merged 1 commit into
mainfrom
session/agitated-bhabha-f1916b
May 26, 2026
Merged

perf(dashboard): replace pause/jobs/pool polling with push events#231
javi11 merged 1 commit into
mainfrom
session/agitated-bhabha-f1916b

Conversation

@javi11
Copy link
Copy Markdown
Owner

@javi11 javi11 commented May 26, 2026

Summary

Three dashboard endpoints were polled by every client on tight intervals:

Endpoint Poll Caller
/api/processor/paused (+ auto-paused, reason) 1000 ms DashboardHeader.svelte
/api/running-job-details 500 ms ProgressSection.svelte
/api/metrics/nntp-pool 5000 ms ProviderStatus.svelte, routes/metrics/+page.svelte

That's ~3 req/s per idle client. It also caused the "HTML instead of JSON" symptom in dev: when the Go backend on :8080 was unreachable, the Vite proxy returned the SPA index.html, which crashed JSON.parse in web-client.ts.

This PR replaces the polling with push events over the existing WebSocket hub. No new transport — SSE was considered and rejected in favor of reusing the WS infrastructure already used for upload/config events.

What changed

Backend (internal/backend/)

  • app.go — added a ProcessingPauseState payload and pauseState() helper; added App.emit(event, payload) that routes to Wails runtime (desktop) or webEventEmitter (web). PauseProcessing/ResumeProcessing now broadcast the full pause snapshot so the UI doesn't need follow-up RPCs.
  • broadcaster.go (new) — one shared ticker per metric:
    • 1 s → running-jobs-updated (JSON dedupe; idle state goes silent)
    • 1 s → processing:auto-paused (state-change detection only)
    • 5 s → nntp-pool-metrics-updated
    • Wired into Startup / Shutdown.

Frontend

  • lib/api/events.ts (new) — typed event payloads + event-name constants.
  • DashboardHeader.svelte, ProgressSection.svelte, ProviderStatus.svelte, routes/metrics/+page.svelte — drop setInterval loops and visibility-change handlers, fetch once on mount, then subscribe via apiClient.on().

Net effect

Replaces N pollers × per-client with 1 ticker × server-side. A typical idle dashboard goes from ~3 req/s to zero recurring HTTP traffic; updates arrive as WS frames.

Reviewer notes

  • The REST endpoints stay — they're still used for the initial fetch on mount. Removing them would force a "wait for first event" race on every page load.
  • nntp-pool-metrics-updated is not deduped because Timestamp / Elapsed / AvgSpeed drift every tick; the cost of an unconditional 5 s emit is negligible.
  • running-jobs-updated dedupe is most effective when idle (no jobs → identical empty payload → no emit).
  • Auto-pause emission piggybacks on the running-jobs tick rather than instrumenting internal/processor/ to avoid an import cycle into internal/backend/.

Test plan

  • go build ./internal/... ./cmd/web/...
  • go vet ./internal/... ./cmd/web/...
  • go test -race ./internal/processor/... ./internal/queue/...
  • bun run check — 0 errors, 0 warnings
  • Manual: go run ./cmd/web + bun run dev, confirm DevTools shows each of the three endpoints called once on mount and never again; WS frames carry the new event types.
  • Manual: toggle pause from the dashboard, confirm header updates with no network round-trip.
  • Manual: kill backend then restart, confirm WS reconnect resyncs dashboard state.
  • Manual: same flow in Wails desktop (wails dev).

Three dashboard endpoints were polled per-client on 500 ms / 1 s / 5 s
intervals (running-job-details, processor/paused, metrics/nntp-pool).
The polling generated ~3 req/s per idle client and crashed JSON.parse
in the dev proxy whenever the Go backend was unreachable.

Reuse the existing WebSocket hub instead of polling:

- App.emit() unifies Wails-runtime and WebSocket dispatch so the
  broadcaster is mode-agnostic.
- eventBroadcaster runs one shared ticker per metric (1 s running-jobs
  with JSON dedupe; 5 s pool metrics; piggybacks auto-pause state-change
  detection on the running-jobs tick).
- PauseProcessing/ResumeProcessing now ship a full {paused, autoPaused,
  reason} snapshot so the UI renders without follow-up RPCs.
- Frontend components drop their setInterval loops, fetch once on mount,
  then subscribe via apiClient.on(). N clients are now driven by 1
  server-side ticker instead of N pollers.
@javi11 javi11 merged commit 851d274 into main May 26, 2026
3 checks passed
@javi11 javi11 deleted the session/agitated-bhabha-f1916b branch May 26, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant