# 学习LRUs @-}当超能力使你的系统变得更好时

<!--category-- ASP.NET, Architecture, CQRS, Bot Detection, Caching, Systems Design -->
<datetime class="hidden">2025-12-09T12:00</datetime>

多数系统在超载后会退化@. @ memorys fill up,}查询慢下来 @,}用户抱怨#,}服务器崩溃=.}

几个不同寻常的 *更好*.

本条说明: **基于 LRU 的行为内存** 当它触动到能力时, 自我=-\优化 =\ - 和这种模式如何驱动我的学习系统 [机器人检测引擎](/blog/botdetection-introduction). 这也是尽可能最小的版本 [DISE 建筑结构](/blog/dise-architecture-overview) -通过资源压力控制进化

如果你读了我的文章 [CQRS 和事件检验](/blog/moderncqrsandeventsourcing)您的, @ '}会认出这里的一些模式@. @ 但是这是 CQRS 被剥除到骨骼上的 CQERS {-}\ n无事件商店{MS K4} 沒有預測,}沒有 Marten}.} 只是一個記憶缓存_, 背景工人,}和 SQLite+.}

[TOC]

## 预算基本行为记忆

在我们潜水前 请让我定义一个术语 I' 会在整个:中使用 **签名**. 签名是代表一种行为模式的任何稳定键@- a hash of IP IP @+%-Agenti,}标题组合的指纹{, 检测器MS K6ZMK7此请求看起来像 X". 缓存存储这些签名以及随着时间推移而演变的学习重量\.}

如果你能建立一个 能够建立:的系统

- 在“1”%s下响应
- 从不在数据库中填入区块
- 压力下的自定义@- @opptimis
- 忘记什么并不重要
- 记得什么是

这到底是什么? `IMemoryCache` 滑动到期后, 如果您了解您正在建造的“ '” 是什么的话, 您将获得 MPK0 @ label

### 什么是路透缓存实际上

LRU ( @Lestern 最近使用过的)}缓存将最近访问到的条目驱逐@'}当缓存填满了,}最冷的条目被扔出去,让它们能容纳热的条目_.}*

多数开发商认为这是一个限制@. @% *@ "Oh no,我的缓存已满 *, 数据正在丢失*!"*

但对于行为系统来说 , 这是个特写

```csharp
// From WeightStore.cs - the bounded memory window
_cache = new MemoryCache(new MemoryCacheOptions
{
    SizeLimit = _cacheSize,           // e.g., 1000 entries
    CompactionPercentage = 0.25       // Remove 25% when limit reached
});
```

这: `SizeLimit` 不只是一个内存限制@ . @ it'}a **选项选择压力**@. 它决定了系统 $#"\}{"}和迫使它关注什么重要事 □.}

### 滑动过期@ - @ 忘记的时钟

加上滑动过期@,}您会自动忘记 @:

```csharp
// From WeightStore.cs:254-259
private MemoryCacheEntryOptions GetCacheEntryOptions()
{
    return new MemoryCacheEntryOptions()
        .SetSlidingExpiration(_slidingExpiration)  // 30 minutes
        .SetSize(1);  // Each entry counts as 1 toward size limit
}
```

如果签名在 @30 minutes@,}(分钟 1) 内访问到的签名是“'Qt ” ,那么它会驱逐{'不是因为它<'> 错误了-} 因为它#'{不再相关 *_.}

由此创建 **自然遗忘**:

- 实施伙伴被重新分配
- Bot 旋转签名
- 交通模式转变
- 昨天的'攻击是今天的'

静态区块列表会变平@ . @ 滑动过期, 让内存保持清新Q.@

## ImemoryCache 模式@ - 微小 CQRS 不说 CQRS

这里“'}这是它赖以运作的模式 ” @. `SqliteWeightStore` 类别 @: @%

```csharp
/// <summary>
///     SQLite implementation of the weight store with sliding expiration memory cache.
///     Uses a CQRS-style pattern with write-behind:
///     - Reads: Hit memory cache first (fast path), fall back to SQLite on miss
///     - Writes: Update cache immediately, queue SQLite writes for background flush
///     Sliding expiration provides automatic LRU-like eviction behavior.
/// </summary>

public class SqliteWeightStore : IWeightStore, IAsyncDisposable
{
    // Memory cache with sliding expiration - auto-evicts least recently used entries
    private readonly MemoryCache _cache;

    // Write-behind queue for batched SQLite persistence
    private readonly ConcurrentDictionary<string, PendingWrite> _pendingWrites = new();
    private readonly Timer _flushTimer;
    private readonly TimeSpan _flushInterval = TimeSpan.FromMilliseconds(500);
```

