# Серія релізів StyloBot: Винайти та виправити безмежний ріст у довгостроковій сферіM SK1Running .NET Services

*Довгий-сервіси, які працюють, повільно рухаються у напрямку необмеженої пам 'яті . ось як я знаходжу цей рух МSK3 як я думаю про те, щоб його виправити без паперування над ним M SK4 і працюючий приклад з StyloBot ' векторного шару схожості, який переніс його від MSC6 GB на Великому Об' єктному Пагорбі до під МSK7 MB.*

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

> **Серія випуску StyloBot**
> 
> 1. [**Подібність, Не ідентичність**](/blog/stylobot-fingerprint): чому StyloBot моделює клієнтів поведінково
> 2. [**Подібність-Усвідомлення ASP.NET UI**](/blog/behaviour-aware-ux): сервер - віддав поверхню над результатом виявлення
> 3. **Винайти та виправити безмежний ріст у довгостроковій-Running M SK1NET Services**: дисципліна надійності
> 4. [**Подібність-Усвідомлення інтерфейсу TypScript**](/blog/typescript-sdk): Express , Fastify, і компоненти браузера
> 5. [**Архітектура Sidecar**](/blog/sidecar-architecture): як реагуючий двигун підключається до не МSK1NET стеків
> 6. [**Навчитися швидше**](/blog/stylobot-release-learning): адаптивна система навчання , чотири МSK2 пам 'ять на рівні M SK3 і кэш вердикта
> 7. [**Випробовуємо те, що не зупиняє нас**](/blog/stylobot-release-nondeterministic-testing): контрольна дисципліна : один БДФ файл керує регресією МSK2 завантаженням МСК3 та калібрацією
> 8. [**StyloExtract - локальний інтерфейс навчання HTML для конвертера Markdown**](/blog/stylobot-release-styloextract): HTML → Марка-даун, який поєднується з детектором МSK2 walker bug lucidVIEW caught МSK3 and the dogfood loop that made it honest

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

Вироблений приклад - векторний шар StyloBot,, але дисципліна - це 't Stylo Bot-спеціальнийM SK3 Будь-яка довга МSK4гортання МSK5NET-сервіс, який накопичує стан з потоку, врешті-решт, зростає до класу błędów, які ви можете зафіксувати в тестах чи в коротких завантаженняхMSC7 Він з 'являється лише після днів реального потокуMスク8 Цей пост є підручником, який я використовую для його віднаймки

