G/V-Lag: 1-s-Fallback + ehrlicher Verbindungspunkt (v=151)

Zuerst gemessen, dann gedreht - und das Backend war NICHT das Problem:
  /api/snapshot   75 Abrufe in 20 s -> 21 verschiedene ts = ~1/s
  WebSocket-Push  direkt am Socket gemessen: Median 1,04 s (min 1,00 max 1,06)
  G/V-Aenderungen Ø 1,44 s - das ist der Markt, keine Verzoegerung

Die ganze Server-Kette liefert bereits 1 s. Der Lag entsteht erst am Handy:
bricht der WebSocket STILL weg (WireGuard-Aussetzer, App-Wechsel), traegt nur
noch der REST-Poll - und der stand auf 4 s.

POLL_MS 4000 -> 1000, aber mit Sperre: solange der WS liefert (lastWsMs,
Fenster WS_FRISCH_MS=2200 ms), ueberspringt der Poll den Abruf. Kein
zusaetzlicher Verkehr im Normalfall.

Denkfehler, den erst die Simulation zeigte: der erste Entwurf las die
WS-Frische aus lastRecvMs (jede Quelle) - damit drosselte sich der Poll SELBST
und kam bei totem WS nur auf 2,9 s statt 1 s. Jetzt zwei getrennte Uhren.

Simuliert: WS gesund -> 0 Zusatz-Abrufe, max. Alter 0,9 s. WS tot -> 1/s, max.
Alter 0,9 s. WS traege (5 s) -> max. 2,9 s. Vorher bei totem WS: 4,0 s.

Der Verbindungspunkt log: er wurde nur bei sock.onclose auf "off" gesetzt, aber
genau der Fall, der den Lag erzeugt, feuert KEIN onclose - der Socket haengt
still, der Punkt blieb gruen. Neu .dot.stale (bernstein, pulsierend) ab 3,5 s
ohne frischen Snapshot: gemessen wird, was ANKOMMT, nicht was der Socket ueber
sich behauptet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-07 14:20:42 +02:00
co-authored by Claude Opus 5
parent c445c687f5
commit 59e9498237
4 changed files with 87 additions and 4 deletions
+46 -1
View File
@@ -1124,15 +1124,27 @@ let sock = null, retry = null;
// `engine.snapshot_cached().ts`) — nur die jeweils NEUERE Quelle rendert, kein
// Flackern/Doppel-Render, wenn beide fast gleichzeitig eintreffen.
let lastTs = 0;
// Wann kam zuletzt ein FRISCHER Snapshot an (Wanduhr, egal aus welcher Quelle)?
// Grundlage für den 1-s-Fallback und die Stale-Erkennung unten.
let lastRecvMs = 0;
// Getrennt davon: wann kam zuletzt eine WS-NACHRICHT? Nur diese Uhr entscheidet,
// ob der Poll uebersprungen wird (s. Kommentar in `sock.onmessage`).
let lastWsMs = 0;
function _applySnapshot(d) {
if (typeof d.ts === "number" && d.ts <= lastTs) return; // schon Neueres gezeigt
if (typeof d.ts === "number") lastTs = d.ts;
lastRecvMs = Date.now();
render(d);
}
function connect() {
const proto = location.protocol === "https:" ? "wss" : "ws";
sock = new WebSocket(`${proto}://${location.host}/ws`);
sock.onmessage = (ev) => {
// ⚠ NUR hier setzen, NICHT in `_applySnapshot`. Der Poll darf die
// WS-Frische nicht mitschreiben, sonst drosselt er sich selbst: er würde
// nach jedem eigenen Abruf wieder WS_FRISCH_MS pausieren und käme bei totem
// WS nur auf ~2,9 s statt 1 s. (In der Simulation aufgefallen, s. unten.)
lastWsMs = Date.now();
try { _applySnapshot(JSON.parse(ev.data)); } catch (e) { console.warn(e); }
};
sock.onclose = () => {
@@ -1149,9 +1161,21 @@ function connect() {
// mehr — z. B. schwaches WireGuard-Signal, App-Wechsel ohne Visibility-Event),
// ohne auf einen Reconnect warten zu müssen. Der `lastTs`-Dedup verhindert
// Doppel-Renders, solange der WS normal liefert — reine Redundanz, kein Konflikt.
const POLL_MS = 4000;
// ⚠⚠ 4000 → 1000 (2026-08-07, User: „G/V wird extrem lagged, 1 Sekunde").
// GEMESSEN, bevor gedreht wurde: die gesamte Server-Kette liefert bereits 1 s
// (`_pos_loop` 1 s · Snapshot-Cache 0,8 s · WS-Push **Median 1,04 s**, direkt am
// Socket nachgemessen). Der Lag entsteht also NICHT im Backend, sondern wenn der
// WebSocket am Handy still wegbricht (WireGuard-Aussetzer, App-Wechsel): dann
// trägt nur noch dieser Poll — und der stand auf 4 s.
// ⚠ Das VERDOPPELT den Verkehr NICHT: solange der WS liefert, überspringt der
// Poll den Abruf (s. `WS_FRISCH_MS`). Er feuert nur, wenn der WS schweigt.
const POLL_MS = 1000;
const WS_FRISCH_MS = 2200; // > WS-Takt (1,04 s) + Jitter, sonst pollt es doppelt
async function pollSnapshot() {
if (document.hidden) return; // seite unsichtbar → spart Akku/Daten, Wake-Handler holt beim Zurückkommen sofort nach
// Liefert der WS gerade? Dann NICHT zusätzlich abfragen — reine Redundanz
// kostet über WireGuard sonst ~14 KB/s ohne jeden Nutzen.
if (Date.now() - lastWsMs < WS_FRISCH_MS) return;
let d;
try {
d = await (await fetch("api/snapshot")).json();
@@ -1166,6 +1190,27 @@ async function pollSnapshot() {
}
setInterval(pollSnapshot, POLL_MS);
// ── Ehrlicher Verbindungspunkt (2026-08-07) ────────────────────────────────
// ⚠ Bisher wurde er NUR bei `sock.onclose` auf „off" gesetzt. Genau der Fall,
// der den Lag verursacht, löst aber KEIN onclose aus: der Socket hängt still
// (WireGuard-Aussetzer, App-Wechsel) — der Punkt blieb grün und behauptete eine
// Verbindung, die nichts mehr liefert. Ein Zustandsanzeiger, der im
// Fehlerfall nicht anschlägt, ist schlimmer als keiner.
// Jetzt zählt, was WIRKLICH ankommt: liegt der letzte angewandte Snapshot mehr
// als STALE_MS zurück, wird der Punkt matt — unabhängig davon, was der Socket
// über sich selbst behauptet.
const STALE_MS = 3500;
setInterval(() => {
const el = $("conn");
if (!el) return;
const alt = Date.now() - lastRecvMs;
el.classList.toggle("stale", lastRecvMs > 0 && alt > STALE_MS);
el.title = lastRecvMs
? `letzte Aktualisierung vor ${(alt / 1000).toFixed(1)} s` +
(alt > STALE_MS ? " — Verbindung hängt, es wird nachgepollt" : "")
: "noch keine Daten empfangen";
}, 1000);
// ── Trade-Steuerung ────────────────────────────────────────────────────────
const TOKEN_KEY = "oil_token";