这是 [非正式的CQRS](/blog/moderncqrsandeventsourcing#part-2-the-half-assed-approach-cache-invalidation) {-}但若无乏味的缓存失效>.} 而不是在写<,}后取消缓存条目。 **缓存是写模型**.SQLite只是永久的分类账

```mermaid
flowchart LR
    subgraph Cache["In-Memory Behaviour Store"]
        A[Hot Signatures] --- B[Sliding Expiry]
    end
    subgraph DB["SQLite Ledger"]
        C[(Durable Write-Behind)]
    end

    A --Periodic Flush--> C
    B --Eviction--> D[Forgotten]

    style Cache fill:none,stroke:#10b981,stroke-width:2px
    style DB fill:none,stroke:#6366f1,stroke-width:2px
```

关键洞察力: **读写到内存**数据库最终是一致的 -和'都很好

## 总是读取路径@ - @ 缓存 first @ MS K1}

当检测器需要学习重量时\ ,}它会击中缓存@:

```csharp
// From WeightStore.cs:421-472
public async Task<double> GetWeightAsync(
    string signatureType,
    string signature,
    CancellationToken ct = default)
{
    var key = CacheKey(signatureType, signature);

    // Check cache first (fast path - no DB access)
    if (_cache.TryGetValue(key, out LearnedWeight? cached) && cached != null)
    {
        _metrics?.RecordCacheHit(signatureType);
        return cached.Weight * cached.Confidence;
    }

    _metrics?.RecordCacheMiss(signatureType);

    // Cache miss - load from DB
    await EnsureInitializedAsync(ct);

    await using var conn = new SqliteConnection(_connectionString);
    await conn.OpenAsync(ct);

    var sql = $@"
        SELECT weight, confidence, observation_count, first_seen, last_seen
        FROM {TableName}
        WHERE signature_type = @type AND signature = @sig
    ";

    // ... execute query ...

    if (await reader.ReadAsync(ct))
    {
        // Cache the result for future reads
        var learnedWeight = new LearnedWeight { /* ... */ };
        _cache.Set(key, learnedWeight, GetCacheEntryOptions());

        return weight * confidence;
    }

    return 0.0;  // No learned weight exists
}
```

热路 **从未访问过数据库**SQLite只是备份存储@. @

## 写入路径@ - @ 缓存立即,+Persist 稍后

当系统学习一些新@ ,} 它会立即更新缓存并排队数据库写入\ MS K1}

```csharp
// From WeightStore.cs:551-582
public Task UpdateWeightAsync(
    string signatureType,
    string signature,
    double weight,
    double confidence,
    int observationCount,
    CancellationToken ct = default)
{
    var key = CacheKey(signatureType, signature);

    // Update cache immediately (source of truth for reads)
    var learnedWeight = new LearnedWeight
    {
        SignatureType = signatureType,
        Signature = signature,
        Weight = weight,
        Confidence = confidence,
        ObservationCount = observationCount,
        FirstSeen = DateTimeOffset.UtcNow,
        LastSeen = DateTimeOffset.UtcNow
    };
    _cache.Set(key, learnedWeight, GetCacheEntryOptions());

    // Queue for async SQLite persistence (write-behind)
    QueueWrite(signatureType, signature, weight, confidence, observationCount);

    return Task.CompletedTask;
}
```

通知@: `UpdateWeightAsync` 返回返回 `Task.CompletedTask` @ - @ it'} 基本上同步@ MS K2 @ 书写是队列的\ , @ 未执行 @ I. @ 这意味着@ MPK5 @

- Sub-milli 秒写缓存
- 在 I/O 上没有屏蔽
- 写作是联合的 @(#最后一次写入 wins@)}%

## 背景 Flusher @ - @ @ I500#% 无聊魔术的背景

@500@ms@,}每封未写的文章都会被冲到 SQLite 上,