Модель поведінки [Подібність, Не ідентичність](/blog/stylobot-fingerprint); поверхня ASPM SK1NET в [Подібність-Усвідомлення ASP.NET UI](/blog/behaviour-aware-ux); джерело на [github.comM SK1scottgal/stylobot](https://github.com/scottgal/stylobot).

[TOC]

---


## Клас проблеми

Довгий-сервіси, що працюють, накопичуютьсяM SK1 Кожна кэша , кожний магазин навчання МSK3 кожне " ми M SK5 ми просто будемо зберігати останні N-запити М SK6 буфер - маленький накопичувач . кожен з них виглядає добре сам по собі MSC8 Разом МSK9 в процесі, який МSK10 працював тиждень-на-всьогоден МСК11 вони формують форму пам 'яті, яку жодний тест ніколи не повторює М СК12

- працює добре в dev, CIM SK1 і перший день виробництва
- поступово рухається у напрямку OOM, потім або скасується, або починає набирати повідомлення

Ви спіймаєте це *навмисно шукаючи його*, про процес, який МSK1 тривав досить довго, щоб форма з 'явилась

## Шаг 1: Periodic reliability reviews МSK1the act of lookingM SK2

Перша частина цієї дисципліни - це: МСК0 , Не технічна . ".". Це МСК2 , календарний запис.

Кожні кілька випусків, Я припиняю додавати додатки і просто дивлюся на працюючу систему під реальним рухомM SK1 запитуючи *чи щось із цього виглядає неправильно?* Ніяких конкретних помилок, немає метиM SK1 Просто дивлячись. Работі системи мовчазно лежать ; вони не втрачають звуку МSK4 не втручаються аж до того, як вони катастрофічно втрачатимуть звуку

Найновіша перевірка виявила błąd, що ця стаття про: `Mostlylucid.BotDetection.Demo` процес, який сидить на 20 жителі Великобританії, встановленному під синтетичною тестовою траєкторією . Таке число МSK2 не повинно пережити тридцять секунд уваги

**Правило:** ставить повторюваний відлік на календарі, щоб читати власні метрики. Нічого не виправитиM SK1 Просто подивитися

## Шаг 2: Дозвольте, що شمارники вас здивують

Правильний інструмент для " є дещо неправильним ?" на МSK2NET-процесі [`dotnet-counters`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters). Це МSK1 це безкоштовне МSK2 це ' це вже вбудовано СМСК4 і це говорить вам правду про те, що ваш процес робить зараз

Кілька команд пізніше на demo 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
```

Це достатньо, щоб знати, що не так, не читаючи коду. [Величезний об 'єкт](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap) був на 13.4 ГБ, і зростав майже МSK1 МБ МSK2 секунда . Щось постійно розподіляло величезні об 'єкти мSK4, а ген M2 GC МСК6 не міг піднятися з ними ммSK7

### Швидкіше поновлення на LOH ( тому що кожен . NET dev досягає цього врешті-решт

Великий кусок об 'єктів - це одна з найпоширеніших несподіванок, коли ви вперше застосовуєте довгу-програму M SK1NET-сервіс. ЦеMSC3 варте переробки, бо майже кожен зустрічається з ним по-важкому :

- GC має три покоління: (ГенМSK1 ГенM SK2 Гн МSK3 для нормального короткого циклу - для живих об 'єктів . Більшість розподілів живе і помирає в гені0,, який дешевий для збору
- **Будь-що більше за 85 КБ не йде в ті покоління** Він йде прямо на **Величезний об 'єкт**.
- ВАК збирається лише під час **Ген2 GC**,, яка є найдорожчою колекцією
- Найгірше, по замовчуванню LOH - **не стиснена** коли він збирається; він просто звільняє прогалиниM SK1 Таким чином, навіть після гену2, LOH стає фрагментованимMSC3 у вас є вільний простірМSK4 але цеMСК5 є неправильнимМСК6 розміром діри для наступного розподілуМ СК7 Нові великі об 'єкти розширюють купу, а не повторюють їїM СК8
- Цифровий ефект: продовжувати розподіляти великі об 'єкти з постійною швидкістюМSK1 і ваш процесM SK2 пам' ять рухається вгору, незважаючи на те, чи об' єкти все ще відносяться до них чи ні МSK3 Це виглядає як витік, навіть якщо він не є

Найбільші причини випадкового росту LOH у реальних послугах .NETM SK1 з мого досвіду:

| Źródło | Чому він опинився на ЛOH МSK2
|---|---|
| `JsonSerializer.Serialize(obj)` повертаючи `string` | Об 'єкт МSK1 МБ стає МSK2 Мб UTF string -16 string , все в одній долиці
| `MemoryStream` ви дозволяєте рости безмежно | Внутрішній буфер подвоюється за МSK1 КБ і залишається на LOH |
| `byte[] buf = new byte[n]` для великих `n` | пряме виділення LOH ; дуже поширено в файлі
| `List<T>` що росте за минулим ~10 K відмітка МSK1 надруковані речі МSK2 Крести опорної массиви 85 КБ і приземлиться на ЛОХ
| `string.Concat` / `StringBuilder` над великим текстом | Финал `ToString()` є одним величезним розподілом |
| `XmlSerializer` / `DataContractSerializer` великих графів | Те ж саме, що і в випадку з JSON |

Якщо ви пам 'ятаєте лише одне правило: **бути підозрюваними до будь-якого кодового шляху, який виробляє єдиний супутний великий об 'єкт на таймері або за запит.** Те, що "'" - це форма, яка руйнує довгі services , а МSK2 і MSК3 є точною формою, яку ми МСК4 збираємося знайти в 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
```

інструменти, які допомагають його діагностувати:

- [`dotnet-counters`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) для живих чисел (показчик LOH вище МSK1

- [`dotnet-gcdump`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump) і [`dotnet-dump`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump) для зйомок, які можна відкрити в Visual Studio або PerfView.

- [PerfView](https://github.com/microsoft/perfview) для відстеження розподілу до стопки.

- **Профильери JetBrains CLI** ( те, що я насправді використовував для цієї переробки ). Зараз упакований як правильний МSK2 NET глобальні інструменти МSK3 так безголовний МСК4 віддалений MСК5 CI розробка пробігу часу не потребує вказівного інтерфейсу gui
  
  - [`JetBrains.dotMemory.GlobalTools`](https://www.jetbrains.com/help/dotmemory/Command-Line_Profiler.html): під 'єднати , зробити знімок з пам' яті МSK2 відкрити `.dmw` робочий простір пізніше, щоб побачити, які залишені коріння тримають LOH розподіли.
  - [`JetBrains.dotTrace.GlobalTools`](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html): одна і та сама форма для зразку МSK1 відображання МSK2 дані про часову лінію . Використовуйте це, коли вам потрібна стопка, а не шкала
  
  ```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
  ```
  
  Розділ праці на цю переробку: `dotnet-counters` сказав мені, що проблема в тому, що LOH

Є шлюпки для порятунку, якщо вам їх дійсно потрібно. `GCSettings.LargeObjectHeapCompactionMode = CompactOnce` змушує потиснути LOH на один - [`RecyclableMemoryStream`](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream) від буферів Microsoft для того, щоб уникнути розподілу в першу чергу. Обидві штуковиниM SK1 Справжнє рішення - майже завжди припинити виробляти гігантський об 'єкт в першу очередь.

### Повернімося до діагностування

Отже, коли ви бачите постійний ріст LOH у довгостроковій-працевлаштуванніM SK1, питання майже завжди таке ж *що' це розподіляє дуже великі об 'єкти , на яку каденцію* Форма відповіді (timerM SK1 обладнатель запитів? сеrializerMSC3 буферний фондМSK4 вказує вам, куди подивитися в коді

Винятки також мали значення. `4/sec` від `SqliteException` low enough to not show in logs anyone reads, but high enough to tell you a code path is silently retrying

**Правило:** коли щось виглядає не так, підтвердити з `dotnet-counters` перед припущенням. Числа зменшать радіус пошуку за порядок величиниM SK1

## Шаг 3: Неправильний МSK1 запах абстракції

Ось де стає цікавою дисципліну: МСК0 , тому що спокуса ",", always , *зламка* симптом, а не питання абстракції.

> ### Гнучкий vocab ( для наступних секційM SK1
> 
> - **Вектор**: фіксованийM SK1масштабний ряд плавників - eMSC3g. `float[64]`each slot is a measured property of the request ( header count , timing МSK3 IP family `float[]`.
> - **Схожість вектора**: зазвичай [Схожість косину](https://en.wikipedia.org/wiki/Cosine_similarity) - косину куту між двома векторами . Близьше МSK2 МSK3 більш схожеM SK4 Фактична математика - це дотиковий продукт, поділений двома нормами; один метод під назвоюMSC6
> - **Приблизний найближчий сусід (ANNM SK1**МSK0 " беручи до уваги цей вектор МSK2 знаходити найближче N з мільйонів , швидкіше M SK4 Brute - порівнює силу OMSC6NM SK7 за запитом MSC8 алгоритми АNN отримують результат на менше ніж МСК9 мілісекунд за рахунок того, що іноді не вистачає *правда* найближчий матч.
> - **HNSW**: один із тих алгоритмів ANN . Він створює шаровий графік, де верхній шар має декілька зразків МSK2 з 'єднані вузли та нижні шари заповнюють деталями M SK3 Ви вводите вгору і збільшуєте зображення MSC4 Чудово для індексування великого , *стабільний* набір векторів. Не розроблений для M SK1 кожна просьба додає один, а старі зникають " МSK3 відповідний момент цілого поста
> - **Центроїди**: середній показник групи векторів - один вектор, який підсумовує кластер МSK2 Якщо у вас є МSK3 вектори, які всі виглядають майже однаково \ , ви можете відкинути їх і залишити один центроід \ МSK5 центроид є поганим, але компактним замінником \. \ Сокремлення використовують це, щоб перетворити велику кількість сирої історії на маленьку історію підсумку \

Для StyloBot, ріст LOH спричинив три класи, які були структурно ідентичними : в процесіM SK2 [HNSW](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world_graphs) (Hierarchічний навігаційний маленький світM SK1 графи МSK2[оригінальний Мальков & Яшунин](https://arxiv.org/abs/1603.09320)), що використовується для пошуку схожості над підписом

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

Кожна з них була нагоджена вчителем, який підписувався `LearningEventType.FullDetection`,, що реагує на **кожен HTTP запит**. Кожна просьба додала вектору . Кожня просьма МSK2 Ніхто не виїхав

І кожні п 'ять хвилин - МСК0 - автоматичний таймер відтворює весь графік на JSON і записує його на диск - МSK1

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

На рівні демонстрації, `signatures.vectors.json` був 104 MBM SK1 В produkcji: `intent.meta.json` на 70 MBM SK1 `intent.vectors.json` на 51 MBM SK1 Серіалізація JSON створює континуальну струну в пам 'яті на ♫ 100+ ♫ MB ♫ МSK3 ♫ Ця струна йде прямо до LOH ♫. ♫ Три індекси ♫МSK5 ♫ кожні п' ять хвилин ♫

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

Тепер : легким рішенням є додавати капулу . `MaxVectors = 10_000`, Виселення з ЛРУ МSK1 корабель це . Це було б спрацювало МСК3 Номери були б нижче MСК4 панель обкладинки виглядала б непогано М СК5 Це також було б неправильним M СК6

HNSW - це чудова технологія, і я використовую її навмисно в інших місцях.[Self-Схований векторні бази з Qdrant](/blog/self-hosted-vector-databases-qdrant), [Гібридні пошуки та індексування RAG](/blog/rag-hybrid-search-and-indexing), [Найменше життєздатний граф РАГ](/blog/graphrag-minimum-viable-implementation)). [Pinecone](https://www.pinecone.io/learn/series/faiss/hnsw/), [Вов 'язати](https://weaviate.io/developers/weaviate/concepts/vector-index#hnsw-index), [pgvector](https://github.com/pgvector/pgvector#hnsw), і [Кдрант](https://qdrant.tech/documentation/concepts/indexing/#vector-index) всі користуються нею всередині. МСК0, але це - индекс корпусу. МSK2, а не кэш. ,, і те, чого насправді потребував StyloBot - кэш *" для кожного активного відбитка пальців , тримайте маленьке вікно недавніх векторів поведінки, щоб детекція могла порівняти теперішній запит з минулим наслідком схожих* Ті ж самі відбитки пальців Bot повторюються. (тримати гаряче ); людські відбити пальця не мають [Так просто, як це можливо](/blog/botdetection-part3-as-simple-as-possible) пост, який вміщував в ” - ” процес Індекс HNSW як функцію “ ; ” цей пост є наступним ” МSK2 ”, визнаючи, де це неправильне ”

МSK0 - це швидкий тест на запах. МSK1 - якщо уявити, що ви кладете тверду кришку на конструкцію. , - це результат, який, семантично, є тим, що вам хотілося.

**Правило:** коли ви бачите безмежний ріст, *" Яким чином ми покриємо цю структуру ?"* але *"" МСК0 - це правильна структура для того, що ми робимо "".* Перший приховує симптоми. Другий слухає, що він вам говорить.

## Шаг 4: Виберіть правильну форму , не просто шапку

Як тільки ви написали неправильну абстракцію, ,, замінник зазвичай пише сам себе. Для StyloBot це були два шари.

**Горячий шар:** а обмежена `BoundedVectorCache<TEntry>`, тонкий рюкзак навколо [`ConcurrentDictionary`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2) з доступом- виведення частоти priorytetуM SK1 Шордер затримки дає Botу - класифікованим записам MSC3x вагу виживання , так що self-キャッシュ МSK5 організовується навколо шаблону руху M SK6

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

**Утримований шар (FOSS):** три нові таблиці SQLite (`signature_centroids`, `session_centroids`, `intent_centroids`) зберігання стислених центродів з нічного `VectorCompactionService`. Вектори зберігаються як сирові плавники МSK1 бульбашки, використовуючи [`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();
```

Компактна бінарна сеrialізація. Ні 100+ МБ струни JSONM SK2 Ні LOH.

> **Запис на рівні зберігання.** SQLite - це FOSS / один - бінарний заголовок МSK2 правильний дзвінок для МSK3 кидати exe на Pi і йти геть \ ." Коммерційний StyloBot використовує PostgreSQL з [pgvector](https://github.com/pgvector/pgvector), дасть вам правильний індексований пошук схожості *всередині бази даних* - на стабільному корпусі , там, де належить HNSW МSK2 горизонтальна скала M SK3 і спільний стан по всьому флоту [sqlite-vss](https://github.com/asg017/sqlite-vss) є кроком вперед, якщо ви переростаєте SQLite, але хочете залишатися одним.

Поиск схожості на первісному шарі FOSS - brute-force cosine over all rows, accelerated with SIMD M SK2[`System.Numerics.Tensors.TensorPrimitives`](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.tensors.tensorprimitives)). На стисненому МSK1центрoidному масштабі МSK2 з сотнями до декількох тисяч рядків ), повний скан займає \ ~1-2 \ ms на Пі \ МSK5 \, що \' \ є хорошим \ : \ , це працює лише в фонових обладначів

Запис на **L1/LM SK1 компактація**, з того часу, як МSK1комплікація МSK2 робить реальну роботу в цій секції . Комплікаційна служба працює щоночі і зменшує історію в два проходи

- **L1**: візьмемо всі ті сирові вектори, що написали сьогодні , група тих, які дуже подібні МSK2 і замінимо кожну групу одним центроідом. МSK3 середній МСК4 дешевий MСК5 часто трапляється М СК6 скромне зниження розміру M СК7 e М סК8 g М S К9 М הסК10x М Ш К11
- **L2**: візьмемо LM SK1 центроїди, які самі по собі схожі на ці дні /тижневі, і знову об 'єднаємо їх MSC3 агресивні ; стають менш рідкісними МSK5 скорочення розміру M SK6 інші

Це одна і та сама модель LSM-Tree Storage Engines M SK1RocksDB, LevelDB , CassandraMSC4 Use for SSTablesMska5 level Мska6 holds recent fineMSKA7grained dataM Ska8 lower levels hold compactedMска9 summarised data Mska10 borrowing it works because the access pattern is the sameMске11 most reads hit recent dataМSK12 a small fraction of readings need the long tailMske13 nothing benefits from keeping every raw row foreverMsek14

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

Ogólnа форма (**закритий гарячий кэш для стабільного стану + компактний постійний склад для історії + періодичний компактор між ними**) з 'являється знову і знову в довгих - функціональних системах навчання МSK2 Це МSK3 варте того, щоб ви зберігали його у вашому наборі інструментів M SK4 Коли хтось звертається до HNSW або pgvector для навантаження на роботу, що – МSK5 – це насправді кэш , це дешевша MSC7 простіша річ, яку він мав би досягнути замість цього MST8

**Правило:** віддають перевагу найпростішій структурі даних, яка підходить *модель часу бігу*, не той, що підходить до набору даних ' розмір на папері

## Шаг 5: Робіть швидкий шлях толерантним до пропущень

Чудовий: МСК0 , тому що "'" має на меті стриманість, а не додавання .

Якщо повільний-фіксний шлях включає в себе пошук бази даних ,, то є спокуса зробити його блоком швидкого шляхуMSC2 DonM SK3t. Швидкий шлях виявлення у StyloBot синхроннийМSK5 і перевірка подібності не є пошуком словника, що блокує МSK6

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

Помилка означає *не схожість сигнал цього запиту*. інші детектори все ще запускаються . Працівник, що керує базами даних, запитує SQLite після закінчення запиту і нагріває кэш на наступний раз

Це те місце, де більшість Caching іде не так. : люди будують Cache , роблять зворотній спір синхронний МSK2 для правильності M SK3 і шлях пропущення стає МSK4 ms p мSK5 піком \ . Cache мав на меті зробити речі швидшими \ МSK7 замість цього він робив найгірший випадок гіршим \.

**Правило:** якщо ви можете' не терпіти пропущення , ви не маєте

## Шаг 6: Аудит *кожного дня* акумулятор, не тільки гучний

Як тільки ви знаходите одну безмежну структуру, уявіть, що є інші.

Аудит є механічним. *що визначає його розмір - МСК0, хто вирішує, колиဝင်ки залишаться - МSK1, а що - ' - найгірший випадок під ворожим рухом?* Якщо ви не можете відповісти на всі три запитання в одному реченні, [Ефемеральні сигнали](/blog/ephemeral-signals) модель: будь-що, що накопичується, має зруйнуватися

Для StyloBot, більшість акумуляторів вже були добрими

- `EphemeralPatternReputationCache`: жорстка капелька на МSK1 записах з розпадом фону і виведенням LRU МSK2механіка розпаду , `Neutral` → `Suspect` → `ConfirmedBad` станова машина, і асимметрична істеріза живуть в [Навчитися швидше](/blog/stylobot-release-learning))
- `BehavioralPatternAnalyzer`: IMemoryCache з перM SK1limiдами ідентичності
- `DriftDetectionHandler`МSK0 10,000 зразки
- `SessionEscalationService`МСК0 35- Minuтний ТТЛ з таймеромМSK2виведення за кермом

Потрібно було звернути увагу на інші класи HNSW: `MarkovTracker._cohortBaselines`. [Марковський ланцюг](https://en.wikipedia.org/wiki/Markov_chain) відслідковувач зберігає базові матриці переходу в коhorті per-, separate from per -signature chainsM SK3 which already had LRU eviction at `MaxTrackedSignatures`). Початкові показники коhorти МSK1 один на коhorту руху, як МSK2 дата-центр - новий МиSK4 або миSK5 житловий МіSK6 повернення МСК7 взагалі не було виселення СМСК8 фиксація ЯМСК9 виселити найхолодніший коhorт ІМСК10 найменший об 'єм переходів УМСК11 коли словник перевершує `SelfMaintenanceOptions.MarkovCohortSize`.

**Правило:** Кожна колекція на один тон відповідає на три запитання, або: МСК0 - це баг , : - це те, що його об 'єднує ,

## Шаг 7: Робіть кожен прив 'язок конфігурованим

Наступний режим непрацездатності після "без прив 'язкуM SK1 - "зав' язаний, що МSK3 неправильний для цієї аппаратурії `MaxEntries = 10_000` все гаразд, доки хтось не запустить вашу службу на Пі4, або в контейнері з M SK1 MB.

Для StyloBot, кожне обмеження приземлилося під одним `SelfMaintenanceOptions` заблокувати `appsettings.json`. Погані варіанти працюють для стандартного сервера . Для обмеженого обладнання там `LowMemory` Статичний пресет:

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

Або через `appsettings.json` для середовища-спеціальне налаштуванняM SK1

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

Глубоша точка – МСК0 – це межа, яка встановлена в одному місці, і її оператори можуть пояснити. `const int` заяви - це те, про що ніхто не може подумати.

**Правило:** кожен кордон оператора міг би хотіти змінити для свого обладнання життя в одному блокі конфігурації, не розсіяний через кодову базуM SK1

## Як виглядає "fixed"

Для StyloBot особливо (FOSS build, LowMemory presetM SK2

| Komponenт МSK1 Перед МSK2 Після |
|---|---|---|
| Індекс HNSW для підпису МSK1 Необмежений LOH МSK2 ~256 КБ гарячий кэш мSK4
| Індекс сеансу HNSW МSK1 Необмежений LOH МSK2 ~258 КБ гарячий кэш M|
| Індекс інтенції HNSW
| Бюфери автозаощадження JSON
| Марковські базові лінії Коhorти МSK1 Необмежений МSK2 ~1 MB
| Вкупний векторний шар МSK1 **13+ ГБЛО** | **<6 MB** |

Модель виявлення незмінна. Що змінилося? *де існують докази схожості* і *коли ' дозволяє впливати на швидкий шлях*. Centroids survive restarts in SQLite (FOSS) or PostgresM SK3pgvector

На Пі4 з пресетом LowMemoryM SK1 будування FOSS сидить під 500 МБ RSS після нагрівання , неодмінно M SK4 платна постгресса МSK5 закріплені депозиції успадковуються так само гаряче MSC6 касетна дисципліна ; постійний шар просто масштабується горизонтально, замість того, щоб жити поруч з процесом

## Об 'єктивний урок

Доdanie капсуни обмежує пам 'ять, не змінюючи архітектуру. МСК0. Неправильна абстракція залишається неправильним. МSK1. Симптом просто затихає. .. Наступна людина, яка торкається системи, успадкує структуру, яка майже працює. ММК3. Вона гірша за ту, що очевидно не працює.

Модель, яка повторюється у довготривалих системах навчання

- **Закрити гарячий шлях**: замкнутийM SK1 в- пам 'ятіMSC3 здатний терпіти пропущені пропущення , політика виведення з ладу з навантаженням
- **Стиснути історію**: компактний бінарний постійний склад , періодично сконцентрований МSK2 доступний за межами потоку запиту
- **Аудитувати все інше**every accumulator answers what bounds it , What evicts it МSK2 What is the worst case
- **Центрізувати кнопки**: один блок конфігурації , не п 'ятнадцять `const`s
- **Заплануйте перегляд**: баг існує лише в запущеній системі

Управляйте формою, не симптомомM SK1

---


## Дальне читання

**Серія випуску StyloBot**

- [Подібність, Не ідентичність](/blog/stylobot-fingerprint): модель поведінки і кластерування Лейдена у цьому шарі - це пам 'ять для
- [Подібність-Усвідомлення ASP.NET UI](/blog/behaviour-aware-ux): поверхня, яка споживає рішення розпізнавання в Razor
- [Навчитися швидше](/blog/stylobot-release-learning): - це система навчання з чотирьох рівнів, і кэш вироків, який ця дисципліна пам 'яті тримає обмеженим
- [Дослідження Bot Part 2: Signature Pipeline and StyloBot Architecture](/blog/botdetection-part2-signature-pipeline-and-stylobot-architecture): оригінальне виявленняM SK1engine architecture post
- [Бот-детектор - частина 3: якомога простіша](/blog/botdetection-part3-as-simple-as-possible): двоє МSK1 падіння лінії МSK2 в ( де перший був введений індикатор HNSW у процесі мSK4

**HNSW і векторний пошук у цьому блогі**

- [Self-Схований векторні бази з Qdrant](/blog/self-hosted-vector-databases-qdrant): Глибоке плавання HNSW `M`, `ef_construct`, налаштування
- [Гібридні пошуки та індексування RAG](/blog/rag-hybrid-search-and-indexing): HNSW як щільна половина гібридної ретривалої трубки
- [Найменше життєздатний граф РАГ](/blog/graphrag-minimum-viable-implementation): DuckDB VSS HNSW gotcha (`array_cosine_distance` проти `array_cosine_similarity`)
- [Семантичний пошук за допомогою ONNX та Qdrant](/blog/semantic-search-with-onnx-and-qdrant): Конфигурація HNSW через Client Qdrant .NET
- [GraphRAG: Чому векторний пошук ламається на рівні Corpus](/blog/graphrag-knowledge-graphs-for-rag): чому чистий АNN не достатньоM SK1 в масштабі

**Внешні показники**

- [HNSW (WikipediaM SK1](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world_graphs)
- [Мальков МSK0 Яшунин , *Надзвичайний і надійний пошук найближчих сусідів, використовуючи ієрархічні графи маленького світу*](https://arxiv.org/abs/1603.09320): оригінальний МSK1 папір
- [Pinecone: Faiss M SK1 HNSW пояснювач](https://www.pinecone.io/learn/series/faiss/hnsw/)
- [Концепції індексування Qdrant](https://qdrant.tech/documentation/concepts/indexing/#vector-index)
- [.NET Docs Large Object Heap](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap)
- [`dotnet-counters` довідка](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters): знаряддя, що покрило поверхню LOH в першу чергу
- [Команда DotMemory JetBrains-лінний profiler](https://www.jetbrains.com/help/dotmemory/Command-Line_Profiler.html): глобальний \ / \ CLI \ МSK2 \ профайлер пам 'яті інструментів, використаний для цієї переробки
- [Команда DotTrace JetBrains-лінний profiler](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html): одна і та сама форма для виконання
- [sqlite-vss](https://github.com/asg017/sqlite-vss): SQLite + Faiss, якщо і коли тривалість шару виростає за межі жорсткої сили

Источник implementу: [github.comM SK1scottgal/stylobot](https://github.com/scottgal/stylobot). Живий двигун , панель управления МSK2 та коммерціальні прилади [stylobot.net](https://www.stylobot.net).