fix(fallback): connectTimeoutMs 2500→15000 e breakerTripAfter 2→3
Il default di 2,5 s per connect+headers è troppo stretto su percorsi VPN/AP: misurati GET /v1/status 1,23 s e POST /v1/memories:search 1,98 s, talvolta oltre 2,5 s. In quelle condizioni il breaker si apriva pur con gateway raggiungibile e tutte le chiamate successive andavano in fast-fail per 2–10 minuti, spingendo di fatto ogni operazione sul solo fallback locale e lasciando l'outbox non sincronizzata. - extensions/shared.ts: connectTimeoutMs 2500 → 15_000 (default, fallback nel path di richiesta e commento), breakerTripAfter 2 → 3 - README.md: default aggiornati + motivazione nella tabella dei timeout - skills/qmem/SKILL.md: default aggiornato e sintassi CLI del reset breaker (`qmem-sqlite.mjs breaker --reset`) Verifica sul campo: con connectTimeoutMs=60000 l'outbox (18 record) è stata sincronizzata completamente e il breaker è rimasto chiuso.
This commit is contained in:
@@ -45,11 +45,11 @@ Config salvata in `~/.config/pi-qmem/config.json` (0600):
|
||||
"localDbPath": "~/.local/share/pi-qmem/qmem.sqlite",
|
||||
"localFallback": true,
|
||||
"offlineQueue": true,
|
||||
"connectTimeoutMs": 2500,
|
||||
"connectTimeoutMs": 15000,
|
||||
"timeoutMs": 30000,
|
||||
"breakerBaseMs": 120000,
|
||||
"breakerMaxMs": 600000,
|
||||
"breakerTripAfter": 2
|
||||
"breakerTripAfter": 3
|
||||
}
|
||||
```
|
||||
|
||||
@@ -63,7 +63,7 @@ Due timeout distinti, per non confondere "gateway giù" con "elaborazione lunga"
|
||||
|
||||
| Fase | Chiave | Default | Significato |
|
||||
|---|---|---|---|
|
||||
| **connect + headers** | `connectTimeoutMs` | **2500** | nessuna risposta entro questo tempo → **gateway non raggiungibile** (fallimento definitivo) |
|
||||
| **connect + headers** | `connectTimeoutMs` | **15000** | nessuna risposta entro questo tempo → **gateway non raggiungibile** (fallimento definitivo). Vale per latenze di rete instabili (VPN/AP): un valore troppo stretto (2,5 s) apre il breaker anche se il gateway è su, con misure tipiche di 1,2–2,0 s per `status`/`search` |
|
||||
| **body** (dopo gli header) | `timeoutMs` | 30000 | budget per il rerank/export/ricerca: un superamento è un fallimento **ambiguo** |
|
||||
|
||||
Comportamento:
|
||||
@@ -72,7 +72,7 @@ Comportamento:
|
||||
**circuit breaker** si apre subito e resta aperto `breakerBaseMs` (**2 min**), con escalation
|
||||
esponenziale fino a `breakerMaxMs` (10 min).
|
||||
- **5xx o body lento**: fallimenti **ambigui** → retry con `Retry-After` e breaker solo dopo
|
||||
`breakerTripAfter` (default 2) fallimenti consecutivi.
|
||||
`breakerTripAfter` (default 3) fallimenti consecutivi.
|
||||
- **Breaker aperto**: le chiamate ritornano in **~0 ms senza toccare la rete** (`error:
|
||||
"gateway_unreachable"`, `breaker_open: true`, `retry_in_ms`), quindi i tool passano subito al
|
||||
fallback locale e l'outbox accoda senza attese.
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface MemoryConfig {
|
||||
apiKey: string;
|
||||
/** Budget (ms) per la risposta DOPO gli header: distingue l'elaborazione lunga (default 30000). */
|
||||
timeoutMs?: number;
|
||||
/** Timeout (ms) per connect+headers: oltre questo il gateway è "non raggiungibile" (default 2500). */
|
||||
/** Timeout (ms) per connect+headers: oltre questo il gateway è "non raggiungibile" (default 15000). */
|
||||
connectTimeoutMs?: number;
|
||||
/** Attesa base del circuit breaker dopo un fallimento definitivo (default 120000 = 2 min). */
|
||||
breakerBaseMs?: number;
|
||||
@@ -52,10 +52,10 @@ const CONFIG_DEFAULTS: MemoryConfig = {
|
||||
url: "https://qmem.enne2.net",
|
||||
apiKey: "",
|
||||
timeoutMs: 30_000,
|
||||
connectTimeoutMs: 2500,
|
||||
connectTimeoutMs: 15_000,
|
||||
breakerBaseMs: 120_000,
|
||||
breakerMaxMs: 600_000,
|
||||
breakerTripAfter: 2,
|
||||
breakerTripAfter: 3,
|
||||
correctMinScore: 0.6,
|
||||
localFallback: true,
|
||||
offlineQueue: true,
|
||||
@@ -147,7 +147,7 @@ export function breakerIsOpen(): boolean {
|
||||
/** Registra un fallimento; con `definitive` (connessione) apre immediatamente. */
|
||||
export function tripBreaker(reason: string, definitive: boolean, cfg?: MemoryConfig): BreakerInfo {
|
||||
const s = loadBreaker();
|
||||
const tripAfter = Math.max(1, cfg?.breakerTripAfter ?? CONFIG_DEFAULTS.breakerTripAfter ?? 2);
|
||||
const tripAfter = Math.max(1, cfg?.breakerTripAfter ?? CONFIG_DEFAULTS.breakerTripAfter ?? 3);
|
||||
s.failures = definitive ? Math.max((s.failures ?? 0) + 1, tripAfter) : (s.failures ?? 0) + 1;
|
||||
s.lastError = reason;
|
||||
s.lastChange = new Date().toISOString();
|
||||
@@ -249,7 +249,7 @@ export async function gatewayRequest(
|
||||
};
|
||||
if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
|
||||
|
||||
const connectMs = Math.max(200, opts?.connectTimeoutMs ?? cfg.connectTimeoutMs ?? 2500);
|
||||
const connectMs = Math.max(200, opts?.connectTimeoutMs ?? cfg.connectTimeoutMs ?? 15_000);
|
||||
const bodyMs = Math.max(500, opts?.timeoutMs ?? cfg.timeoutMs ?? 30_000);
|
||||
const url = `${cfg.url}${route}`;
|
||||
let lastError: unknown = null;
|
||||
|
||||
@@ -41,11 +41,11 @@ In quel caso:
|
||||
dalle sessioni pi + ultimo enrich): verifica prima dell'uso;
|
||||
- comandi: `/qmem:local status | import | find <query> | queue | flush | breaker [reset] | enrich | pull`
|
||||
(`import` dalle sessioni, `enrich`/`pull` dal gateway quando torna online);
|
||||
- **circuit breaker**: quando il gateway non risponde entro `connectTimeoutMs` (default 2,5 s) le
|
||||
- **circuit breaker**: quando il gateway non risponde entro `connectTimeoutMs` (default 15 s) le
|
||||
chiamate successive falliscono in ~0 ms **senza toccare la rete** per ~2 minuti (stato in
|
||||
`~/.local/share/pi-qmem/breaker.json`): il fallback locale è immediato. Un 5xx o un body lento
|
||||
sono invece "ambigui" (retry con `Retry-After`, apertura dopo 2 fallimenti). Reset:
|
||||
`/qmem:local breaker reset`.
|
||||
sono invece "ambigui" (retry con `Retry-After`, apertura dopo 3 fallimenti). Reset:
|
||||
`/qmem:local breaker reset` (CLI: `node scripts/qmem-sqlite.mjs breaker --reset`).
|
||||
|
||||
`qmem_store` accoda in locale: con il gateway giù il record entra nell'**outbox**
|
||||
locale (SQLite), è subito ricercabile (marcato ⏳) e viene inviato al gateway al
|
||||
|
||||
Reference in New Issue
Block a user