```csharp
// From WeightStore.cs:274-357
public async Task FlushPendingWritesAsync(CancellationToken ct = default)
{
    if (_pendingWrites.IsEmpty) return;

    // Only one flush at a time
    if (!await _flushLock.WaitAsync(0, ct)) return;

    try
    {
        await EnsureInitializedAsync(ct);

        // Snapshot and clear pending writes atomically
        var writes = new List<PendingWrite>();
        foreach (var key in _pendingWrites.Keys.ToList())
        {
            if (_pendingWrites.TryRemove(key, out var write))
            {
                writes.Add(write);
            }
        }

        if (writes.Count == 0) return;

        await using var conn = new SqliteConnection(_connectionString);
        await conn.OpenAsync(ct);
        await using var transaction = await conn.BeginTransactionAsync(ct);

        try
        {
            var sql = $@"
                INSERT INTO {TableName}
                    (signature_type, signature, weight, confidence,
                     observation_count, first_seen, last_seen)
                VALUES (@type, @sig, @weight, @conf, @count, @now, @now)
                ON CONFLICT(signature_type, signature) DO UPDATE SET
                    weight = @weight,
                    confidence = @conf,
                    observation_count = @count,
                    last_seen = @now
            ";

            foreach (var write in writes)
            {
                await using var cmd = new SqliteCommand(sql, conn, transaction);
                // ... add parameters and execute ...
            }

            await transaction.CommitAsync(ct);
            _logger.LogDebug("Flushed {Count} pending writes in {Duration:F1}ms",
                writes.Count, sw.ElapsedMilliseconds);
        }
        catch
        {
            await transaction.RollbackAsync(ct);
            throw;
        }
    }
    finally
    {
        _flushLock.Release();
    }
}
```

这是 **事件@-_group @-}光**MSKO你得到的:

- Batched 寫作 @(#appactive I/_O)}
- 交易一致性
- 如果相同的签名在 @500}%s@ms=,}中更新了 10次,则仅写入最终值@)}
- SQLite 对这种访问模式非常满意

## EMA 更新 -学习指数移动平均值

当新观测到达时, 系统使用指数移动平均值来更新重量@:}

```csharp
// From WeightStore.cs:584-635
public Task RecordObservationAsync(
    string signatureType,
    string signature,
    bool wasBot,
    double detectionConfidence,
    CancellationToken ct = default)
{
    var key = CacheKey(signatureType, signature);

    // Calculate new weight using EMA in memory
    var alpha = 0.1;  // Learning rate
    var weightDelta = wasBot ? detectionConfidence : -detectionConfidence;

    double newWeight;
    double newConfidence;
    int newObservationCount;

    if (_cache.TryGetValue(key, out LearnedWeight? existing) && existing != null)
    {
        // Apply EMA: new_weight = old_weight * (1-α) + delta * α
        newWeight = existing.Weight * (1 - alpha) + weightDelta * alpha;
        newConfidence = Math.Min(1.0, existing.Confidence + detectionConfidence * 0.01);
        newObservationCount = existing.ObservationCount + 1;
    }
    else
    {
        // First observation
        newWeight = weightDelta;
        newConfidence = detectionConfidence;
        newObservationCount = 1;
    }

    // Update cache immediately
    var learnedWeight = new LearnedWeight
    {
        SignatureType = signatureType,
        Signature = signature,
        Weight = newWeight,
        Confidence = newConfidence,
        ObservationCount = newObservationCount,
        FirstSeen = existing?.FirstSeen ?? DateTimeOffset.UtcNow,
        LastSeen = DateTimeOffset.UtcNow
    };
    _cache.Set(key, learnedWeight, GetCacheEntryOptions());

    // Queue for persistence
    QueueWrite(signatureType, signature, newWeight, newConfidence, newObservationCount);

    return Task.CompletedTask;
}
```

EMA 公式平滑学习@: @% `new_weight = old_weight × (1 - α) + new_value × α`

带着=和0.1:

- 新证据提供#10%
- 历史证据提供#90%
- 这防止了一次观测的野生波动

## 为什么超支使系统成为系统 *更好*

这里的'是大多数人怀念 .的关键见解

当缓存填充:时

- 低端-}频率信号掉出
- 只有热的 @ ( @ 经常访问@ MS K1 @ 签名留在记忆中
- 数据库的时差以一个电流循环@ - @ 和该+'}%s fine
- 系统 **焦点集中** 压力下

想想看,如果“50,000”独有的签名击中了您的机器人探测器@,}但您只有对“MS K3”的内存。

