Long- 运行服务一般会慢慢地向无约束的内存漂移\ .\ 此处 @'}我如何发现这种飘移, 我是如何想在不打印它的情况下修补它的?,\ 以及 StyloBot' 矢量相似层的一个工作范例。 从“13” GB(大型物体升降)到 “6” MBZK8*
StyloBot 释放系列
- 行为@, @% 不是身份为何StyloBot以行为方式模拟客户?
- ASP=.@NET 用户界面@ : 服务器@ MS K1 在检测结果上方的表面
- 在LongM-Running .NET服务中查找和固定无约束增长@:@ 可靠性纪律 @ ( @ 工作示例 @ I:_ StyloBot)}
- 檔案型態Script UI: Express, fastifyMS K2 和浏览器组件
- 侧车建筑@ : 检测引擎如何连接到非@ MS K1NET 堆栈
- 学习加快速度“:”适应性学习系统“, 4 -”记忆和裁决缓存
- 静坐不动的测试: 校验纪律 @: 一个 BDF 文件驱动回溯@,load @MS K3和校准
- StyloExptracte {-} 本地学习 HTML 到 标记下转换器: HTML→ Markdown 层与探测器对齐 MSSK2 行尸虫清晰识别捕捉了, 和狗食环让它诚实
工作实例是 StyloBot 矢量层@,,但该学科是 @'tStyloBot-}特定 任何长的- 运行着“.NET” 服务,它积累了交通状态,最终会形成一种在测试或短程负载中可以捕捉到的错误=+'}.}但它只在实际流量数日后才会出现_.}这篇文章是我用来找到它的游戏手册
行为模式在 行为@, @% 不是身份;ASP.NET表面 ASP=.@NET 用户界面;来源于 GithubMS .comMS K1scottgal/stylobot (星际机器人).
Long- 執行服務累积@ . @ 每個快取目錄@ I,} 每個學習商店@ MPK3 每個 "we' 只會保留最後的N要求 " 缓冲区只是一個微小的caculator MS K7 每個人都自己看起來很好
你抓住这个 故意寻找它@,}关于一个过程的'* 运行时间足够长,足以让形状出现 .
本学科的第一个部分是“'@t technology @.}它的日历条目‘.’ 。
我停止添加功能, 只要看看运行系统 在现实的交通 #,#问 这看起来有错吗 ? 没有特定的bug@ , @ 没有目标 @ MS K1 @ 仅仅看 @ MPK2 @ 运行系统静悄悄地 *;} 他们没有大功告成 直到它们失败前
最新审查发现此文章的错误, Mostlylucid.BotDetection.Demo 以合成试验交通为例 .}该数字不能维持三十秒的注意
规则#: 在日历上设置一个重复的空格, 以读取您自己的标准@ . @ not to fix anything .} 只看 @ MS K2}
用于“"”的正确工具在 “.” NET 进程上是错的 @ ?"+% dotnet-counters@ . @ it' @ sfree@ MS K2} 它已经安装了 @ I' @ 并且它告诉了你 你的程序正在做什么的真相
稍后在 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
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}这足够知道什么是错的 {\fn方正准圆简体\fs12\1cHC9ECC4}没有读出任何代码 大型天体 heap 正在不断地分配巨型物体 , 和Gená2GC可以跟得上它们
大型天体 heap 是您第一次描述一个长的“-”运行中 @.NET service@.}它最常见的惊喜之一, 因为几乎每个人都遇到困难的方式_:}
根据我的经验,LOH意外增长的最大来源 在真实的“.NET services ”,中
| “ | ”源於「 | 」 为什么它最终会出现在LOH ‘ | ’上? |
|---|---|---|---|
JsonSerializer.Serialize(obj) 返回a string |
}一个 MB 对象变成 一个 MSC2 的 MB UTF=-16}字符串=,}全部在同一分配中 * | * | |
MemoryStream 您让未绑定的“ |
” 内部缓冲在 @ 85 KB 上双倍增长, 并留在 LOH + | ||
byte[] buf = new byte[n] 用于大 n @ |
直接LOH分配; 非常常见在文件中}♪/}网络读作@ | ||
List<T> 后补阵列横跨 @85}KB 然后降落在LOH + |
|||
string.Concat / StringBuilder 在大文本上方 ToString() 是单一的大笔拨款 |
|||
XmlSerializer / DataContractSerializer 和 JSON 案的形状相同 |
如果你只记得一个大拇指规则 : 怀疑在定时器上或按要求生成单个毗连大物体的代码路径@. 和在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 >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 -=YTET -伊甸园字幕组=- 翻译:
dotnet-gcdump 和 dotnet-dump 在视觉工作室或 PerfView@. 中您可以打开的快照
《观察展望》 用于追踪下至堆叠的资源分配量@. @%
JEBrains CLI 定位仪 @ (} @ action: what I actually used to this rework=). {现在包装成适当的 @ . nET global moments@,}所以没有头部的@ MS K4 远程 @/\ CI 运行时描述不需要 Rider GUI_:}
JetBrains.dotMemory.GlobalTools@ : @ 附加@ MS K1 @ 抓取一个记忆快照@ I,} 打开 .dmw 工作空间后看哪些保留根持有LOH分配款@.JetBrains.dotTrace.GlobalTools@ :} 用于取样的相同形状 @ I/ @ 跟踪@ MS K2 时间轴数据 * .} 当您需要堆叠而不是对齐时使用此形状 * @ MPK4 @ e @ MOSK5g. @ MASSK7 @ 什么是这个计时器实际上正在做\ ?".}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 壓力-OffLOH clatition. RecyclableMemoryStream 从微软池库缓冲器中提取来避免最初的分配@. 两者都是石膏_. 真正的修复方法几乎总是要停止生产巨型物体_MS K2
所以只要你看到LOH在长期的-运行服务中稳步增长 ,,问题几乎总是相同 @'_%s 分配非常大型的物体@,}关于什么内容? 解答的形状 @(timer?}请求處理器@? recirizer @MS K3缓冲池_?) 指示您在代码中查找何处 。
例外处理太过重要, 4/sec 联 年 月 日 SqliteException 低到不显示在日志中任何人读@,}但高到可以告诉你代码路径是静悄悄地重试.}这样类型的线索定期复查会发现事故反应从来就不是 @.}
规则#: 当事情看起来不对劲时",}确认使用 dotnet-counters 在猜测“.”之前, 数字会将搜索半径缩小为星等的顺序@ .
这里的学科变得有趣了 ,,因为诱惑是 补贴和 而非质疑抽象的症状....
Quick vocab {(} 用于接下来的几节 @ )
- 矢量: 固定的- 浮标大小阵列 @- e.g}.
float[64]. @ 每个槽都是请求的量度属性 @(@header count%,}时间,}IP family_,}等等□.).}两个要求""{eel 类似="}在“64-维空间”中产生彼此相近的矢量float[].- 矢量相似性通常都是: 余弦相似性 “-”是两个矢量之间角的余弦,一种叫做“.”的方法。
- 最近的近邻 @(_ANN)}: @ @ MS K1 @ given this containor % 1} 找到最接近的向量 % 2} find the closest N of million\ most ,}快速 ". Brute-}參考是 OQ}NQNSK7}每個查詢}.}ANN算法都以偶爾失蹤為代價 得到 sub=-milli秒的結果 真实 最贴近的匹配@. @%
- HNSW 赫尔辛基: ANN 算法之一 @. 它构建了一个多层图, 上面一层有几口井@-连接的节点和下层填充细节. 您在顶部输入并在“.”中缩放为=%, 稳定 不是为 " 设计的矢量集.}每份请求都增加一个旧的, 移出@"}{-} 这整篇文章的相关点 @.}
- 中枢: 一组矢量的平均值 @- 一种矢量来总结群集@. 如果您有“10,000 矢量看起来大致相似”, 您可以扔掉它们并保留一个中子{; 机器人失落了,但紧凑的代用品}. 压缩策略用它将很多原始历史转换成一些简略的历史_.
对于StyloBot, 来说,LOH的增长导致了三个在结构上完全相同的等级。 HNSW 赫尔辛基 “(”世界历史导航小世界“)”图表“Z(”原版 Malkov @&_# Yashunin 纸用于在签名@,}会话 @,}和意向矢量上进行类似搜索的 )}每人都有像:}这样的字段
private readonly List<float[]> _graphVectors = new();
每一个都由一个学习处理器喂养, LearningEventType.FullDetection火烧的, 每个 HTTP 请求@. @每一份请求都加上矢量@.}每一份请求都没有被逐出 .
每5分钟 , 自动存取定时器将整张图表连载到JSON 然后写成磁盘
private readonly TimeSpan AutoSaveInterval = TimeSpan.FromMinutes(5);
在演示规模下, signatures.vectors.json 在生产过程中 : intent.meta.json MB , intent.vectors.json 在“51”MB.和“JSON序列化”中,在“MS K2” MBZ.创建了一条毗连的内存字符串。
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<float[]><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校对:Portnoy
香港新南威尔士州是绝佳的技术,带有 Qdrant 的自定义“-” 矢量数据库, RAG混合搜索和索引, 最小可见图形). 松松, 断断, pgvictor 变量,和 解冻 内部所有使用@. @ but it'}但是它是一个 Cample_,}索引而不是一个缓存{,} 而StyloBot实际上需要的是一个缓存}=:} @" 用于每个活动指纹@, 保留一个小窗口的最近行为矢量, 这样检测可以比较当前请求与过去类似要求的行为=." 重覆 Bot 指纹 重复 ( 保持热); 人类指纹... *' t @(evict). 尽可能简单 @-_process HNSW 索引被列出为一个特性@;}这篇文章是随后的#-\up 承认错误的地方
上面有'QQQS a quick test of the smell@:}如果你想象一下在结构上打个硬盖... ...",\Q"是这个结果,你想要的是什么? ?
规则#: 当您看到无限制增长时 @, @ 问题不是 我们如何控制这个结构? 但是,但 @"#这是正确结构 我们做我们该做的: 'BARBAR?"} 第一个隐藏症状 .,第二个听到什么 它的'告诉你
当您在 StyloBot 上命名错误的抽象@,}替换者通常会自己写 @._StyloBot 是两层=:}
热层@: @% a 受约束的 BoundedVectorCache<TEntry>, 一个薄包装纸围绕 ConcurrentDictionary 保留计分器给 Bot- 分类条目“2x生存权重”\ , 因此在交通模式周围的缓存自=-组织@:
retentionScorer: (_, entry) => entry.WasBot ? 2.0 : 1.0
常态层(FOSSMS K1 3个新的 SQLite 表 @ ( @signature_centroids, session_centroids, intent_centroids) 储存压缩的中央机器人 从夜间 VectorCompactionService. 矢量作为原始浮标存储 MemoryMarshal.AsBytes:
internal static byte[] PackFloats(float[] v) =>
MemoryMarshal.AsBytes(v.AsSpan()).ToArray();
常规二进制序列@. @No @100+_MB JSON字符串 @MS K2}没有LOH.{
存储阶梯注. SQLite 是指 FOSS @/ @ 单 @ - 二进制后端@-} 正确呼叫“ MS K3” 把前一号扔在Pi上, 并用 PostgreSQL 用 pgvictor 变量, 给予您适当的索引相似性搜索@ MS K1HNSW/IVFFlat 数据库内 @.}相同的建筑规则@,}不同的后端#. sqlite-vs 如果您超越 SQLite , 但想要保持单身\ - pinary; 更清洁的升级是 Postgres 梯级\ MPK2\ }
对自由和开放源码软件持久性层的相似性搜索是各行Brute-Force cosine,通过SIMD {(}加速。System.Numerics.Tensors.TensorPrimitives在压缩的-centroid size @MSK2100s 到几千行@),}一个完整的扫描在Pi~1-2ms上
注 注 L1/L2 压缩校对:Soup
这是同样模式的 LSM-}树枝存储引擎 @ (RocksDB, 级别 DBMPK3卡珊德拉MS K4用于SSTabletsMSKO5 等级@0 持有最新细微的-Grained dataMSQK8 下层持有压缩的数据,简略数据_.借用它,因为访问模式与最近的数据相同最常用的是点击最近的数据}有一小部分读数需要长的尾巴*;}保持每一条原始行永远<.>没有好处
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
常规形状 *\ (}稳定状态 {+ 为历史的 +}BAR 一个定期压缩器.}当有人找到HNSW或Pgvictor的工作量, 而工作量实际上是一个缓存@,}这是他们本该更便宜的、更简单的东西。
规则#: 更喜欢适合 运行时间模式“,”不是符合纸上的数据集的“'”大小
@,}因为它关于克制,而不是附加#.
如果缓慢的“-”和“fix 路径”路径包含一个数据库查询@,, 诱惑是将其上的快速路段设置为“ .唐't}.StyloBot 中的检测快路是同步的,, 而相似性检查是一个非“MS K6堵塞字典查找_:
if (!_cache.TryGet(signatureId, out var entry))
return null; // no signal this request - other 48 detectors still run
遗漏意味着 没有任何相似性信号表示这一请求其它探测器仍在运行@.}在请求完成后背景处理器查询 SQLite, 并给下次的缓存热 ;}快速路径从不阻隔数据库查询.}
这是大多数缓存错误的地方 @ : @ 人们建立一个缓存@ , @ 使后退同步的“ I"” 为正确性 @ MPK3, 而错位路径则变成 “ MS K4+% s pQ99}pQMSK5 spignation_ . ” 该缓存旨在让事情更快地 @ OMK7 @ 代之以它使得最糟糕的情况更糟\ MSKO8 @
规则#: 如果您能忍受一个错误的, 您没有\ '" 没有缓存 . 你有一个漂亮的队列@.}
一旦你找到了一个未绑定的结构 , 假设还有其他结构
审计是机械化的“.”审计,对于每个长长的“-” 词典来说都是机械式的 “MSK1 活字典” “MS K2” 列表“,” 或者设置为“MSC4” 由谁来决定条目何时离开“,”和在敌对交通下最坏的情况是什么? 如果您能用一句话回答所有三句话“, ” 它“MS K2” 是一个漏出的 “.” “ @(Same colory as the single 短讯信号 模式@: @ 任何累积的必须衰变的东西@ ,_ 驱逐 @ MS K2} 或缩写@ MPK3 *
对于StyloBot, 大部分蓄积器已经很好了 -MSSK2
EphemeralPatternReputationCache“:”硬盖在“10,000”条目中,带有背景衰变和LRU驱逐“MS K2” Neutral → Suspect → ConfirmedBad 和不对称歇斯底里住在一起 学习加快速度)BehavioralPatternAnalyzer: IMemoryCache 以 per @-}身份限制 {(50} 路径_ _,}\ MS K4 时间 , NSK6min TTL)DriftDetectionHandler: MS K1 型態× NSK3 樣本, TTL-prunedSessionEscalationService@: @ @ 35- @ minute TTL 用计时机 @ I-# 驱动驱逐超越HNSW班级需要的关注 MarkovTracker._cohortBaselines# .# Markov 链条 运行跟踪器维护 per-\ccohort 基线过渡矩阵@ (\sparate MaxTrackedSignatures“).” 组群基线“(one”,每个交通群组,如“MS K2 datacenter”、“-new @"}或“" SelfMaintenanceOptions.MarkovCohortSize.
规则#: 每个单顿收藏都回答三个问题, 或者它“'” 是一个bug:}是什么界限 它被驱赶到什么? “,” 什么?
@ "No contribution@ MS K1之后的下一个失败模式是 @ I"bound: @ MPK3%s wrong for this general_ ". hard# -} MaxEntries = 10_000 直到有人在Pi4,或装有 @256MBZ.的容器上 运行您的服务为止
对于StyloBot MSMK0来说,每个限制都落到单线下 SelfMaintenanceOptions 区块内 appsettings.json. 默认值适用于标准服务器@. 限制的硬件 @' a LowMemory 固定预设@ : @
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;
});
或经由 appsettings.json 用于环境的 M- @ 特定调制@ : @
{
"BotDetection": {
"SelfMaintenance": {
"SignatureCacheSize": 1000,
"SessionCacheSize": 500,
"IntentCacheSize": 300,
"CentroidRetentionDays": 14
}
}
}
更深的点@ : @ 一个地方设置的绑定@ I'}% 1} 是操作员可以解释的关于 < . * 一个绑结,%'_ 覆盖了15个区域 const int @._Councilizing the knobs is half of yourself@-_BARBAR_没有人能解释这些声明的理由吗?
规则#: 每个操作员都可能想要改变 硬件使用寿命 在一个配置街区,}没有分散在代码库@.}
StyloBot 具体为 @(_FOSS 建设@,}低记忆预设):}
| @ | }元件@ | #之前 @MS K2_% | |
|---|---|---|---|
| 簽署 HNSW 索引 * | 未到達的 LOH *MS K2 * @~256 KB 熱藏快取 * | ||
| 会话 HNSW 指数 @ | 无界LOH @MS K2@~258KB热缓存 @ | ||
| 本端 HNSW 指数 @ | 无路的LOH @MS K2@~43KB热缓存 # | # | |
| @ | _JSON 自动保存缓冲器 @MS K1}#100-500_MBLOH 每毫微秒$5}( | Q)♫ @0 | # |
| }MB MT MS K4QK cap) |
检测模式没有变化.}改变的是 如果存在相似性证据 和 当它“'”允许影响快速路径时“.”中心在SQLite {(}FOSZ})}或邮差+}pgvector @(}$paid_);}夜间压缩仍然生产着LMS K6LMSSK7}它们不需要无约束的内存.}
在Pi4中, 自由开放源码在“ 500 MBRS”下建起的座位。
添加上限约束内存而不改变架构@. @% 错误的抽象始终是错误的 @ ; @% 这个症状只会变得安静点 @ I.}下一个触摸系统的人会继承一个几乎能起作用的结构_ MS K3, 这比显然不具有' @ t.# 更糟糕
在长“-”正在运行的学习系统中循环的模式@:
const瑞 s固定形状@,}不是症状=.}
StyloBot 发射系列
HNSW 和此博客中的矢量搜索
M, ef_construct,调音array_cosine_distance Vs 和 array_cosine_similarity)外部参考资料
dotnet-counters 参考文献编号“: ” 工具, 它首先浮现了LOH执行源来源 @: GithubMS .comMS K1scottgal/stylobot (星际机器人).+Live 引擎 @,_QTaptro@,#和商用控制 stylobot% . @ net.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.