# StyloBot-utsläppsserien

*långvarig service tenderar att sakta drifta mot ett ogränsat minne. Här är det. Detta är hur jag ser den driften. Det här är vad jag tänker på att fixa utan att skriva på den. Det är ett fungerande exempel från StyloBot. Masket av vektors likhet som tog den från GB på Big Object Heap till under MB.*

[<img src="/articleimages/stylobot-logo.svg" alt="StyloBot" width="120" />](https://www.stylobot.net)

> **StyloBot-utsläppsserien**
> 
> 1. [**Behavior, Inte Identitet**](/blog/stylobot-fingerprint): varför StyloBot modellerar klienter beteendemässigt
> 2. [**Behavior-Är medveten ASP.NET UI**](/blog/behaviour-aware-ux): servern - skickade ut ytan över det upptäcktsresultatet
> 3. **Att hitta och lösa gränslös tillväxt i långtids-, -- och / eller körande- -.-nätverk**: tillförlitlighetsdisciplinen ( fungerande exempel
> 4. [**Behavior-Akvis typscript-UI**](/blog/typescript-sdk): Express , Fastify, och webbladerkomponenter
> 5. [**Sidecar-arkitekturen**](/blog/sidecar-architecture): hur detektoren kopplas till icke--.NET-skivor
> 6. [**Att lära sig bli snabbare**](/blog/stylobot-release-learning): adaptiv inlärningssystem, ,, fyra, M SK2, övningsminnet, ,, och bedömningsキャッシュen
> 7. [**Att testa det som inte stannar kvar**](/blog/stylobot-release-nondeterministic-testing): kontrolldisciplinen : en BDF-fielddriver regression M SK2 belastning , och kalibrering
> 8. [**StyloExtract - en lokal lärande HTML omvandlare till Markdown**](/blog/stylobot-release-styloextract): HTML, →, Markdown-lagret som paras med detektorn, ,, walkersbug lucidVIEW som fångade, M SK3, och den hundföda-loopan som gjorde det ärligt

<!--category-- ASP.NET, StyloBot, Bot Detection, Performance, Architecture -->
<datetime class="hidden">2026-06-01T10:30</datetime>

Det fungerade exemplet är StyloBots vektorlayer, , men disciplinen är: ' t Stylobot, -, specifikt, M SK3, Allt långt fungerande -, NET-tjänster som samlar upp tillstånd från trafiken växer till en klass av bugs du kan ta tag i.

Behavioralmodellen finns i [Behavior, Inte Identitet](/blog/stylobot-fingerprint); ASP.NET ytan i [Behavior-Är medveten ASP.NET UI](/blog/behaviour-aware-ux); källa på [github.com/scottgalM SK2stylobot](https://github.com/scottgal/stylobot).

[TOC]

---


## Problemets klass

lång tid.-. körde tjänster samlas in. .. varje cache, ,. varje lärandebutik. ,. varje . M SK4. vi, '. vi håller bara de sista N-ansökningarna. Mska6. buffern är en liten samlare. Mske7. Var och en ser bra ut på egen hand. M Ska8. Tillsammans med Mska9. i en process som varit igång i en vecka. M ska11. de skapar ett minne som inget test någonsin återskapar.

- fungerar bra i dev, CI, och första dagen i produktion
- flyter gradvis mot OOM, sen kraschar den eller börjar bläddra

Du fångar det här med *medvetet att leta efter den*, på en process som

## steg 1: periodiska tillförlitlighetsbedömningar (synningen

Det första stycket i disciplinen är: "'", inte tekniskt, . , det är:

Varje få release , Jag slutar att lägga till egenskaper och tittar bara på det som körs under realistisk trafik, frågar *ser något av det här ut fel?* Inga specifika bugs, inga mål.

Den senaste recensionen fångade bugs den här postan är om: `Mostlylucid.BotDetection.Demo` process som sitter vid en GB-resident som ställs under syntetisk testtrafik.

**Reglern:** sätter en återhämtande lucka på kalendern för att läsa dina egna mätvärden. Att inte fixa något

## steg 2: Låt bokstäver överraska dig

Det rätta verktyget för " är något fel på ?" [`dotnet-counters`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters). Det, ', är gratis, ,, det, ', är redan installerat, ,, och den berättar sanningen om vad din process gör just nu.

Några kommandor senare på StyloBot demo:

```
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
```

Det är tillräckligt för att veta vad som är fel utan att läsa någon kod än. [Den stora objekthaufen](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap) var på 13.4 GB och växte på nästan 100 MB/secM SK3 Något allocaterade enorma objekt kontinuerligt

### En snabb uppvärmning på LOH ( eftersom varje | . | NET dev träffar detta i slutändan

Den stora objektmasken är en av de vanligaste överraskningar när du först profilerar en lång service.

- GC har tre generationer (Gen0, GenM SK2 Gen+2) för normala korta apparater - levande objekt . De flesta allocationer lever och dör i gen+0, vilket är billigt att samla in
- **Inget större än 85 KB går inte in i dessa generationer** Den går rakt ut på **Den stora objekthaufen**.
- LOH samlas bara under **Gen2 GC**, som är den mest dyra samlingen .NET gör
- värre, standardmässigt är LOH **inte kompakt** när den är samlad, ;, så frigör den bara luckorna.., så även efter en gen, 2,, blir LOH fragmenterad.:, så har man fri plats.M SK4, men det är ' som är fel. -, stora hål för nästa fördelning.MSC7, nya stora objekt sträcker ut högen istället för att återanvända den.
- Nettoeffekt.:. Håll till att fördela stora objekt i en stabil takt.,. Och din processor. '. minnesavtrycket sträcker sig uppåt oavsett om objekten fortfarande är refererade.M SK3. Det ser ut som ett utbrott även när det inte är det.

De största orsakerna till oavsiktlig LOH-ökning i verkliga .NET-tjänster

| källa ♫ | ♫ Varför hamnar det på LOH ♫
|---|---|
| `JsonSerializer.Serialize(obj)` återvänder en `string` | Ett ♫ 100 ♫ objekt blir ett ♫200 ♫ MB UTF ♫
| `MemoryStream` du låter växa obegränsad | Internal buffer dubbleras förbi 85 KB och stannar på LOH |
| `byte[] buf = new byte[n]` för stora `n` | Direkt LOH-alokation
| `List<T>` som växer förbi ~10 Kreferens | - | skriftippade föremål || | ryggradsskivan korsar ♫ 85 | KB och landar på LOH | МSK4
| `string.Concat` / `StringBuilder` över större text | slutligen `ToString()` är en enda enorm fördelning |
| `XmlSerializer` / `DataContractSerializer` av stora diagram | Samma form som JSON fallet |

Om du bara minns ett tumregler: **vara misstänksam mot vilken kodsträng som producerar ett enskilt, sammanhängande stort objekt på en timer eller per begäran.** Att "'" är den form som förstör långa tjänster, och det är precis den form vi kommer att hitta i StyloBot.

```mermaid
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
```

verktyg som hjälper att diagnostisera det:

- [`dotnet-counters`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) för riktiga siffror (LOH-mätaren ovanför ).

- [`dotnet-gcdump`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump) och [`dotnet-dump`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump) för kortbilder som du kan öppna i Visual Studio eller PerfView.

- [PerfView](https://github.com/microsoft/perfview) själv för att spåra fördelningarna ner till en stapel.

- **JetBrains CLI profiler** ( vad jag faktiskt använde för den här omställningen
  
  - [`JetBrains.dotMemory.GlobalTools`](https://www.jetbrains.com/help/dotmemory/Command-Line_Profiler.html): ansluta , ta ett minnessnapshot , öppna `.dmw` arbetsyta senare för att se vilka bevarade rötter som håller LOH-aloktionerna.
  - [`JetBrains.dotTrace.GlobalTools`](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html): samma form för att ta prover | / | spårning ♫ / | data från tidsramen ♫ МSK3 ♫ Använd detta när du behöver en stapel istället för en mätare ♫
  
  ```bash
  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
  ```
  
  Tilldelning av arbetskraft för denna omställning `dotnet-counters` berättade för mig att LOH var problemet.

Det finns flykthäckar om du verkligen behöver dem. `GCSettings.LargeObjectHeapCompactionMode = CompactOnce` tvingar en one-off LOH kompaktion [`RecyclableMemoryStream`](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream) från Microsoft pools buffer för att undvika fördelningen på första plats. Båda är plaster. Det verkliga lösningen är nästan alltid att sluta producera det gigantiska objektet på första Stelle

### Tillbaka till diagnosen

Så när du ser en stabil LOH-ökning i en lång service, så är frågan nästan alltid densamma. *vad "'" innebär att man ska fördela väldigt stora objekt på vilken tidpunkt, , och varför* Svarets form (timer? begäranbehandlare ? serialisator M SK3 bufferpool ?) talar om var man ska titta i kod

De undantagliga motsvarigheterna spelade också någon roll `4/sec` av `SqliteException` är tillräckligt låg för att inte dyka upp i journaler någon läser, men tillräckligt hög för att säga att en kodpfad tyst testar om igen.

**Reglern:** när något ser fel ut, bekräfta med `dotnet-counters` före att gissa. siffrorna kommer att förväxla sökradiet med en storleksordning

## steg 3: Den felaktiga - avstraktions lukten | ( | fungerande exempel | : | HNSW för kakning |

Det är här som disciplinen blir intressant, eftersom frestelsen *fläcka* symptomet snarare än att ifrågasätta abstraktionen

> ### Quick vocab ( för de kommande sektionernaM SK1
> 
> - **Vektor**: ett fixerat array av float `float[64]`. Varje lucka är en mätt egenskap av begäran. ( siffransräkning, , timing, M SK3 IP-familjen, ,, etc., .).. Två begäran som МSK6, som känns lika, MSC7, producerar vektorer som är nära varandra i den dimensionella utrymmet. `float[]`.
> - **Vektors likhet**: vanligtvis [kosine likhet](https://en.wikipedia.org/wiki/Cosine_similarity) - kosinusen av vinkeln mellan två vektorer . närmare 1.0 = mer liknär . Den faktiska matematiken är en punktprodukt som är delat av två normer
> - **Ungefär närmaste granne (ANNM SK1**: "given den här vektorn, hittar den närmaste N av miljonerM SK3 snabbtMSC4 Brutto-- krafts jämförelse är O~(~N~M SK7~ per query~ .~ ANN-algoritmer får sub--~millisekunderresultat till kostnaden att ibland missa *sant* närmaste match.
> - **HNSW**: en av dessa ANN-algoritmer . Den bygger en lagerad graf där den övre skivan har ett fåtal brunn M SK2 sammankopplade noder och lägre skivor fyller i detaljer . Du går in på toppen och zoomar in *stabila* rader vektorer. inte designad för " varenda begäran lägger till en och gamla försvinner " | | - den relevanta punkten i hela det här meddelandet
> - **Centroider**: genomsnittet i en grupp av vektorer ♫ - ♫ en vektor som summerar klustern ♫. ♫ Om du har ♫ 10,000 ♫ vektorr som alla ser ungefär likadana ut ♫ МSK4 ♫ så kan du kasta bort dem och behålla ett centroid ♫

För StyloBot ledde LOH-ökningen till tre klassier som var strukturellt identiska [HNSW](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world_graphs) (Hierarchisk navigerad liten värld[original Malkov & Yashunins pappers](https://arxiv.org/abs/1603.09320)) som används för likhetssökningar över undertecknande , sessie M SK2 och intentionsvectorn . Var och en hade ett fält som

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

Var och en fick hjälp av en lärande handledare som `LearningEventType.FullDetection`, som aktiveras **varje HTTP- begäran**. Varje begäran satte till sig en vektor . varje begäran . Det fanns ingen utfånganing

Och var femte minut, ,, serialiserade en själv sparande timer hela grafen till JSON och skrev den på disket.

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

På demoskala, `signatures.vectors.json` var 104 MB `intent.meta.json` på 70 MBM SK1 `intent.vectors.json` vid 51 MBM SK1 JSON serialisering skapar en sammanhängande raden i minnet vid ♫ 100+ ♫ MB ♫. ♫ Den raden går direkt till LOH ♫

```mermaid
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
```

Nu är det enkelt att lägga till ett kapsyl `MaxVectors = 10_000`, LRU utfånganing , skicka iväg det . Det skulle ha fungerat, . siffrorna skulle ha kommit ner, , brädan skulle se bra ut, M SK5 Det hade också varit fel

HNSW är en utmärkt teknik och jag använder den medvetet på andra ställen.[Self-Hosted Vector Databases with Qdrant](/blog/self-hosted-vector-databases-qdrant), [RAG Hybridsökning och indexering](/blog/rag-hybrid-search-and-indexing), [Minimalt tänkbara grafRAG](/blog/graphrag-minimum-viable-implementation)). [Pinecone](https://www.pinecone.io/learn/series/faiss/hnsw/), [Veva](https://weaviate.io/developers/weaviate/concepts/vector-index#hnsw-index), [pgvector](https://github.com/pgvector/pgvector#hnsw), och [Qdrant](https://qdrant.tech/documentation/concepts/indexing/#vector-index) alla använder den internt, men det är en indeks av ett corpus, inte ett kass, och vad StyloBot faktiskt behövde var en kass. *" för varje aktiv fingeravtryck , spara ett litet fönster av nyliga beteendevektorer så att spårning kan jämföra den nuvarande begäran mot det förflutna beteendet från liknande* Bot fingerprints repeti (stay hot); human fingerprint donM SK2t (evict [Så enkel som möjligt](/blog/botdetection-part3-as-simple-as-possible) post listade inprocessen HNSW-indexet som en egenskap

Det finns ett snabbt prov på lukten, : om du föreställer dig att sätta en hård kapsyl på konstruktionen. , är resultatet fortfarande semantiskt det du ville ha.

**Reglern:** när man ser en gränslös tillväxt, så är frågan inte *" hur kan vi täcka den här strukturen* men *" är den rätta strukturen för vad vi gör* Den första gömmer symptomet. Den andra hör vad det säger till dig.

## steg 4: Välj den rätta formen , inte bara en kapsyl

När du fått den felaktiga abstraktionen, ' och , så skriver ert substituerande vanligtvis upp sig själv.

**Heiß lager:** en begränsad `BoundedVectorCache<TEntry>`, en tunn förpackning runt [`ConcurrentDictionary`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2) med tillgång.

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

**Hållbar lager (FOSS):** tre nya SQLitetablar (`signature_centroids`, `session_centroids`, `intent_centroids`) lagring av komprimerade centroider från en nattlig `VectorCompactionService`. Vector som lagras som råfloat [`MemoryMarshal.AsBytes`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal.asbytes):

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

Kompakt binär serialisering. Nej 100+ MB JSON-strängarM SK2 Nej LOHMSC3

> **Storage tier note.** SQLite är den FOSS-versionen / eninlägsen - det binära bakvändet | | - | det rätta samtalet för | " | släppa exet på Pi och gå iväg | МSK4 | Commercial StyloBot använder PostgreSQL med [pgvector](https://github.com/pgvector/pgvector), ger dig rätt indexerade likheterssökningar *inuti databasen* - på ett stabilt korpus , där HNSW tillhör M SK2 horisontell skala, , och gemensamt tillstånd över en flod, [sqlite-vss](https://github.com/asg017/sqlite-vss) är en startplötslig punkt om du vill bli bättre på SQLite men vill vara enskild.

Sammalikhetssökningen på den FOSS-härdliga lagern är brute-force cosine över alla rader[`System.Numerics.Tensors.TensorPrimitives`](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.tensors.tensorprimitives)). På kompakta, -, centraliserade skalor, (, hundratusen till ett par tusen rader och ),, tar en full skanning ~1-2, ms på en Pi,

En anteckning om **Compaction:**, eftersom "kompakationen ♫ ♫ " ♫ gör verkligt arbete i den här sektionen ♫ . ♫ Kompaktionsservicen kör nattligt och reducerar historien på två gång ♫

- **L1**: tar alla de råda vectorna som skrevs idag , blandar de som är väldigt lika M SK2 och ersätter varje grupp med en centroid
- **L2**: tar L, 1, centroider som är liknande under dagar och veckor, och sammanfogar dem igen. ., Aggressiv, ;, händer mindre ofta, M SK5, storskalig minskning. ♫ (, något annat ♫

Det här är samma mönster LSM-trädslagsmotorer | ( | RocksDB |, | LevelDB ♫ , | Cassandra | ) | användar för SSTables | МSK5 | nivå | 0 | håller nygranna data |- | strukturerade data ♫ ; | lägre nivåer håller kompakta data ♪ , | sammanfattade data \ . | att låna det fungerar eftersom tillgångsmönstret är samma | : | de flesta läsarna träffar nydata ♪ ; | en liten bråkdel av läsarna behöver den långa stapeln | SMk13 | inget gynnar från att hålla varje rå rad för alltid |

```mermaid
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
```

Den generella formen (**bounded hot cache for the steady state + compact persistent store for history + a periodic compactor between them**) dyker upp om och om igen i långa, fungerande lärande system. . Det är värt att hålla det i sin verktygslåda.. När någon tar sig fram till HNSW eller pgvector för en arbetslast som ' i själva verket är ett kass.

**Reglern:** föredrar den enklaste datastrukturen som passar *starttidsmönster*, inte den som passar in i datauppsättningen

## steg 5: Få den snabba vägen att tolerera missar

Ett subtilt: ,, eftersom det handlar om tystnad snarare än addition.

Om den långsamma-fix-stigen inbegriper en databassuppsökning, är frestelsen att blockera den snabba vägen på den . DonMSC3 tM SK4 Detektorns snabba bana i StyloBot är synkroniseradMska5 och likhetskontrollen är en icke-blockerande ordbokuppsökning

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

En miss innebär *ingen likhetsignal för den här begäran*. De andra detektörerna fortsätter att fungera . En bakgrundsbehandlare frågar SQLite efter att beställningen tagit slut och värmer upp cachet till nästa gång

Det är här som de flesta kackersystem går fel på.: människor bygger upp en kacker, ,, gör fallbacken synkroniserad, ", för korrekthet, ",, och missstigen blir en "50", mps p, msp5, spike, M SK6. Cachet var tänkt att göra saker snabbare, ;, men istället gjorde det värsta fallet värre.

**Reglern:** om du kan' inte tolerera en miss

## steg 6: Revidering *varenda dag* accumulator, inte bara den högljudda

När du har funnit en ogränsad struktur så antar du att det finns andra. Den högljudda är den som bröt först.

Den är mekanisk. *vad gränsar dess storlek, ,, vem bestämmer när inskrywingerna lämnar, M SK1, och vad som är det värsta fallet under fiendtrafiken?* Om du kan svara på alla tre i en mening [Äphemerala signaler](/blog/ephemeral-signals) modell: allt som samlas måste förrutsna , utfånga, , eller kompakta

För StyloBot, var de flesta akkumulatorer redan bra-begränsadeM SK2

- `EphemeralPatternReputationCache`: hårda kapsyln vid 10,000 siffran med bakgrundsdeka och LRU-utskrävning `Neutral` → `Suspect` → `ConfirmedBad` statmaskin, och asymmetrisk hysteresi lever i [Att lära sig bli snabbare](/blog/stylobot-release-learning))
- `BehavioralPatternAnalyzer`: IMemoryCache med per-identitetsgränser (50 vägar
- `DriftDetectionHandler`:  10,000 mönster
- `SessionEscalationService`: 35-minuter TTL med timer-driven bortföring

En sak som behövdes uppmärksamhet bortom HNSW-klassen `MarkovTracker._cohortBaselines`. Den [Markovkedjan](https://en.wikipedia.org/wiki/Markov_chain) spåraren håller per-kohorts baseline-transition matriser M SK1separade från per -signaturkedjorn, som redan hade LRU utfångas vid `MaxTrackedSignatures`). Kohortens baseliner | ( | en per trafikkohort som |" | datacenter | - | ny | МSK4 | eller |" | beboestad ♫ - | återvänd ♫ `SelfMaintenanceOptions.MarkovCohortSize`.

**Reglern:** varenda enton-samtal besvarar tre frågor eller så: ' är en bug, what bounds it, what evicts it, ,, what,', what is its worst?

## steg 7: Gör varje gräns konfigurerad

Nästa avbrottsmodi efter "no bound" är "bound som ' inte stämmer för den här hårdvaran `MaxEntries = 10_000` är okej tills någon driver din tjänst på en Pi4, eller i en behållare med M SK1 MB.

För StyloBot, landade varje gräns under en enda `SelfMaintenanceOptions` blockera in `appsettings.json`. Standardstandarden fungerar för en standardserver `LowMemory` statisk preset:

```csharp
public static SelfMaintenanceOptions LowMemory => new()
{
    SignatureCacheSize  = 1_000,
    SessionCacheSize    = 500,
    IntentCacheSize     = 300,
    MarkovCohortSize    = 2_000,
    CacheSlidingExpiration = TimeSpan.FromHours(1),
};
```

```csharp
builder.Services.AddBotDetection(opts =>
{
    opts.SelfMaintenance = SelfMaintenanceOptions.LowMemory;
});
```

Eller via `appsettings.json` för miljön-specifik tunning:

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

Den djupare punkten, :, en gräns som sets på ett ställe är något som operatörer kan resonera med. `const int` deklarationer är något som ingen kan resonera med.

**Reglern:** varje gräns som operatören kanske vill ändra för sina hårdvara-liv i en konfigurationsblock

## Hur "fixed" ser ut

Speciellt för StyloBot (FOSS build, LowMemory preset

| Komponent | Innan | Efter |
|---|---|---|
| Handskrift HNSW-index ♫ | ♫ Ogränsad LOH ♫ МSK2 ♫ ♫
| Sessions HNSW-index | Ogränsad LOH ♫ ♫ | ♫
| Intens HNSW-index | | | Ogränsad LOH |
| JSON-autosave buffer | 100-500 MB LOH varje 5 min
| Markov-kohorts baseliner | | | Ogränsad |
| Hela vektorskivan | **13+ GB LOH** | **<6 MB** |

Detektormodellen är oföränderlig. Vad som ändrats är *där likheterna finns* och *när det tillåts att påverka den snabba vägen*. Centroider överlever återuppstartar i SQLite (FOSS) eller PostgresM SK3pgvector | (paid |); | nattlig kompaktion producerar fortfarande L | L

På en Pi4 med LowMemory preset , placerar den FOSS-byggnaden under 500 MB RSS efter uppvärmning M SK3 oändligt . Belönad Postgres MSC5 stödda utpostningar ärvt samma varmhet Mska6 kassdiskriminering Mske7 den bestående lagern skalas horisontellt istället för att bo bredvid processen M Ska8 på vilket sätt som helst Msaka9 förutsägbar stabiltMska10 tillståndets omlopp M ska11 nått efter upplärmningen Mcka12 oberoende av upptime eller trafiken Mikka13

## Den generella lärdomen

Att lägga till en kapsyl begränsar minnet utan att ändra arkitekturen.

Pattern som återhämtar sig i långa system

- **Capa den varma vägen**: begränsad, , i, -, minnet, M SK3, designad för att hantera misser, MSC4, bortskövning som är sammanlänkad med arbetsbelan
- **Komprimera historien**: kompakt binär bestående lager , periodiskt kompakt
- **Kontrollera allt annat**: varje akkumulator svarar vad som gränsar den, , vad som utfångar den,
- **Centralisera knobarna**: en config block , inte femton `const`s
- **Skedulera tittandet**: problemet existerar bara i det som körs

Fixera formen, inte symptomet.

---


## Fortsättning avläsning

**StyloBot-utsläpp serie**

- [Behavior, Inte Identitet](/blog/stylobot-fingerprint): beteendemodellet och Leidens klustering av denna lager är minnet för
- [Behavior-Är medveten ASP.NET UI](/blog/behaviour-aware-ux): ytan som tar upp detektionsavsikter i Razor
- [Att lära sig bli snabbare](/blog/stylobot-release-learning): det fyraste systemet för lärande och bedömande kass som den här minnesdisciplinen begränsar
- [Bot-detektorparti 2: Signature Pipeline och StyloBots arkitektur](/blog/botdetection-part2-signature-pipeline-and-stylobot-architecture): den ursprungliga upptäckten - maskinens arkitektur
- [Bot-detektorparti 3: så enkel som möjligt](/blog/botdetection-part3-as-simple-as-possible): den tvåM SK1 linjedrockan - i ( där HNSW-indexet i processen inleddes för första gången

**HNSW och vektorsökning i den här bloggen**

- [Self-Hosted Vector Databases with Qdrant](/blog/self-hosted-vector-databases-qdrant): HNSW djupdykning `M`, `ef_construct`, tunning
- [RAG Hybridsökning och indexering](/blog/rag-hybrid-search-and-indexing): HNSW som den stora halvan av ett hybridt återvinningsrör
- [Minimalt tänkbara grafRAG](/blog/graphrag-minimum-viable-implementation): DuckDB VSS HNSW fick dig (`array_cosine_distance` mot `array_cosine_similarity`)
- [Semantiska sökningar med ONNX och Qdrant](/blog/semantic-search-with-onnx-and-qdrant): HNSW konfiguration via Qdrants .NET-client
- [GraphRAG: Varför vektorsökningen bryts ner på Corpus-nivån](/blog/graphrag-knowledge-graphs-for-rag): varför rent ANN inte är sufficient på skala

**Externa referenser**

- [HNSW (Wikipedia)](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world_graphs)
- [Malkov  & Yashunin, *Effektiv och robust närmaste grannesökning med hjälp av hierarkiska navigerbara små världar*](https://arxiv.org/abs/1603.09320): det ursprungliga 2016 papperet
- [Pinecone: Faiss / HNSW förklarare](https://www.pinecone.io/learn/series/faiss/hnsw/)
- [Qdrant-indexerande koncept](https://qdrant.tech/documentation/concepts/indexing/#vector-index)
- [.NET Large Object Heap docs](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap)
- [`dotnet-counters` referensram](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters): redskapet som först nådde upp LOH: n
- [JetBrains dotMemory-commando-linns profiler](https://www.jetbrains.com/help/dotmemory/Command-Line_Profiler.html): CLIs globala \/ \ - \ verktygsminnet profiler som används för den här omställningen
- [JetBrains dotTrace-commando-lins profiler](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html): samma form för prestation / profilering av tidslinje
- [sqlite-vss](https://github.com/asg017/sqlite-vss): SQLite + Faiss, om och när den bestående ytan blir större än brute force

källan till implementeringen: [github.com/scottgalM SK2stylobot](https://github.com/scottgal/stylobot). Livmotorn , brädan, , och kommersiella kontrollen [stylobot.net](https://www.stylobot.net).