**最热门的10,000** 通常代表实际交通量的99%

在制作《,》中,我看到大约每天 . 1-off 签名 “(crappers 尝试一次“,随机探查”\MS K5 合法用户,他们再也不会回来") 和也许5–10,000 不断回覆的<.}这些#5–10,000是风险生命中的“99%MSK11k12tail sign@?noiseMSQZ14t 伤害检测精度 {'}它甚至会通过减少低-信任模式的假阳性来改善它

```mermaid
flowchart TB
    subgraph Input["50,000 Unique Signatures"]
        Hot[Hot Signatures\n~10,000]
        Cold[Cold Signatures\n~40,000]
    end

    subgraph Cache["Bounded Cache (10,000)"]
        Kept[Kept in Memory]
    end

    subgraph Evicted["Evicted"]
        Lost[Forgotten\nNoise Traffic]
    end

    Hot --> Kept
    Cold --> Lost

    style Hot fill:none,stroke:#10b981,stroke-width:2px
    style Cold fill:none,stroke:#94a3b8,stroke-width:2px
    style Kept fill:none,stroke:#10b981,stroke-width:2px
    style Lost fill:none,stroke:#ef4444,stroke-width:2px
```

溢流 **锐利** 行为内存@. @ 系统自定义 @ - @ optiminises. @

### 当溢出时

这是't magic}. 有一些边缘案例 LRU的压力对你起作用

- **缓存太小了**: 如果您的缓存只持有 @100条目,但您有真正重要的签名@1,000}{,} 您在积累足够证据前会不停地发作并失去有用的模式\□'ll}

- **统一运输**: 如果您在微小的内部系统中运行, 其中几乎一切都是 @"Hot" (ffew 独有的签名 @MS K5}所有重复的\), 溢出都使您的收益更少 @.}选择压力对“.”无可选择

- **Cold- 启动问题**最新部署的系统没有学习到重量 所有东西都同样寒冷 在热点信号建立之前 前几小时会有更高的假正率

该模式在您拥有时最有效 **具有功率的高度基点@- @Law 分布** “-”很多独特的签名“,” 但是一个小子集 控制着交通量“MS K2” 那“MSC3”和“机器人检测流量”完全一样

## 将缓存和数据库保存在同步的 - Tag\ I- 基于无效

缓存是读取的真理源@,}但数据库是持久的分类账 @.}当它们漂移时会怎样?

### 衰减的同步同步化

当数据库重量衰减\ ,时,缓存需要跟随\ MPK1 `DecayOldWeightsAsync` 使用两种方法处理 :\ {}

```csharp
// From WeightStore.cs:725-766
public async Task DecayOldWeightsAsync(TimeSpan maxAge, double decayFactor, CancellationToken ct = default)
{
    await EnsureInitializedAsync(ct);

    await using var conn = new SqliteConnection(_connectionString);
    await conn.OpenAsync(ct);

    var cutoff = DateTimeOffset.UtcNow.Subtract(maxAge).ToString("O");

    // Decay old weights in the database
    var sql = $@"
        UPDATE {TableName}
        SET weight = weight * @decay,
            confidence = confidence * @decay
        WHERE last_seen < @cutoff
    ";

    await using var cmd = new SqliteCommand(sql, conn);
    cmd.Parameters.AddWithValue("@decay", decayFactor);
    cmd.Parameters.AddWithValue("@cutoff", cutoff);

    var updated = await cmd.ExecuteNonQueryAsync(ct);

    // Delete weights that have decayed below threshold
    var deleteSql = $@"
        DELETE FROM {TableName}
        WHERE confidence < 0.01 OR (ABS(weight) < 0.01 AND observation_count < 5)
    ";

    await using var deleteCmd = new SqliteCommand(deleteSql, conn);
    var deleted = await deleteCmd.ExecuteNonQueryAsync(ct);

    if (updated > 0 || deleted > 0)
    {
        _logger.LogInformation(
            "Weight decay: {Updated} decayed, {Deleted} deleted",
            updated, deleted);

        // Compact cache to remove stale entries
        _cache.Compact(0.25);
    }
}
```

