Back to "StyloBot Release Series: Ricercare e riparare la crescita senza limiti nel lungo periodo-Running M SK2NET Services"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Architecture ASP.NET Bot Detection Performance StyloBot

StyloBot Release Series: Ricercare e riparare la crescita senza limiti nel lungo periodo-Running M SK2NET Services

Monday, 01 June 2026

Lungi-servitivi in funzione tendono a spostarsi lentamente verso la memoria senza confini . Qui' ecco come scopro quel spostamentoM SK3 come penso di ripararlo senza scriverci sopraMSC4 e un esempio funzionante da StyloBotMska5 della strata di similitude dei vettori che l'ha portata dal MSSK6 GB sul Large Object Heap al sotto 6 MBMSM8

StyloBot

StyloBot Release Series

  1. Comportamento, Non identità: perché StyloBot modella i clienti comportamentalmente
  2. Comportamento-Aware ASP.NET UI: il server- restituisce la superficie su quel risultato di rilevamento.
  3. Trovare e riparare la crescita senza limiti nei servizi NET Long-Running .: la disciplina di affidabilità (esempio funzionanteM SK2 StyloBot)
  4. Comportamento-Conoscere l'interfaccia grafica del tipoScript: ExpressM SK1 Fastify, e componenti del browser
  5. L'architettura della Sidecar: come il motore di rilevazione si collega a stack non -.NET
  6. Imparare a diventare più veloce: il sistema di apprendimento adattativo, quattroM SK2 memoria a livello di livello+, e la cassa del verdetto
  7. Provare a fare ciò che non si ferma.: la disciplina di verificazioneM SK1 un file BDF guida la regressione , carico, e calibrazione
  8. StyloExtract - un convertitore locale di HTML per Markdown: l'HTML→Layere Markdown che si combina con il rilevatore , il walkeder bug lucidVIEW catturatoM SK3 e la catena di cibo per cani che lo rende onesto

L'esempio lavorato è la strata di vectore del StyloBot, ma la disciplina non è't StylobotM SK2specifici . Qualunque lungoMSC4running MST5NET service che accumula lo stato dal traffico alla fine cresce una classe di errori che poteteMSSK6t catturare nei test o in breve attività di caricoMSR7 Si mostra solo dopo giorni di traffico realeMSL8 Questo post è il manuale che uso per scoprirloMsl9

Il modello comportamentale si trova in Comportamento, Non identità; la superficie ASP.NET in Comportamento-Aware ASP.NET UI; fonte a github.comM SK1scottgal/stylobot.


La classe del problema.

Longi-servitivi in funzione accumulano. Ogni cassaM SK2 ogni learned store , ogni MSC4 noiMST5 mantendremo solo le ultime richieste NMSL6 il buffere è un piccolo accumulatoreMSR7 Ognuna sembra bene da solaMsl8 InsiemeM SR9 su un processo che è stato in funzione per una settimanaMSTR11 formano una forma di memoria nessun test si riproduce maiMstr12

  • Funziona bene in dev, CI, e il primo giorno di produzione.
  • gradualmente si sposta verso OOM, poi cade o inizia a caricare.

Lo prendi da qui. cercando deliberatamente., su un processo che ' è andato avanti abbastanza a lungo da far emergere la forma.

