# StyloBot Release Series: Trouver et corriger la croissance sans limite dans les services de réseau longs

*Long-les services qui fonctionnent ont tendance à se déplacer lentement vers la mémoire non liée .Cette est la façon dont je trouve cette dérive,la manière dont j’envisage de le corriger sans en papierer sur elle–, et un exemple travaillé du StyloBotMSC5, couche de similitude vectorielle qui l’a ramenée de M SK6 GB sur le Large Object Heap à sous 6 MBMST8*

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

> **StyloBot Release Series**
> 
> 1. [**Comportement, Non identité**](/blog/stylobot-fingerprint): pourquoi StyloBot modele les clients de façon comportementale
> 2. [**Behaviour-Aware ASP.NET UI**](/blog/behaviour-aware-ux): le serveur - remet la surface sur ce résultat de détection
> 3. **Trouver et rétablir une croissance sans limites dans les services longs-Running .NET**: la discipline de fiabilité
> 4. [**Behaviour-Aware TypeScript UI**](/blog/typescript-sdk): ExpressM SK1 Fastify, et composants de navigateur
> 5. [**L'architecture de la Sidecar**](/blog/sidecar-architecture): comment le moteur de détection se connecte aux piles NET non -.
> 6. [**Apprentissage pour devenir plus rapide**](/blog/stylobot-release-learning): le système d'apprentissage adaptatif , quatre- mémoire de niveauM SK3 et cache des verdicts
> 7. [**Étudier ce qui ne va pas se tenir**](/blog/stylobot-release-nondeterministic-testing): la discipline de vérification : un fichier BDF conduit à la régressionM SK2 charge, et à l'étalonnage
> 8. [**StyloExtract - un convertisseur HTML d'apprentissage local à Markdown**](/blog/stylobot-release-styloextract): la couche de marquage HTML→ qui se joigne au détecteur , le bug lucidVIEW du fauteuil roulant qui a capté l’image

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

L'exemple travaillé est la couche de vector StyloBot, mais la discipline n'est pas't Stylo BotM SK2specificMSC3 Tout service longMST4running MSST5NET qui accumule l'état du trafic augmente finalement une classe d'erreurs que vous pouvezMst6t catch dans les essais ou les courts essais de chargementM st7 Il apparaît seulement après des jours de trafic réelMstr8 Ce post est le guide que j'utilise pour le trouverM str9

Le modèle comportemental se trouve dans [Comportement, Non identité](/blog/stylobot-fingerprint); la surface ASP.NET dans [Behaviour-Aware ASP.NET UI](/blog/behaviour-aware-ux); source à [github.comM SK1scottgal/stylobot](https://github.com/scottgal/stylobot).

[TOC]

---


## La classe du problème

Long-services opérationnels s’accumulent. Chaque cache, , chaque magasin d’apprentissage, , chaque M SK4nous, MSC5 nous garderons seulement les dernières demandes de N." le buffer est un petit accumulateur,. chacun semble bien sur son propre compte,. Ensemble,M SK9 dans un processus qui, depuis une semaine, a été mis à jour par ' , ils forment une forme de mémoire qu’aucun essai ne reproduit jamais

- fonctionne bien dans dev, CI, et le premier jour de production
- se déplace graduellement vers OOM, puis soit s'écrase, soit commence à naviguer

Vous l'apercevez *en cherchant délibérément*, sur un processus qui ' a duré assez longtemps pour que la forme apparaisse

## Étape 1: Examens périodiques de la fiabilité (l'acte d'examiner les données

Le premier élément de la discipline est :

Chaque quelques sorties, Je cesse d’ajouter des fonctionnalités et je regarde simplement le système en cours sous un trafic réaliste, demandant *Tout cela semble-t-il mauvais?* Aucune erreur spécifique, aucun objectif . Je regarde simplement. Les systèmes qui fonctionnent se déplacent silencieusementM SK3 ils ne faillent pasMSC4ils n’échouent pas fortement jusqu’à ce qu’ils échouent de façon catastrophiqueMST5 Si vous regardez seulement lorsque quelque chose se romptMst6 vousMSt7vous avez déjà perduM st8

La révision la plus récente a capté le bug ce post est sur: `Mostlylucid.BotDetection.Demo` Processus de sitting at a 20 GB resident set under synthetic test traffic . That' is the kind of number that should not survive thirty seconds of attention

**Règle:** Mettez un espace récurrent sur le calendrier pour lire vos propres mesures.

## Étape 2: Laissez les compteurs vous surprendre

L’outil approprié pour " est quelque chose de mal ?" sur un processus \.NET est [`dotnet-counters`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters). Celle-ci, ', est gratuite, c'est déjà installée ' ce qui vous dit la vérité sur ce que votre processus fait actuellement

Quelques commandes plus tard sur le 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
```

Que' est suffisant pour savoir ce qui est errant, sans lire de code encore. [Grand trou d'objets](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap) a été de 13.4 GB et s’est accrue à près d’M SK1 MB / sec. Et quelque chose affectait des objets énormes continuellementMSC4 et le GenMSSK5 GC ne pouvait pas se tenir avec euxMST6

### Un rafraîchissement rapide sur le LOH ( car chaque .NET dev touche ce dernier en bout de ligne

Le Large Object Heap est l’une des surprises les plus courantes lorsque vous présentez pour la première fois un long service -running .NET. Il vaut la peine de le redéfinir parce que presque tout le monde l’obtient de la manière difficile.

- Le GC est composé de trois générations :
- **Tout ce qui est plus grand que 85 KB ne va pas dans ces générations.** Il va directement sur le **Grand trou d'objets**.
- Le LOH n'est recueilli qu'au cours d'un **Gen2 GC**, qui est la collecte la plus coûteuse .NET fait. GenM SK3 se produit fréquemmentMSC4 et le temps de démarrage essaie dur d'y éviter
- pire, par défaut le LOH est **non compacte** quand il est recueilli; il libère simplement les trous. Donc même après une génétique 2, le LOH se fragmenteM SK3 vous avez de l’espace libreMSC4 mais c’est ce qui est incorrectMST5 des trous de taille appropriée pour la prochaine allocationMSP7 De nouveaux grands objets élargissent le chevauchement plutôt que de le réutiliserMSV8
- Efficacité nette: continuer à attribuer de grands objets à un rythme constantM SK1 et votre processus ' sa empreinte de mémoire marche vers le haut, que les objets soient ou non encore renvoyés. Il semble comme une fuite même s’il n’est pas une

Les sources les plus importantes de croissance accidentelle des LOH dans les services réels .NETM SK1 selon ma expérience:

| Source | Pourquoi finit-il sur le LOH ?
|---|---|
| `JsonSerializer.Serialize(obj)` Retour d'un `string` | Un objet de 100 MB devient une chaîne UTF de \200 MB de |-16 d'une seule allocation.
| `MemoryStream` vous laissez augmenter sans limite | Le tampon interne double au-delà de 85 KB et demeure sur le LOH |
| `byte[] buf = new byte[n]` pour le grand `n` | Répartition directe du LOH ; très courante dans le fichier
| `List<T>` qui augmente depuis ~10 K référence-articles typés | croisement de l’arrée d’appui 85 KB et débarque sur le LOH S|
| `string.Concat` / `StringBuilder` sur un grand texte | Final `ToString()` est une seule allocation énorme |
| `XmlSerializer` / `DataContractSerializer` de grands graphiques | La même forme que le cas JSON |

Si vous ne vous souvenez qu’une règle de la colonne d’oeil: **être soupçonné de tout chemin de code qui produit un grand objet contigu unique sur le temps-temps ou par demande.** Il s’agit de la forme qui écrase les longs services d’exploitation, - et ,; elle est exactement celle que nous allons trouver dans 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
```

Outils qui aident à le diagnostiquer:

- [`dotnet-counters`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) pour les numéros vivants (la jauge LOH au-dessus ).

- [`dotnet-gcdump`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump) et [`dotnet-dump`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump) pour des snapshots que vous pouvez ouvrir dans Visual Studio ou PerfView.

- [PerfView](https://github.com/microsoft/perfview) elle-même pour tracer les allocations jusqu’à un stack.

- **Profileurs de JetBrains CLI** ( ce que j’ai réellement utilisé pour cette révision de travail). Maintenant emballé comme appropriée . Outils mondiaux du NETM SK3 si silencieux
  
  - [`JetBrains.dotMemory.GlobalTools`](https://www.jetbrains.com/help/dotmemory/Command-Line_Profiler.html): attacher , prendre une photo de mémoire `.dmw` plus tard pour voir quelles racines sont maintenues les allocations de LOH.
  - [`JetBrains.dotTrace.GlobalTools`](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html): la même forme pour l'échantillonnage / le traçage ♫ / les données de lignes horaires ♫
  
  ```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
  ```
  
  Division de la main-d’œuvre pour ce réaménagement: `dotnet-counters` je lui ai dit que le LOH était le problème; la photo de l’image de souvenir me a dit que la cause était le timer d’auto-conservation.

Si vous en avez vraiment besoin, il y a des cages d’évacuation. `GCSettings.LargeObjectHeapCompactionMode = CompactOnce` force une compaction LOH de 1 - off . [`RecyclableMemoryStream`](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream) des tampons de base Microsoft pour éviter la répartition en premier lieu. Les deux sont des plasters. La bonne solution est presque toujours d’arrêter de produire l’objet géant en premier placeM SK2

### Retour au diagnostic

Ainsi, chaque fois que vous voyez une croissance constante du LOH dans un service de longue durée, la question est presque toujours la même. *Quels sont les critères d’attribution des objets très grands, quelle cadence et pourquoi?* La forme de la réponse (timer? gestionnaire des demandes ? serialisateurM SK3 pool de tamponMSC4 vous dit où regarder dans le code

Les exceptions sont également importantes. `4/sec` de `SqliteException` est assez bas pour ne pas apparaître dans les registres quiconque lit, mais suffisamment élevé pour vous dire qu’un chemin de code se rétryne silencieusement . C’est ce type d’aiguillage que l’examen périodique repère la réponse à un incident qui n’y arrive jamais

**Règle:** lorsque quelque chose semble mal, confirmer avec `dotnet-counters` before guessing. Les chiffres atténueront le rayon de recherche d’une ordre de grandeur

## Étape 3: La mauvaise approcheM SK1l'odeur d'abstraction (exemplaire amélioré: HNSW pour le cachingMSC4

C’est là que la discipline devient intéressante, car la temptation est toujours de *ajustement* le symptôme plutôt que de remettre en question l’abstraction.

> ### Vocab rapide (pour les prochaines sectionsM SK1
> 
> - **Vector**: un ensemble fixe de floats d’une taille de - - eM SK3g. `float[64]`. Chacune des fentes est une propriété mesurée de la demande. `float[]`.
> - **Comparaison des vecteurs**: habituellement [similitude des cosine](https://en.wikipedia.org/wiki/Cosine_similarity) - la cosine de l'angle entre deux vecteursM SK1 Plus proche de 1.0 = plus similaire. La mathématique réelle est un produit ponctuel divisé par deux normesMSC5 une méthode appeléeMNK6
> - **Environ le voisin le plus proche (ANNM SK1**: " étant donné ce vecteurM SK2 trouver le N le plus proche sur les millions, rapideMSC4 bruteMST5 la comparaison de force est OMSSK6NMSS7 par demandeMSL8 Les algorithmes ANN obtiennent des résultats sub--milliseconds au prix occasionnel d’avoir manqué le *TÉLÉCOPIEURS* match le plus proche.
> - **HNSW**: l’un de ces algorithmes ANN. Il construit un graphe stratifié où la couche supérieure a quelques puits - des nodes connectés et les couches inférieures remplissent en détailM SK3 Vous entrez au sommet et zoomez dansMSC4 Excellent pour indexer une grande *Établissement stable* ensemble de vecteurs. Non conçu pour "toute demande ajoute un et les anciens vont disparaîtreM SK2 - le point pertinent de ce tout postMSC4
> - **Centres**: la moyenne d'un groupe de vecteurs - un vecteur qui résume le regroupement. Si vous avez des vecteurs dont l'apparence est presque identique à celle de 10,000, vous pouvez les jeter et conserver un centroidM SK5 le centroid est un suppléant défaillant mais compactMSC6 Les stratégies de compression utilisent cela pour transformer une grande partie de l'histoire brute en une petite histoire sommaire.

Pour StyloBot,, la croissance du LOH a entraîné trois classes structuralement identiques dans le processus [HNSW](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world_graphs) (Hiérarchique de petits mondes navigables géographiques[original Malkov & papier Yashunin](https://arxiv.org/abs/1603.09320)) utilisé pour la recherche de similitude au-dessus de la signatureM SK1 session , et vecteurs d'intention. Chacun avait un champ commeMSC4

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

Chacune a été fournie par un gestionnaire d'apprentissage qui s'est abonné à `LearningEventType.FullDetection`, qui déclenche un feu **toutes les demandes HTTP**. Chacune des demandes a ajouté un vecteur

Et tous les cinq minutes, ,, un Timeur de sauvegarde automatique a serialisé l’ensemble du graphique à JSON et l’a écrit sur le disque.

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

À l’échelle de démonstration, `signatures.vectors.json` était 104 MBM SK1 En production: `intent.meta.json` à 70 MBM SK1 `intent.vectors.json` à 51 MBM SK1 La serialisation de JSON crée une chaîne contiguë dans la mémoire à |100+ MB\. Cette chaîne va directement vers le LOH\ .\ Trois indices\ ,\ tous les cinq minutes\ MSC6\ croissant continuellement\ M SK7\

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

Maintenant: la solution facile est d’ajouter un cap. `MaxVectors = 10_000`, Évacuation de la LRU , l’expédition à bord de celle-ci . Il aurait pu fonctionnerM SK3 Les chiffres seraient dépassés, le tableau de bord serait apparu bienMNK5 Il serait également erronéMRK6

HNSW est une excellente technologie et je l’utilise délibérément ailleurs ([Self-Databases de vecteurs hébergées avec Qdrant](/blog/self-hosted-vector-databases-qdrant), [Recherche et indexation hybrides de RAG](/blog/rag-hybrid-search-and-indexing), [GraphRAG minimal viable](/blog/graphrag-minimum-viable-implementation)). [Pinecone](https://www.pinecone.io/learn/series/faiss/hnsw/), [Favoriser](https://weaviate.io/developers/weaviate/concepts/vector-index#hnsw-index), [pgvector](https://github.com/pgvector/pgvector#hnsw), et [Qdrant](https://qdrant.tech/documentation/concepts/indexing/#vector-index) tous les utilisateurs l’utilisent à l’interne. *"pour chaque empreinte digitale active, conserver une petite fenêtre de vecteurs comportementaux récents afin que la détection puisse comparer la demande actuelle avec le comportement antérieur d’un vecteur similaire* Les empreintes digitales des bottes se répètent ( demeurer chaude ); les empreintements digitales humaines ne sont pas non plus visibles [Le plus simple possible](/blog/botdetection-part3-as-simple-as-possible) Post list the in-process HNSW index as a feature ; this post is the followingM SK2up admitting where that was the wrong fit.

Il y a un test rapide de l’humidité, :, si vous imaginez couper une bouchonneuse sur la structure. , est le résultat sémantiquement toujours ce que vous voulez.

**Règle:** Lorsque vous voyez une croissance sans limites, la question n’est pas *"comment capter cette structure ?"* mais *"est-ce la bonne structure pour ce que nous faisons?* La première dissimule le symptôme; la seconde entend ce qu’elle vous dit.

## Étape 4: Choisissez la forme appropriée , pas seulement un cap

Une fois que vous avez désigné la mauvaise abstraction, le remplacement écrit habituellement lui-même. Pour StyloBot, il y avait deux couches.

**La couche chaude:** a délimitée `BoundedVectorCache<TEntry>`, un enveloppement épais autour [`ConcurrentDictionary`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2) avec accès-évacuation prioritaire de la fréquence. Le coefficient de retenue donne aux entrées classifiées par le bot -un poids de survie MSC3xM SK4 de sorte que l’auto-cache

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

**La couche persistante (FOSS):** trois nouvelles tables SQLite (`signature_centroids`, `session_centroids`, `intent_centroids`) entreposage de centrides comprimés à partir d'un `VectorCompactionService`. Vektors stockés sous forme de floats bruts [`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();
```

Système de serialisation binaire compacte. Non 100+ Strings JSON MBM SK2 Aucun LOH.

> **Note de niveau d’entreposage.** SQLite est la version FOSS / single-binary backend - le bon appel pour "mettre l’exe sur un Pi et marcher à l’extérieurM SK4 StyloBot commercial utilise PostgreSQL avec [pgvector](https://github.com/pgvector/pgvector), vous donne une recherche de similitude indexée appropriée (HNSWM SK2IVFFlat *dans la base de données* - sur un corps stable , où l’HNSW appartient, à une échelle horizontaleM SK3 et état partagé dans toute la flottilleMSC4 La même règle architecturaleMska5 un backend différentM Ska6 [sqlite-vss](https://github.com/asg017/sqlite-vss) est une pierre angulaire si vous éloignez SQLite mais que vous voulez rester un seul utilisateur.

La recherche de similitude sur la couche persistante FOSS est brute-force cosine sur toutes les lignesM SK1 accéléré avec SIMD ([`System.Numerics.Tensors.TensorPrimitives`](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.tensors.tensorprimitives)). À l'échelle compacteM SK1centroïde ( centaines à quelques milliers de rangées), un balayage complet prend mSSK4 ms sur une PiMSC5 Celle-ci

Une note sur **L1/LM SK1 compaction**, depuis "compaction" fait du travail réel dans cette sectionM SK3 Le service de compaction fonctionne tous les soirs et réduit l'historique en deux passes

- **L1**: prenez tous les vecteurs bruts écrits aujourd'hui , groupez ceux qui sont très similaires M SK2 et remplacez chaque groupe par un centroïde ( la moyenne MNK4 CheapMNK5 se produit fréquemment MMK6 modeste réduction de taille
- **L2**: prendre LM SK1 centroïdes qui sont eux-mêmes semblables au cours des jours, / semaines et les fusionner de nouveau, MSC3 aggressif, ; se produit moins fréquemment,; réduction de grande envergure.

Il s'agit du même modèle LSM-moteurs de stockage d'arbres (RocksDB,LevelDB+, Cassandra=)Utilisation pour SSTablesM SK5Niveau 0contient des données récentes finesMSC7dont les données sont consolidéesMNK8les niveaux inférieurs contiennent des données compactesMMK9donts sommairesMNK10En empruntant, cela fonctionne parce que le mode d'accès est le mêmeMRK11la plupart des lectures atteignent les données récenteMRK12une petite fraction des lecturements ont besoin de la longue queueM SK13 Rien ne permet de garder chaque ligne brute pour toujours.

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

La forme générale (**Cache chaude limitée pour l'état stable + entrepôt compact persistant de l'histoire + compacteur périodique entre eux**) apparaît de plus en plus dans les systèmes d’apprentissage à long terme -en cours. Il vaut la peine de garder le ' dans votre trousse d’outilsM SK4 Lorsque quelqu’un s’adresse à l’HNSW ou au pgvector pour une charge de travail qui, selon lui, est en fait un cache, , c’est la chose la moins coûteuse

**Règle:** La structure de données la plus simple qui convient au *modèle de temps d'exécution*, non celui qui convient à l'ensemble de données ' sa taille sur papier

## Étape 5: Faire en sorte que le chemin rapide tolére les erreurs

C’est une chose subtile, car il s’agit de retenue plutôt que d’addition.

Si le chemin de détectation rapide dans StyloBot est synchrone, et que la vérification des similitudes n’est pas une étude du dictionnaire qui bloque-, il y a temptation à bloquer ce chemin.

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

Une erreur signifie *Aucun signal de similitude pour cette demande*. Les autres détecteurs continuent de fonctionner. Un gestionnaire d'arrière-plan demande SQLite après que la requête a été remplie et que le cache est chauffé pour la prochaine fois.

C'est là où la plupart des systèmes de cache vont mal.

**Règle:** si vous pouvez't tolérer une erreur , vous n’avez pas'il n’y a pas de cacheM SK3 Vous avez une foule fantaisie

## Étape 6: Vérification *tous les* accumulateur, non seulement l'actuelle forte

Une fois que vous avez trouvé une structure non liée, supposez qu’il y en a d’autres.

La vérification est mécanique. *qui décide quand les entrées quittent la zone, quel est le cas le pire dans un trafic hostile?* Si vous pouvez't répondre à toutes les trois dans une seule phrase , c’est la même discipline que le [Signales éphémères](/blog/ephemeral-signals) Modèle: tout ce qui s'accumule doit se dégraderM SK1 évacuer , ou compacter.)

Pour StyloBot, la plupart des accumulateurs étaient déjà bien munis

- `EphemeralPatternReputationCache`: cap dur à 10,000 entrées avec décomposition de fond et évacuation des LRU `Neutral` → `Suspect` → `ConfirmedBad` la machine étatique, et l'hysterèse asymétrique vivent dans [Apprentissage pour devenir plus rapide](/blog/stylobot-release-learning))
- `BehavioralPatternAnalyzer`: IMemoryCache avec des limites d'identité par - (50 voies de routesM SK3 | | 100 horaires, \ 15-min TTLMSC7
- `DriftDetectionHandler`:  10,000 échantillons
- `SessionEscalationService`: 35-minute TTL avec horlogeM SK2évacuation dirigée

On a besoin d’une attention au-delà des classes HNSW: `MarkovTracker._cohortBaselines`. Le [Chaîne Markov](https://en.wikipedia.org/wiki/Markov_chain) (separée des chaînes de signatures perM SK2, qui avaient déjà été évacuées par les LRU à `MaxTrackedSignatures`). Les lignes de base des cohortes (une par cohorte de trafic, comme "centre de données -nouveau"ou S"résidentiel-retournement") n'ont pas eu d'évacuation du tout. La fixationM SK9 évacuer les cohorts les plus froides SSK10les transitions totales les moins élevées) lorsque le dictionnaire dépasse `SelfMaintenanceOptions.MarkovCohortSize`.

**Règle:** Chaque collecte d’un seul tonne répond à trois questions ou c’est-à-dire :

## Étape 7: Faire chaque lien configurable

Le mode de défaillance suivant après "no bound" est "bound thatM SK3s wrong for this hardware `MaxEntries = 10_000` est bon jusqu’à ce que quelqu’un effectue votre service sur Pi4, ou dans un conteneur avec 256 MBM SK2

Pour StyloBot, chaque limite a atterri sous un seul `SelfMaintenanceOptions` blocage dans `appsettings.json`. Les paramètres par défaut fonctionnent pour un serveur standard. `LowMemory` Présélection statique:

```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;
});
```

Ou par l'entremise `appsettings.json` pour l'environnement-ajustement spécifiqueM SK1

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

Le point plus profond, :, une limite que ' fixe en un seul endroit est quelque chose dont les opérateurs peuvent raisonnablement penser. `const int` les déclarations est quelque chose que personne ne peut expliquer.

**Règle:** tous les limites que l’opérateur pourrait vouloir changer pour leur matériel vie dans un bloc de configuration , ne pas éparpillée à travers la base de code .

## À quoi ressemble "fixed"

Pour StyloBot spécifiquement (FOSS buildM SK1 LowMemory preset):

Composante | Component | Avant | Après M|
|---|---|---|
| Indice de la signature HNSW | LOH non lié
| Indice de l'HNSW des séances | LOH non lié
| Indice de l'intensité du HNSW | LOH non lié
| Les tampons de sauvegarde automatique du JSON | MSC2 LOH MB tous les MSC3 min SMC4
| Lignes de référence de la cohorte de Markov | Non lié
| La couche de vecteur totale | **13+ GB LOH** | **<6 Mo** |

Le modèle de détection est inchangé. Ce qui a changé *où l'on trouve des preuves de similitude* et *quand il est permis de modifier le chemin rapide*. Centroïdes survivants remarrages dans SQLite (FOSS) ou PostgresM SK3pgvector MSC4paidMSC5 la compaction nocturne produit encore LMST6LMSS7 aucun n'exige une mémoire non limitéeMSST8

Sur un Pi4 avec le LowMemory preset, la construction FOSS se situe sous M SK2 MB RSS après le réchauffement , indéfinimentMNK4 Postgres payésMMK5 les déploiements appuyés héritent de la même chaleurMRK6 discipline des caches MNK7 la couche persistante s'élargit simplement horizontalement au lieu d'être vivant à côté du processusMZK8 En tout état de cause

## La leçon générale

L’ajout d’un cap limite la mémoire sans changer l’architecture. La mauvaise abstraction demeure erronéeM SK1 le symptôme devient simplement plus silencieux . La personne suivante qui touche le système hérite d’une structure qui pratiquement fonctionne, ce qui est pire que celle qui ne fonctionne pas de toute évidence.

Le modèle qui se répète dans les systèmes d’apprentissage à long terme

- **Capter le chemin chaud**: boundedM SK1 in-memory , conçu pour tolérer les missesMSC4 politique d'évacuation alignée sur la charge de travail
- **Compression de l'histoire**: store binaire persistante compacte , comprimée périodiquement , accessible hors du fil de demande
- **Vérification de tout le reste**: chaque accumulateur répond à ce qui l'oblige , ce qui le évacue
- **Centraliser les boutons**: un bloc de configuration , pas quinze `const`La plupart des enquêtes menées dans le cadre de l’enquête ont été effectuées par les autorités locales.
- **Calendrier de la recherche**: le bug n'existe que dans le système en cours

Fixer la forme, non le symptôme.

---


## Autres lectures

**StyloBot : série de sorties**

- [Comportement, Non identité](/blog/stylobot-fingerprint): le modèle comportemental et le regroupement Leiden de cette couche est la mémoire pour
- [Behaviour-Aware ASP.NET UI](/blog/behaviour-aware-ux): la surface qui consomme les verdicts de détection dans Razor
- [Apprentissage pour devenir plus rapide](/blog/stylobot-release-learning): le système d’apprentissage de quatre niveaux et la cache des verdicts que cette discipline de mémoire maintient limitée
- [Bot Detection Part 2: Signature Pipeline and StyloBot Architecture](/blog/botdetection-part2-signature-pipeline-and-stylobot-architecture): la détection originale-poste d'architecture de l'engine
- [Partie de détection des bottes 3: aussi simple que possible](/blog/botdetection-part3-as-simple-as-possible): la diminution des deux lignes de l’indice HNSW dans le processus -, où il a été introduit pour la première fois

**HNSW et recherche vectorielle dans ce blog**

- [Self-Databases de vecteurs hébergées avec Qdrant](/blog/self-hosted-vector-databases-qdrant): plongée profonde de l’HNSW : couches graphiquesM SK2 `M`, `ef_construct`, réglage
- [Recherche et indexation hybrides de RAG](/blog/rag-hybrid-search-and-indexing): HNSW comme la moitié dense d'un pipeline hybride de récupération
- [GraphRAG minimal viable](/blog/graphrag-minimum-viable-implementation): DuckDB VSS HNSW vous aperçoit (`array_cosine_distance` contre `array_cosine_similarity`)
- [Recherche sémantique avec ONNX et Qdrant](/blog/semantic-search-with-onnx-and-qdrant): Configuration de l'HNSW via le client Qdrant .NET
- [GraphRAG: Pourquoi la recherche par vecteur se détériore au niveau du corpus](/blog/graphrag-knowledge-graphs-for-rag): pourquoi la pure ANN n’est pas suffisante

**Références externes**

- [HNSW (Wikipedia)](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world_graphs)
- [Malkov & YashuninM SK1 *Recherche efficace et robuste approximative du voisin le plus proche à l'aide de graphiques hiérarchiques Navigables Small World*](https://arxiv.org/abs/1603.09320): le papier original 2016
- [Pinecone: Faiss M SK1 Expliqueur de l'HNSW](https://www.pinecone.io/learn/series/faiss/hnsw/)
- [Concepts d'indexation Qdrant](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` Référence](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters): l’outil qui a en premier lieu survolé le LOH
- [Commande JetBrains dotMemory-profileur de ligne](https://www.jetbrains.com/help/dotmemory/Command-Line_Profiler.html): le profileur de mémoire d'outils CLI / global- utilisé pour cette révision
- [Commande JetBrains dotTrace-profileur de ligne](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html): la même forme pour le rendement / profilage des lignes de temps
- [sqlite-vss](https://github.com/asg017/sqlite-vss): SQLite + Faiss, si et quand la couche persistante dépasse la force brute

Source de la mise en œuvre: [github.comM SK1scottgal/stylobot](https://github.com/scottgal/stylobot). moteur en direct , tableaux de bord M SK2 et contrôles commerciaux à [stylobot.net](https://www.stylobot.net).