数据库记录衰减后 @, @ we call `_cache.Compact(0.25)` -这迫使 `MemoryCache` 将最近使用过的“.”优先排序。 下一读将会从数据库中重新装入新值@.}

### Tag-基于驱逐

有时您需要取消整个类缓存条目 {-} 的无效性, 例如当再培训检测器时或当外部数据更改时\ :} 。

```csharp
// From WeightStore.cs:768-777
/// <summary>
///     Evicts all cached entries for a specific signature type (tag-based eviction).
/// </summary>

public void EvictByTag(string signatureType)
{
    // MemoryCache doesn't natively support tag-based eviction, but we can compact
    // For now, just compact - sliding expiration will handle stale entries
    _cache.Compact(0.1);
    _logger.LogDebug("Compacted cache for signature type: {SignatureType}", signatureType);
}
```

# .NET @NSK1%s# `MemoryCache` : 强制退出 stale 条目@, 让我们从数据库中读取重新聚集Q.

### 同步战略

关键的观点是 **完全同步是有必要的**系统容忍漂移,因为... ...:...

1. **DB 的缓存遗漏未重装入** - 如果条目被驱逐@, 下一读取新数据
2. **滑动过期处理柄的渐变** @ - @ {% 1} 未进入的条目@ MS K1 @ minutes auto @ MPK2 @ evict
3. **装甲部队的更新** - 定期压缩推出旧条目
4. **在联合后写-}更新** @-多个快速更新变成 DB 写入

这是最终完成的一致 右@. @% 此缓存会停留在数据库中, 而无需复杂的无效逻辑@ . @ @ action

```mermaid
flowchart TB
    subgraph Sync["Cache-Database Synchronisation"]
        D[Database Decay] --> C[Cache Compact]
        E[Tag Eviction] --> C
        S[Sliding Expiration] --> M[Cache Miss]
        M --> R[Reload from DB]
    end

    style Sync fill:none,stroke:#6366f1,stroke-width:2px
```

没有缓存无效性地狱@ . @ 无复式酒吧@ MPK1_ sub @ MS K2} Just clatication and 自然过期@ I.

## 正在深解@ : @ 声望系统

> **注::** 如果您想要 LRU @+}写下「 -} 后面的樣式」 < ,> 您可以在此停止此處 < MS K3} 這篇文章其他部分顯示我如何對全套名譽 {- – 州機}, – 歇斯底里\ , 和時代衰變♪.@ MSKO8_ 用于這些建構應變系統 < I. >

### 歇歇和衰变

对于典型的名声 ( 跟踪签名是机器人还是人,随时间推移 □), 同样的原则适用,但具有更多精密性 :

```csharp
// From PatternReputation.cs:42-108
public record PatternReputation
{
    public required string PatternId { get; init; }
    public required string PatternType { get; init; }
    public required string Pattern { get; init; }

    /// <summary>Current bot probability [0,1]. 0 = human, 1 = bot, 0.5 = neutral</summary>

    public double BotScore { get; init; } = 0.5;

    /// <summary>Effective sample count - decays over time, increases with observations</summary>

    public double Support { get; init; } = 0;

    /// <summary>Current reputation state - determines fast-path behavior</summary>

    public ReputationState State { get; init; } = ReputationState.Neutral;

    // Computed properties
    public double Confidence => Math.Min(1.0, Support / 100.0);

    public bool CanTriggerFastAbort =>
        State is ReputationState.ConfirmedBad or ReputationState.ManuallyBlocked;

    public bool CanTriggerFastAllow =>
        State is ReputationState.ConfirmedGood or ReputationState.ManuallyAllowed;
}
```

### 具有歇斯底西氏症的州过渡

模式从中立直接翻转到确认的Bad.

```csharp
// From PatternReputation.cs:367-421 - simplified
public PatternReputation EvaluateStateChange(PatternReputation reputation)
{
    if (reputation.IsManual)
        return reputation;

    var newState = reputation.State;
    var score = reputation.BotScore;
    var support = reputation.Support;

    switch (reputation.State)
    {
        case ReputationState.Neutral:
            // Can promote to Suspect or ConfirmedGood
            if (score >= 0.6 && support >= 10)
                newState = ReputationState.Suspect;
            else if (score <= 0.1 && support >= 100)
                newState = ReputationState.ConfirmedGood;
            break;

        case ReputationState.Suspect:
            // Can promote to ConfirmedBad or demote to Neutral
            if (score >= 0.9 && support >= 50)
                newState = ReputationState.ConfirmedBad;
            else if (score <= 0.4 || support < 10)
                newState = ReputationState.Neutral;
            break;

        case ReputationState.ConfirmedBad:
            // Can demote to Suspect (requires MORE evidence to forgive)
            if (score <= 0.7 && support >= 100)
                newState = ReputationState.Suspect;
            break;
    }

    // ... log state change and return ...
}
```