passo 1: Rivisioni periodiche di affidabilità (l'atto di guardare)

Il primo pezzo della disciplina è: "' non è tecnico, ., ma ' è l'ingresso del calendario."

Ogni poche versioni, smetto di aggiungere funzionalità e guardo solo il sistema in funzione sotto un traffico realistico, chiedendo Qualcuno di questi sembra sbagliato? Non c'è un errore specifico, nessun obiettivo. Guardare soloM SK2 I sistemi che funzionano si muovono silenziosamente ; non falliscono a voce alta, finché non fallisce catastroficamenteMST5 Se guardate solo quando qualcosa si rompeMst6 voiMSt7 avete già persoM st8

La più recente revisione ha catturato il bug di questo post è su:. Mostlylucid.BotDetection.Demo Il processo si trova a un residente 20 GB che è stato messo sotto un traffico di test sintetico. QuelloM SK2 è il tipo di numero che non dovrebbe sopravvivere a trenta secondi di attenzioneMSC3

Regola: Mettete un slot ricurrente sul calendario per leggere le vostre stesse misurazioni. Non riparare niente. Solo guardareM SK2

Step 2: Lasciate che i contatori vi sorprendano

L'outil giusto per "è qualcosa di sbagliato?" su un processo .NET è dotnet-counters. E'' gratis, è ' è già installatoM SK4 e vi dice la verità su quello che il vostro processo sta facendo proprio ora

Qualche comando dopo alla demo dello StyloBot:

dotnet.gc.last_collection.heap.size[loh]   13,393,217,096 bytes (13.4 GB)
dotnet.gc.heap.total_allocated              97 MB/sec
dotnet.exceptions[SqliteException]          4/sec

Quello' è abbastanza per sapere cosa c'è di sbagliato, senza leggere ancora nessun codice. Grande massa di oggetti Era di 13.4 GB e cresceva a quasi 100 MB/secM SK3 Qualcosa stava assegnando enormi oggetti in continuazioneMSL4 e il GenMSR5 GC non riusciva a riuscirci con loroMsl7

Un rapido rifresco sul LOH (perche' ogni .NET dev lo colpisce alla fine.

Il Large Object Heap è una delle più comuni sorprese la prima volta che si presenta un lungo service -running .NET . Vale la pena ripensarlo, perché quasi tutti lo incontrano in modo difficile.

  • Il GC ha tre generazioni (GenM SK1 Gen1, Gen+2) per brevi tipi normaliMSC4oggetti viventi . La maggior parte delle asignazioni vive e muore in Gen=0, che è economico di raccogliere
  • Qualunque cosa più grande di 85 KB non va in quelle generazioni. Va dritto verso l'acqua. Grande massa di oggetti.
  • Il LOH viene raccolto solo durante un Gen2 GC, che è la collezione più costosa .NET lo fa. GenM SK3 succede raramenteMSC4 e il tempo di lavoro cerca di evitarlo
  • peggior, di solito il LOH è non compacto quando viene raccolto; rilascia solo gli orizzontiM SK1 Quindi anche dopo un Gen2, il LOH diventa frammentato : hai spazio liberoMST4 ma èMSSK5 nel sbagliato MST6 buchi di dimensione per la prossima asignazioneMSS7 nuovi grandi oggetti estendono l'erba piuttosto che la riutilizzareMSM8
  • Effetto netto: continuare a assegnare grandi oggetti ad un ritmo stabileM SK1 e il tuo processo' la memoria sale su se gli oggetti sono ancora riferiti o menoMSC3 Sembra una perdita anche quando non è una cosa.

Le maggiori fonti di crescita casuale del LOH nei servizi reali .NETM SK1 nella mia esperienza:

Source Perché finisce sul LOH
JsonSerializer.Serialize(obj) che restituisce un string Un oggetto di MB di 100 diventa un stringo di UTF di \200 MB di -16 di , tutti in una sola asignazione.
MemoryStream Lasciate crescere senza limiti Il buffer interno si raddoppia oltre 85 KB e rimane sulla LOH
byte[] buf = new byte[n] per il grande. n Allocation diretta di LOH; molto comune nel fileM SK2network legge
List<T> che cresce dopo ~10 Referenza KM SK1elementi tipificati Raccogliture dell'array di supporto 85 KB e attrae sulla LOH SSK4
string.Concat / StringBuilder su grande testo Finale ToString() E' una sola grande assegnazione .
XmlSerializer / DataContractSerializer di grandi grafici La stessa forma del caso JSON

Se vi ricordate solo una regola del pollice: essere sospettosi di qualsiasi percorso di codice che produce un singolo grande oggetto contiguo su un timer o per richiesta. Quella ' è la forma che rovina i lunghi servizi di gestione, e quella ' è esattamente la forma in cui stiamo per trovare nel StyloBot.

flowchart LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef store fill:none,stroke:#f59e0b,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    A["Per-request handler<br/>Add to collection"]:::input --> B["Long-lived List / Dict / Buffer"]:::store
    C["Timer / autosave<br/>every N min"]:::async --> D["JsonSerializer / MemoryStream<br/>single &gt;85 KB allocation"]:::problem
    B --> D
    D --> E["Large Object Heap"]:::problem
    E --> F["Gen2 GC<br/>infrequent, expensive"]:::async
    F --> G["LOH not compacted by default<br/>fragments build up"]:::problem
    G --> H["Process RSS marches upward"]:::problem

strumenti che aiutano a diagnosticarlo:

  • dotnet-counters per numeri vivi (il tasso di LOH sopra ).

  • dotnet-gcdump e dotnet-dump per immagini che si possono aprire in Visual Studio o PerfView.

  • PerfView stesso per tracciare le azioni fino ad un stack.

  • Profilisti JetBrains CLI (quello che ho usato per questo lavoro di riprogettamento). Ora impacchettato come un vero .Tool globali NETM SK3 così senza testa / dal remoto |/ La profilazione del tempo di run-time CI non ha bisogno di un GUI da Rider.

    • JetBrains.dotMemory.GlobalTools: attaccare, scattare un fotogramma di memoria , aprire il .dmw lo spazio di lavoro più tardi per vedere quali radici conservate mantengono le azioni LOH.
    • JetBrains.dotTrace.GlobalTools: la stessa forma per campionare / il tracciamento ♫/ i dati della linea temporale ♫ . ♫ Usate questo quando avete bisogno di una pila piuttosto che di un contatore ♫
    dotnet tool install -g JetBrains.dotMemory.GlobalTools
    dotnet tool install -g JetBrains.dotTrace.GlobalTools
    
    dotMemory get-snapshot <pid> --save-to-dir=./snapshots
    dotTrace attach <pid> --profiling-type=Sampling --timeout=60s --save-to=./trace.dtp
    

    Divisione del lavoro per questo ritocco: dotnet-counters mi ha detto che il problema era la LOH; l'immagine della dotMemory mi ha raccontato che il timer autosave era la causa.

Ci sono scatole di fuga se ne avete davvero bisogno. GCSettings.LargeObjectHeapCompactionMode = CompactOnce forza una compactazione LOH di uno-off. RecyclableMemoryStream da Microsoft pools buffers per evitare l'allocation in primo luogo. Entrambi sono plastiM SK1 La vera soluzione è quasi sempre fermare la produzione dell'oggetto gigante in primo posto.

Torniamo al diagnostico.

Quindi, ogni volta che si vede una crescita costante del LOH in un lungo servizio, la domanda è quasi sempre la stessa. Che cosa'ha a disposizione oggetti molto grandiM SK1 su quale cadenza, e perché? La forma della risposta (timerM SK1 handler di richiesta? serializzatoreMST3 pool di tamponiMst4 ti dice dove guardare nel codiceMSST5

Le eccezioni contro importavano anche. 4/sec Di SqliteException è abbastanza bassa da non apparire nei log che qualcuno legge, ma abbastanza alta per dirti che un percorso di codice sta quietamente riprovando. Quello ' è il tipo di indizio che la revisione periodica rileva che l'incidente non reagisce maiM SK3

Regola: quando qualcosa sembra sbagliato, conferma con dotnet-counters prima di indovinare. I numeri ridurranno il raggio di ricerca in un ordine di grandezza.

passo 3: sbagliatoM SK1odore di abstrazione (esempio funzionante: HNSW per il cachingMSC4

Questo è il punto in cui la disciplina diventa interessante, perché la tentazione è sempre di Pieghe il sintomo piuttosto che mettere in discussione l'astratto.

Quick vocab (per le prossime sezioni)

  • Vector: un'array di float di dimensione fixed- - eM SK3gMSC4 float[64]. Ogni slot è una proprietà misurata della richiesta ( conteggio delle sequenze di caratteri, timingM SK3 famiglia IPMSC4 ecceteraMNK5 Due richieste che " sembrano similiMRK7 producono vectori che sono vicini l'un all'altro in M64- spazio dimensionaleMMK9 Non ci si soprascende. float[].
  • La somiglianza dei vettori: di solito La somiglianza di cosine. - la cosine dell'angolo tra due vettori. più vicina a 1.0 = più simileM SK4 La matematica reale è un prodotto di punti diviso da due normeMSC5 una procedura chiamata
  • Avvicinamento più vicino (ANNM SK1: "given this vector, find the closest N out of millionsM SK3 fastMST4 BruteMSV5force comparison is OMSC6NM SV7 per queryMSP8 Gli algoritmi ANN ottengono risultati sub-MSV9millisecondi al costo occasionalmente di perdere il Veramente. Match più vicino.
  • HNSW: uno di quegli algoritmi ANN. Si costruisce un grafico stratificato dove la strata superiore ha alcuni pozziM SK2 nodi connessi e strati inferiori riempiono i dettagli . Si entra in alto e si zooma inMSC4 Eccellente per l'indexazione di una grande stabile set of vectors. Non progettato per " ogni richiesta aggiunge uno e quelli vecchi sparisconoM SK2 - il punto rilevante di tutto questo postMSC4
  • Centroide: la media di un gruppo di vettori - un vettore che riassume il cluster. Se avete 10,000 vettori che sembrano tutti molto similiM SK4 potete buttarli via e tenerne uno centroideMSC5 il centroide è un sostituto perdente ma compactoMNK6 Le strategie di compactazione usano questo per trasformare molta storia cruda in una piccola storia di riassuntoMMK7

Per StyloBot, la crescita del LOH ha portato a tre classi che erano identiche strutturalmente: nel processoM SK2. HNSW (Hierarchical Navigable Small World) grafici (originale Malkov & carta Yashunin) usata per la ricerca della somiglianza sulla firmaM SK1 sessione, e vettori dell'intenzione . Ognuna aveva un campo comeMSC4

private readonly List<float[]> _graphVectors = new();

Ognuno è stato alimentato da un handler di apprendimento che si è sottoscritto a LearningEventType.FullDetection, che spara su ogni richiesta HTTP. Ogni richiesta ha aggiunto un vettore . Ogni domanda. Non c'è stata evizioneM SK3

E ogni cinque minuti, un timer di autosave serializzava l'intero grafico a JSON e lo scriveva al disco:

private readonly TimeSpan AutoSaveInterval = TimeSpan.FromMinutes(5);

A scala demo, signatures.vectors.json Era 104 MBM SK1 In produzione: intent.meta.json a 70 MBM SK1 intent.vectors.json La serializzazione JSON crea una sequenza contigua nella memoria a 100+ MB. Quella sequence va dritta al LOHM SK4 Tre indice , ogni cinque minutiMSC6 che cresce continuamenteMNK7

flowchart LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    R["HTTP request"]:::input --> H["LearningHandler<br/>FullDetection event"]:::async
    H --> L["List&lt;float[]&gt;<br/>graph vectors<br/>(no eviction)"]:::problem
    L -.5 min timer.-> J["JsonSerializer.Serialize<br/>~100 MB string"]:::problem
    J --> LOH["Large Object Heap"]:::problem
    L -->|grows every request| L
    LOH -.fragmentation.-> RSS["Process RSS climbs"]:::problem

Ora: la soluzione facile è aggiungere un cappo. MaxVectors = 10_000, Evacuazione dell'esercito dell'LRU, l'accompagnarlo . Avrebbe funzionatoM SK3 I numeri sarebbero scesiMSC4 il pannello sarebbe andato beneMST5 Avremmo anche sbagliato MST6

HNSW è una tecnologia eccezionale e la uso deliberatamente altrove (Self-Database di Vector Hosted con Qdrant, La ricerca e l'indexazione ibride di RAG, GraphRAG Minimum Viable). Pinecone, Invecchiare, pgvector, e Qdrant tutti lo usano internamente. Ma è un indice di corpus, non una cacheM SK3 e quello che il StyloBot aveva veramente bisogno era una cache "per ogni impronta digitale attivaM SK1 tenere una piccola finestra di vettori comportamentali recenti così che la rilevazione possa confrontare la richiesta attuale con il comportamento passato da quelli simili." Le impronte digitali del bot si ripetono (stay hot);le impronte digitales umane non lo fannoM SK2t (evict Semplice quanto possibile post ha classificato l'indice HNSW del processo in - come una caratteristica.

C'è' un rapido test per l'odore : se immaginate di schiacciare una copertina rigida sulla struttura, il risultato è semanticamente ancora la cosa che volevateM SK3 Una cassa HNSW con una costante agitazione non èMSC4un indiceMST5 maMST6un cattivo cassa indossando un indice MST7il vestitoMst8

Regola: Quando si vede una crescita senza limiti, la domanda non è: "come capiamo questa struttura?" Ma " è la struttura giusta per quello che stiamo facendo. La prima nasconde il sintomo.

Passo 4: Prendete la forma giusta, non solo una cappotta

Una volta che'hai dato il nome a un'abstrazione sbagliata, la sostituzione di solito si scrive da sola . Per StyloBot erano due stratiM SK3

Layere calda: un confinato BoundedVectorCache<TEntry>, un rivestimento sottile intorno. ConcurrentDictionary con accesso-evizione prioritaria di frequenzaM SK1Il punteggio della retenzione dà ai bot-inträgi classificati un peso di sopravvivenza 2x, così che il cache stessoMSC5organizza intorno al modello di trafficoMNK6

retentionScorer: (_, entry) => entry.WasBot ? 2.0 : 1.0

Layere persistente (FOSS): tre nuove table SQLite (signature_centroids, session_centroids, intent_centroids) immagazzinamento di centroidi compressi da un nightly VectorCompactionService. Vectori immagazzinati come float crudo32 gocce usate MemoryMarshal.AsBytes:

internal static byte[] PackFloats(float[] v) =>
    MemoryMarshal.AsBytes(v.AsSpan()).ToArray();

Serializzazione binaria Compacta. Niente 100+ String JSON MBM SK2 Nient'altro LOH .

Nota del livello di memorizzazione. SQLite è il FOSS / singolo- backend binario - la giusta chiamata per " lanciare l'exe su un Pi e andarseneM SK4 Il StyloBot commerciale usa PostgreSQL con pgvector, che vi da una ricerca corretta di somiglianza indice (HNSW/IVFFlat all'interno della base di dati. - su un corpo stabile , dove appartiene l'HNSW, scala orizzontaleM SK3 e stato condiviso in tutta la flottaMSC4 Stessa regola architettonicaMNK5 backend diversoMMK6 sqlite-vss è una pietra miliare se si supera SQLite ma si vuole rimanere unito-binarioM SK1 l'aumento più pulito è il Postgres tier.

La ricerca di somiglianza sulla strata persistente FOSS è brutta-force cosine su tutte le righe, accelerata con SIMD M SK2System.Numerics.Tensors.TensorPrimitives). A scala compactaM SK1centroide ( centinaia a qualche migliaio di linee), una scansione completa richiede mSSK4 ms su un PiMSC5 QuelloMST6la fineMSL7 questo funziona solo in manipolatori di fondoMSR8 mai sulla strada veloce per la rilevazioneMsl9

Una nota su L1/LM SK1 compaction, dal momento che "compazione" sta facendo un lavoro reale in questa sezioneM SK3 Il servizio di compactazione funziona di notte e riduce la storia in due passaggiMSC4

  • L1: prendiamo tutti i vectori grezzi scritti oggiM SK1 rimpiazzamo quelli che sono molto simili, e sostituiamo ogni gruppo con un centroide.
  • L2: prendono LM SK1 centroidi che sono simili nei giorni, / settimane, e li uniscono di nuovo,. aggressivi,M SK4 si verifica meno spesso,; riduzioni di grande dimensione,MSC6altri ~10x). Ciò che sopravvive a L'2 è la forma a lungo termine del traffico, non il rumore transitorio.

Questo è lo stesso schema LSM-Motori di stoccaggio degli alberi (RocksDBM SK2 LevelDB , CassandraMSC4 uso per le SSTablesMSSK5 livello | 0 contiene dati recenti corretti |- dati ristretti |MSC8 livelli inferiori contieneno dati compressi | MSC9 | dati sommizzati | SMC10 | Borrowing it works because the access pattern is the same | : | la maggior parte dei lettori colpisce i dati più recenti | SSK12 | una piccola frazione dei lettori ha bisogno della coda lunga | МSK13 | non c'è niente di buono nel mantenere ogni fila nera per sempre |

flowchart LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef store fill:none,stroke:#f59e0b,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px

    R["HTTP request"]:::input --> FP["Fast path<br/>BoundedVectorCache.TryGet"]:::good
    FP -->|hit| S["Use similarity signal"]:::good
    FP -->|miss| N["null<br/>other detectors still run"]:::good
    R -.post-response.-> BG["Background learning handler"]:::async
    BG --> DB["SQLite (FOSS)<br/>or Postgres+pgvector (paid)"]:::store
    BG --> WARM["Warm cache for next time"]:::async
    WARM --> FP
    NC["Nightly VectorCompactionService<br/>L1 then L2"]:::async --> DB
    DB --> NC

La forma generale (Cache caldo confinata per lo stato stabile + archivio persistente compacto per la storia + un compactore periodico tra loro) si presenta più e più volte in sistemi di apprendimento a lungo -funzionano . vale la pena tenerlo nella vostra cassetta di attrezzi. Quando qualcuno si rivolge al HNSW o al pgvector per un carico di lavoro che '\ è in realtà una cache \ ,\ questo è il più economico \ ,\ la cosa più semplice per cui avrebbero dovuto arrivare .\

Regola: Preferisce la struttura di dati più semplice che corrisponda al il modello del tempo di esecuzione, non quello che corrisponde al set di dati ' la dimensione sulla carta.

passo 5: Rendere il percorso veloce tollerare gli errori

Una cosa sottile, perché è una questione di contenzione piuttosto che di aggiunta.

Se il percorso di aggiustazione lento-include un'osservazione della base di dati, la tentazione è di fare un blocco del percorso veloce su di esso . DonM SK3t. Il percorso veloce di rilevamento in StyloBot è sincronizzatoMSC5 e il controllo della somiglianza non è un lookup di dizionario che blocchi l'esempio.

if (!_cache.TryGet(signatureId, out var entry))
    return null; // no signal this request - other 48 detectors still run

Una sbagliosa significa Non c'è segnale di somiglianza in questa richiesta.. Gli altri rilevatori continuano a funzionare. Un handler di fondo interroga SQLite dopo che la richiesta è completata e riscalda il cache per la prossima volta ; il percorso veloce non blocca mai una domanda di databaseM SK3

Questo è il punto in cui la maggior parte del caching va storto: le persone costruiscono un cache, fanno il fallback sincronizzato " per la correttezzaM SK3 e il percorso di errore diventa un MSC4ms pMST5 spikeMst6 Il cache era stato progettato per rendere le cose più velociMSt7 invece ha peggiorato il peggiore casoM st8

Regola: se puoi'non tollerare un errore, non lo haiM SK2non c'è una cassa . hai una fila fantasticaMSC4

passo 6: Rivisione ogni anno. accumulatore, non solo quello forte

Una volta che avete trovato una struttura senza confini, assumete che ci siano altre.

L'audit è meccanico. Per ogni lungo dizionario-per un dizionaio viventeM SK2 lista , o settaMSC4 Che cosa limita la sua dimensione, chi decide quando si lasciano le entrate, e qualeM SK2 è il caso peggiore sotto un traffico ostile ? Se potete' non rispondere a tutti i tre in una sola frase, è un'infiltrazione . M SK4La stessa disciplina che la Signali efemerali modello: ogni cosa che si accumula deve decadereM SK1 estrarre , o compacto.)

Per StyloBot, la maggior parte degli accumulatori erano già buoniM SK1distretti:

  • EphemeralPatternReputationCache: cella rigida alle 10,000 entrate con decasione di fondo e estinzione delle LRU SSK2meccanica della decazione NeutralSuspectConfirmedBad la macchina dello stato, e l'isteresia assimetrica vivono in Imparare a diventare più veloce)
  • BehavioralPatternAnalyzer: IMemoryCache con per-limiti di identità (50 percorsiM SK3 100 timing, \15-min TTL
  • DriftDetectionHandler: 10,000 campioni
  • SessionEscalationService: 35-minuta TTL con timer-evizione guidata

C'è bisogno di attenzione oltre le classi HNSW: MarkovTracker._cohortBaselines. Il La catena di Markov Il tracciatore mantiene le matrice di transizione di base per la cohorta (separate dalle catene di firma per l'-, che avevano già avuto l'evizione delle LRU. MaxTrackedSignatures). Le linee di base della cohorta (una per una cohorte di traffico come "datacenterM SK3nove"o M"residenzialeMSC6intornamento~") non c'è stata alcuna evizione~. La regola R:eliminare le cohorti più fredde P(la minore transizione totale~M SK11 quando il vocabolario supera SelfMaintenanceOptions.MarkovCohortSize.

Regola: ogni singolo tono di raccolta risponde a tre domande o è un errore.

passo 7: Rendere ogni confine configurabile

Il prossimo modo di fallimento dopo "no bound" è "bound cheM SK3 è sbagliato per questo hardware MaxEntries = 10_000 va bene finché qualcuno non gestisce il vostro servizio su un Pi4, o in un contenitore con 256 MBM SK2

Per StyloBot, ogni limite è atterrato sotto un singolo. SelfMaintenanceOptions bloccare. appsettings.json. I predefinimenti funzionano per un server standard. Per l'hardware limitato ci sono LowMemory Presizione statica:

public static SelfMaintenanceOptions LowMemory => new()
{
    SignatureCacheSize  = 1_000,
    SessionCacheSize    = 500,
    IntentCacheSize     = 300,
    MarkovCohortSize    = 2_000,
    CacheSlidingExpiration = TimeSpan.FromHours(1),
};
builder.Services.AddBotDetection(opts =>
{
    opts.SelfMaintenance = SelfMaintenanceOptions.LowMemory;
});

O attraverso. appsettings.json per l'ambiente-Tuning specifico:

{
  "BotDetection": {
    "SelfMaintenance": {
      "SignatureCacheSize": 1000,
      "SessionCacheSize": 500,
      "IntentCacheSize": 300,
      "CentroidRetentionDays": 14
    }
  }
}

Il punto più profondo, :, un confine che ' ha fissato in un posto è qualcosa di cui gli operatori possono ragionare. const int Le declarazioni sono qualcosa su cui nessuno può ragionare.

Regola: ogni confine che l'operatore potrebbe voler cambiare per la vita del loro hardware in un singolo blocco di configurazione, non sparso attraverso la base di codici.

Come appare "fixed"

Per StyloBot in particolare (FOSS buildM SK1 LowMemory preset):

Componente Prima Dopo S
Signature HNSW index Unbounded LOH
Indice di sessione HNSW Unbounded LOH ~258 Cache calda KB SSK4
Indice dell'intenzione HNSW LOH senza confine SSK2 ~43 Cache calda KB S
I bufferi di auto-saldaggio JSON 100-500 MB LOH ogni SSK3 min
Linee di base della cohorta di Markov Senza confine SSK2 ~1 MB
Layere totale del vettore 13+ GB LOH <6 MB

Il modello di rilevamento non è cambiato. Che cosa è cambiata? dove ci sono prove di somiglianza. e quando è stato permesso di influenzare il percorso veloce.. Centroidi sopravviventi ricominciano a relazionare in SQLite (FOSS) o PostgresM SK3pgvector (paidMSC5 la compressione notturna produce ancora LMSSK6L\2; nessuno di questi richiede memoria senza confini

Su un Pi4 con il LowMemory preset, la costruzione FOSS si trova sotto 500 MB RSS dopo il riscaldamentoMSC3 indefinitamenteM SK4 Postgres pagatoMNK5 deploiementi supportati ereditano lo stesso caldoMRK6 disciplina di archiviamentoMMK7 la strata persistente scale semplicemente orizzontale invece che vivere vicino al processoMEK8 in ogni casoMZK9 stabile prevedibileMKK10 immagazzinatura di statoMGK11 raggiungibile dopo il caloreMKZ12 indipendentemente dal tempo di rialzo o dal trafficoMDK13

La lezione generale.

L'aggiunta di una copertina limita la memoria senza cambiare l'architettura. La falsa astrazione rimane sbagliata; il sintomo diventa più silenziosoM SK2 La persona successiva che tocca il sistema eredita una struttura che quasi funziona , che è peggiore di quella che ovviamente non funziona

Il modello che si riproduce nei sistemi di apprendimento a lungo-running learning systems:

  • Cappare il percorso caldo.: confinatoM SK1 in-memory , progettato per tollerare i mischiMSC4 politica di deportazione armonizzata con la carico di lavoro
  • Comprimere la storia.: archivio binario persistente compacto , compresso periodicamente, accessibile fuori dal thread della richiesta
  • Controlliamo tutto il resto.: ogni accumulatore risponde a cosa lo restringe , ciò che l'evicisce M SK2 cosa' è il caso peggiore
  • Centralizzare i bottoni.: un blocco di configurazione, non 15 constSì.
  • Programmare la ricerca: il bug esiste solo nel sistema in corso

Fixare la forma, non il sintomo.


Ancora una lettura.

StyloBot

HNSW e ricerca dei vettori in questo blog

Riferizioni esterne

Source per la implementazione: github.comM SK1scottgal/stylobot. Motore viventeM SK1 Armatura dashboard, e controlli commerciali a stylobot.net.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.