mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-03 03:12:31 +00:00
Merge pull request #1983 from PR0M3TH3AN/codex/improve-websocket-stability-and-ui-responsiveness
Add per-relay backoff and rate-limited relay summary logging
This commit is contained in:
1 file changed
+85
-20
+85
-20
@@ -413,10 +413,13 @@ const RELAY_CONNECT_TIMEOUT_MS = 5000;
|
||||
const RELAY_RECONNECT_BASE_DELAY_MS = 2000;
|
||||
const RELAY_RECONNECT_MAX_DELAY_MS = 60000;
|
||||
const RELAY_RECONNECT_MAX_ATTEMPTS = 5;
|
||||
const RELAY_BACKOFF_BASE_DELAY_MS = 1000;
|
||||
const RELAY_BACKOFF_MAX_DELAY_MS = 8000;
|
||||
const RELAY_CIRCUIT_BREAKER_THRESHOLD = 3;
|
||||
const RELAY_CIRCUIT_BREAKER_COOLDOWN_MS = 10 * 60 * 1000;
|
||||
const RELAY_FAILURE_WINDOW_MS = 5 * 60 * 1000;
|
||||
const RELAY_FAILURE_WINDOW_THRESHOLD = 3;
|
||||
const RELAY_SUMMARY_LOG_INTERVAL_MS = 30000;
|
||||
const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1";
|
||||
// We use the policy TTL, but currently the storage backend is hardcoded to IDB (with localStorage fallback).
|
||||
// Future refactors should make EventsCacheStore dynamic based on CACHE_POLICIES[NOTE_TYPES.VIDEO_POST].storage.
|
||||
@@ -1298,6 +1301,7 @@ export class NostrClient {
|
||||
this.relayFailureCounts = new Map();
|
||||
this.relayFailureWindows = new Map();
|
||||
this.relayCircuitBreakers = new Map();
|
||||
this.relaySummaryLogTimestamps = new Map();
|
||||
this.relayReconnectTimer = null;
|
||||
this.relayReconnectAttempt = 0;
|
||||
this.isInitialized = false;
|
||||
@@ -4558,16 +4562,41 @@ export class NostrClient {
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
logRelaySummary({
|
||||
key,
|
||||
level = "warn",
|
||||
message,
|
||||
payload,
|
||||
} = {}) {
|
||||
if (!key || !message) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
const lastLogged = this.relaySummaryLogTimestamps.get(key) || 0;
|
||||
if (now - lastLogged < RELAY_SUMMARY_LOG_INTERVAL_MS) {
|
||||
return;
|
||||
}
|
||||
this.relaySummaryLogTimestamps.set(key, now);
|
||||
const channel = isDevMode ? devLogger : userLogger;
|
||||
const logFn =
|
||||
typeof channel[level] === "function" ? channel[level] : channel.warn;
|
||||
if (payload) {
|
||||
logFn(`[nostr] ${message}`, payload);
|
||||
} else {
|
||||
logFn(`[nostr] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
resolveRelayBackoffMs(failureCount, ttlOverride) {
|
||||
const baseMs = 15000;
|
||||
const maxMs = 5 * 60 * 1000;
|
||||
const baseMs = RELAY_BACKOFF_BASE_DELAY_MS;
|
||||
const maxMs = RELAY_BACKOFF_MAX_DELAY_MS;
|
||||
const computed = Math.min(
|
||||
maxMs,
|
||||
baseMs * Math.pow(2, Math.max(0, failureCount - 1))
|
||||
);
|
||||
const override = Number(ttlOverride);
|
||||
if (Number.isFinite(override) && override > 0) {
|
||||
return Math.max(computed, Math.floor(override));
|
||||
return Math.min(computed, Math.floor(override));
|
||||
}
|
||||
return computed;
|
||||
}
|
||||
@@ -4580,11 +4609,24 @@ export class NostrClient {
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
const hadBackoffState =
|
||||
this.relayBackoff.has(normalized) ||
|
||||
this.unreachableRelays.has(normalized) ||
|
||||
this.relayFailureCounts.has(normalized) ||
|
||||
this.relayCircuitBreakers.has(normalized);
|
||||
this.relayBackoff.delete(normalized);
|
||||
this.relayFailureCounts.delete(normalized);
|
||||
this.relayFailureWindows.delete(normalized);
|
||||
this.unreachableRelays.delete(normalized);
|
||||
this.relayCircuitBreakers.delete(normalized);
|
||||
if (hadBackoffState) {
|
||||
this.logRelaySummary({
|
||||
key: `relay-recovered:${normalized}`,
|
||||
level: "info",
|
||||
message: "Relay recovered; backoff cleared.",
|
||||
payload: { relay: normalized },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
recordRelayFailureWindow(url) {
|
||||
@@ -4626,7 +4668,10 @@ export class NostrClient {
|
||||
reason,
|
||||
});
|
||||
this.unreachableRelays.add(normalized);
|
||||
if (windowFailureCount >= RELAY_FAILURE_WINDOW_THRESHOLD) {
|
||||
const shouldOpenCircuit =
|
||||
nextFailureCount >= RELAY_CIRCUIT_BREAKER_THRESHOLD ||
|
||||
windowFailureCount >= RELAY_FAILURE_WINDOW_THRESHOLD;
|
||||
if (shouldOpenCircuit) {
|
||||
const now = Date.now();
|
||||
const openUntil = now + RELAY_CIRCUIT_BREAKER_COOLDOWN_MS;
|
||||
const existing = this.relayCircuitBreakers.get(normalized);
|
||||
@@ -4634,26 +4679,40 @@ export class NostrClient {
|
||||
existing && Number.isFinite(existing.openUntil)
|
||||
? Math.max(existing.openUntil, openUntil)
|
||||
: openUntil;
|
||||
const circuitReason = reason || "windowed-failures";
|
||||
const circuitReason =
|
||||
reason ||
|
||||
(nextFailureCount >= RELAY_CIRCUIT_BREAKER_THRESHOLD
|
||||
? "consecutive-failures"
|
||||
: "windowed-failures");
|
||||
this.relayCircuitBreakers.set(normalized, {
|
||||
openUntil: nextOpenUntil,
|
||||
failureCount: nextFailureCount,
|
||||
reason: circuitReason,
|
||||
});
|
||||
if (isDevMode) {
|
||||
devLogger.debug("[nostr] Circuit breaker opened for relay.", {
|
||||
this.logRelaySummary({
|
||||
key: `relay-circuit:${normalized}`,
|
||||
level: "warn",
|
||||
message: "Circuit breaker opened for relay.",
|
||||
payload: {
|
||||
relay: normalized,
|
||||
openUntil: nextOpenUntil,
|
||||
failureCount: nextFailureCount,
|
||||
reason: circuitReason,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (isDevMode) {
|
||||
devLogger.debug(
|
||||
`[nostr] Relay backoff set for ${normalized} (${backoffMs}ms).`,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
this.logRelaySummary({
|
||||
key: `relay-backoff:${normalized}`,
|
||||
level: "warn",
|
||||
message: "Relay backoff applied.",
|
||||
payload: {
|
||||
relay: normalized,
|
||||
backoffMs,
|
||||
failureCount: nextFailureCount,
|
||||
retryAt,
|
||||
reason,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getHealthyRelays(candidates) {
|
||||
@@ -4673,9 +4732,12 @@ export class NostrClient {
|
||||
this.relayFailureWindows.delete(url);
|
||||
this.unreachableRelays.delete(url);
|
||||
this.relayBackoff.delete(url);
|
||||
if (isDevMode) {
|
||||
devLogger.debug(`[nostr] Circuit breaker reset for ${url}.`);
|
||||
}
|
||||
this.logRelaySummary({
|
||||
key: `relay-circuit-reset:${url}`,
|
||||
level: "info",
|
||||
message: "Circuit breaker reset for relay.",
|
||||
payload: { relay: url },
|
||||
});
|
||||
}
|
||||
if (!this.unreachableRelays.has(url)) {
|
||||
return true;
|
||||
@@ -4691,9 +4753,12 @@ export class NostrClient {
|
||||
this.relayFailureCounts.delete(url);
|
||||
this.relayFailureWindows.delete(url);
|
||||
this.unreachableRelays.delete(url);
|
||||
if (isDevMode) {
|
||||
devLogger.debug(`[nostr] Relay backoff expired for ${url}.`);
|
||||
}
|
||||
this.logRelaySummary({
|
||||
key: `relay-backoff-expired:${url}`,
|
||||
level: "info",
|
||||
message: "Relay backoff expired.",
|
||||
payload: { relay: url },
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user