注意不对称@: @ it'}比被封锁更容易被堵 #.# `ConfirmedBad → Suspect` 需要@100}支持 @,}同时 `Neutral → Suspect` 这是故意的 - 它比怀疑更难原谅

### 时间衰落@ - @ 指数性遗忘

当图案变安静时 ,}它们向中立方向衰落

```csharp
// From PatternReputation.cs:334-361
public PatternReputation ApplyTimeDecay(PatternReputation reputation)
{
    if (reputation.IsManual)
        return reputation;

    var hoursSinceLastSeen = (DateTimeOffset.UtcNow - reputation.LastSeen).TotalHours;

    if (hoursSinceLastSeen < 1)
        return reputation;  // Too recent to decay

    // Score decay toward prior (0.5 = neutral)
    // new_score = old_score + (prior - old_score) × (1 - e^(-Δt/τ))
    var scoreDecayFactor = 1 - Math.Exp(-hoursSinceLastSeen / _options.ScoreDecayTauHours);
    var newScore = reputation.BotScore + (0.5 - reputation.BotScore) * scoreDecayFactor;

    // Support decay
    // new_support = old_support × e^(-Δt/τ)
    var supportDecayFactor = Math.Exp(-hoursSinceLastSeen / _options.SupportDecayTauHours);
    var newSupport = reputation.Support * supportDecayFactor;

    return reputation with
    {
        BotScore = Math.Clamp(newScore, 0, 1),
        Support = newSupport
    };
}
```

默认的时间常数@ : @ {}

- **计分衰减**@: @ @ 168 @ hours@(7_days @ I)}#-}分数移到中性, 经过一周的无活动之后
- **支持衰变 □**@: @ 336 @ hours@(14_ 天#)} @MS K4}信任在两周后下降 @MSKO5 @

这意味着一个已确认的 -bad IP 一个月静下来, 最终会下降回到中立\.}不是因为它改革了#-}因为它的证据变得迟钝

## 背景维修处

缩略 `ReputationMaintenanceService` 正在运行三个定期任务@: @%

```csharp
// From ReputationMaintenanceService.cs:48-129
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Reputation maintenance service starting");

    // Load persisted reputations on startup
    await _cache.LoadAsync(stoppingToken);

    var decayInterval = TimeSpan.FromMinutes(60);    // Hourly decay sweep
    var gcInterval = TimeSpan.FromHours(24);          // Daily garbage collection
    var persistInterval = TimeSpan.FromMinutes(5);   // Persist every 5 minutes

    var lastDecay = DateTimeOffset.UtcNow;
    var lastGc = DateTimeOffset.UtcNow;
    var lastPersist = DateTimeOffset.UtcNow;

    while (!stoppingToken.IsCancellationRequested)
    {
        await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        var now = DateTimeOffset.UtcNow;

        // Decay sweep: push stale scores toward neutral
        if (now - lastDecay >= decayInterval)
        {
            await _cache.DecaySweepAsync(stoppingToken);
            lastDecay = now;
        }

        // Garbage collection: remove old neutral patterns
        if (now - lastGc >= gcInterval)
        {
            await _cache.GarbageCollectAsync(stoppingToken);
            lastGc = now;

            var stats = _cache.GetStats();
            _logger.LogInformation(
                "Reputation stats: {Total} patterns, {Bad} bad, {Suspect} suspect",
                stats.TotalPatterns, stats.ConfirmedBadCount, stats.SuspectCount);
        }

        // Persistence: save to SQLite
        if (now - lastPersist >= persistInterval)
        {
            await _cache.PersistAsync(stoppingToken);
            lastPersist = now;
        }
    }

    // Final persist on shutdown
    await _cache.PersistAsync(CancellationToken.None);
}
```

垃圾收集器移除图案@: @%

- {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}早年
- 支持@≤ @ @ 1.0 @
- 中立国

这阻止了 - memory 字典不受约束地成长,同时保存宝贵的学习模式@. {

## SQLite 不是一个笑话 @ - @ it'# 这里完美

许多开发者对 PostgreSQL 或 Redis. 反射达标, 但对于此模式来说 @ , SQLite 是理想的@ MS K2}

1. **在后面写- 消除瓶颈** - SQLite's s單一}-priterrationd doesn{'t matters 批量时不重要
2. **当地储存** -无网络长期性,没有连接池
3. **零配置** - 只是一个文件路径
4. **适合边缘部署** -在草莓皮上奔跑
5. **便携式** - 数据库只是您可以复制的文件

计划是最小的 :

```sql
CREATE TABLE IF NOT EXISTS learned_weights (
    signature_type TEXT NOT NULL,
    signature TEXT NOT NULL,
    weight REAL NOT NULL,
    confidence REAL NOT NULL,
    observation_count INTEGER NOT NULL DEFAULT 1,
    first_seen TEXT NOT NULL,
    last_seen TEXT NOT NULL,
    PRIMARY KEY (signature_type, signature)
);

CREATE INDEX IF NOT EXISTS idx_signature_type ON learned_weights(signature_type);
CREATE INDEX IF NOT EXISTS idx_confidence ON learned_weights(confidence);
CREATE INDEX IF NOT EXISTS idx_last_seen ON learned_weights(last_seen);
```

如果您需要更大的比例@ , @ 换成 PostgreSQL.} 如果您需要 HA 或复制@ MPK2} 替换到 Redis 或者分布的缓存 @ MS K3} 架构将“ '} t change” @- 只要连接字符串\ . SQLite 是边缘部署的默认值 @ IMK7} 而不是宗教=.}

## DISE Tie-在 @ - 失败作为进化

如果 [DiSE 日志](/blog/dise-architecture-overview) 是完整的进化引擎,}这个缓存模式是 MItochondriaMS K1}最小的一块仍然在限制下 像进化一样的小块_.}

这种模式在最起码的级别上执行DISE原则 @:

- **资源制约** @→ 选择压力
- **LRU驱逐** {\fn华文楷体\fs16\1cHE0E0E0}生还者是适龄者
- **时间衰变** → 忘记使适应成为可能
- **EMA 更新EMA** → 通过观察变异
- **歇歇症** 通过抵抗变革实现稳定

一个完整的缓存是 @ '@ title: listbox **进化压力**“. 系统自我”“-Opptiminises:热签名” “MS K3冷签将“,”逐出,行为记忆与实际重要内容趋同。”

没有 ML 训练@.没有外部模型 @. 只是像生活系统一样的建筑_.

## “-”简单结构“, 新兴行为

整个图案归结为“:”

1. **Cache是真理之源,** @ -_ sub MS K1_ milli second access
2. **立即写入更新缓存@ , @ @ label** 无屏蔽 I/O
3. **Sliding 过期失效自动提供 LRU** @-# 建在@._NET
4. **宽幅大小造成选择压力** @- 溢出突出焦点
5. **背景水流使 SQLite 同步** 最终的一致是好的
6. **时间衰变可以忘记** {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}失踪的证据

你的小行为商店的行为 更像一个生活系统 比CROUD.}它记得什么重要 它忘记了什么

最小架构@ → @ 突发的正确性 @ MS K1 @
上移@→ @ 更好地聚焦 @ . @
压力: →稳定性 @.

如果您想在动作中看到此操作@ , @ check out [多数是卢希德.}(机器人检测)](/blog/botdetection-introduction) *-和 [DISE 建筑系列](/blog/dise-architecture-overview) 更深层次的哲学

## 链接链接链接

- [Bot探测器检测介绍](/blog/botdetection-introduction) -使用这些模式的系统
- [DiSE 架构概览](/blog/dise-architecture-overview) -通过压力控制进化
- [现代CQRS和事件观察](/blog/moderncqrsandeventsourcing) 当您需要时, - @ 整个图案@ MS K1 @ I) @
- [GitHub: mostlucidMS K1bot 检测器](https://github.com/scottgal/mostlylucid.nugetpackages) -}完整的源代码
- [NuGet 软件包](https://www.nuget.org/packages/mostlylucid.botdetection/) - 安装它