LRUs lernen, wenn die Kapazität übertrifft, macht Ihr System besser (Deutsch (German))

LRUs lernen, wenn die Kapazität übertrifft, macht Ihr System besser

Tuesday, 09 December 2025

//

18 minute read

Die meisten Systeme degradieren, wenn überlastet ,. , Speicher füllt up , , , Abfragen verlangsamen , , , Benutzer beschweren sich ,, , Server abstürzen .. ,

Ein paar ungewöhnliche bekommen besser.

Dieser Artikel zeigt, wie ein LRU---basierter Verhaltensspeicher wird selbst-optimierend, wenn es die Kapazität erreicht, und wie dieses Muster das Lernsystem in meinem Bot-DetektionsmotorDies ist auch die kleinste mögliche Version von DiSE-Architektur Kontrolle der Entwicklung durch Ressourcendruck

Wenn Sie lesen meinen Artikel auf CQRS und Event SourcingAber dies ist CQRS auf den Knochen gestreift , um das Ereignis speichern ,, , keine Projektionen , , , kein Marten .. , Nur ein Speicher-Cache , , , ein Hintergrund-Worker , und SQLite ,. ,

Die Grundidee des Verhaltensgedächtnisses auf einem Budget

Bevor wir tauchen, lassen Sie mich einen Begriff definieren. UnterschriftEine Signatur ist ein stabiler Schlüssel, der ein Verhalten Muster repräsentiert ein Hash von IP-+.Benutzer--.Agent.,. ein Fingerabdruck von Header-Kombinationen.,. ein Detektor.MS K6. eine Klassifizierung von .MSC7. Diese Anfrage sieht aus wie X-MSC8. Der Cache speichert diese Signaturen zusammen mit gelernten Gewichten, die sich im Laufe der Zeit entwickeln.MSSK9.

Was, wenn du ein System bauen könntest, das

  • Responses in unter 1-ms
  • Blockiert niemals auf Datenbank schreibt
  • Selbstbeherrschung unter Druck
  • Vergessen, was nicht zählt
  • Erinnert sich an das, was tut

Das ist genau das, was IMemoryCache mit gleitendem Ablauf gibt Ihnen -, wenn Sie verstehen, was Sie Gebäude .

Was ein LRU-Cache eigentlich nicht tut

LRU -(-least Kürzlich verwendet ,)-Caches Aussparungseinträge, die havenay , '-t vor kurzem zugegriffen wurde , wenn der Cache füllt up , die kältesten Einträge werden rausgeworfen, um Platz für heiße diejenigen machen ..-

Die meisten Entwickler sehen dies als Einschränkung. Mein Cache ist voll, die Daten werden verloren.

Aber für Verhaltens-Systeme ist dies ein Feature.

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

Das SizeLimit ist gerade ein Speichereinschränkung ,. , es , ' , AuswahldruckEs bestimmt, wie viel sich das System erinnert und zwingt es, sich auf das zu konzentrieren, was zählt.

Sliding Expiration Die Uhr, die vergisst

Kombiniert mit gleitendem Ablauf erhalten Sie automatisches Vergessen

// 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
}

Wenn eine Signatur innerhalb von 1 Minuten zugegriffen wird, ist , Ausgestellte ' Nicht weil es ' falsch ist, weil es nicht mehr relevant .

Dies schafft Natürliches Vergessen:

  • IPs werden neu zugewiesen
  • Bots rotieren Signaturen
  • Verkehrsverlagerung
  • Gestern-'-Angriffe sind heute-MSC2

Statische Blocklisten gehen abgestanden.-Sliding Ablauf hält den Speicher frisch

Das IMemoryCache-Muster, ohne CQRS zu sagen

Hier ist das Muster, das es Arbeit macht. SqliteWeightStore Klasse - :-

/// <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);

Das ist Informelle CQRS -, aber ohne die mühsame Cache-Ungültigkeit... ., anstatt Cacheeinträge nach writes_,, zu entkräften. der Cache IST das SchreibmodellSQLite ist nur das langlebige Buch.

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

Wichtigste Einsichten (MSK) liest und schreibt ins Gedächtnis gehenDie Datenbank ist schließlich konsistent und dass die ' fein ist.

Der Lesen-Pfad

Wenn ein Detektor ein gelerntes Gewicht benötigt, trifft er den Cache.

// 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
}

Heiße Pfade nie auf die Datenbank getroffenDer Cache ist die Quelle der Wahrheit für Reads... SQLite ist nur Backup-Speicher.

Der Schreib-Pfad Benachrichtigen Sie sofort Bewährtes Später

Wenn das System etwas Neues lernt, aktualisiert es den Cache sofort und wartet die Datenbank write-:

// 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;
}

Hinweis: UpdateWeightAsync Renditen Task.CompletedTask ' ist im Wesentlichen synchron, . ist das Schreiben in der Warteschlange nicht ausgeführt . Dies bedeutet: :

  • Sub.-.Millisekunden-Schreiblatenz
  • Keine Blockierung auf I/-O
  • Schriften werden zusammengeschmolzen.

Der Hintergrund Flusher ----500-ms der langweiligen Magie

Jedes ausstehende Schreiben wird in einem Batch zu SQLite gespült.

// 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();
    }
}

Das ist Ereignis---Sourcing---LichtSie erhalten:

  • Batched schreibt, effizientes I/.
  • Kohärenz der Transaktionen
  • Coalesced updates Wenn die gleiche Signatur aktualisiert wird, ist nur der Endwert geschrieben.
  • SQLite ist mit diesem Zugriffsmuster vollkommen zufrieden

EMA-Updates Lernen mit exponentiellen Moving Averages

Wenn eine neue Beobachtung eintrifft, verwendet das System exponentielle Moving Averages, um Gewichte zu aktualisieren.

// 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;
}

Die EMA-Formel glättet das Lernen new_weight = old_weight × (1 - α) + new_value × α

mit einer Dicke von mehr als 2 mm

  • Neue Beweise tragen dazu bei
  • Historische Beweise tragen dazu bei
  • Dies verhindert wilde Schaukeln aus Einzelbeobachtungen

Warum Überfluss das System macht Besser.

Hier ist die Schlüssel-Insight, dass die meisten Menschen vermissen

Wenn der Cache nach oben füllt

  • Niedrige Frequenzsignaturen fallen aus
  • Nur heiße, häufig zugegriffene Signaturen bleiben im Speicher
  • Die Datenbank verzögert sich um einen Flush-Zyklus und das ist fein.
  • Das System Schwerpunkte unter Druck

Denken Sie darüber nach: :, wenn einzigartige Signaturen Ihren Bot-Detektor treffen, aber Sie haben nur Speicher für 10,000,, welche Unterschriften wichtig sind.

Die heißesten ,- , die typischerweise darstellen , 99% , des tatsächlichen Verkehrs , . ,

In der Produktion, ,, sehe ich ungefähr ein "- Off"-Signaturen pro Tag, die einmal versuchen, MSC4, zufällige Sonden, ,, legitime Benutzer, die nie zurückkehren, und vielleicht auch 5–10,000, die ständig wiederholen, MCSK8. Jene MSC9 sind, wo 99%, des Risikos lebt.., Die langen -,tail Signaturen, ?, Noise, MKS14, sie zu beseitigen, tut nicht weh, Detektionsgenauigkeit, sondern könnte es sogar verbessern, indem sie falsche Positive von niedrigen -, Vertrauensmustern, MSV18, reduzieren.

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

Überfluss schärft das Verhaltensgedächtnis, das System selbst, das - optimiert,. .

Wenn Überströmung nicht hilft

Es gibt Randfälle, in denen LRU-Druck gegen dich wirkt.

  • Cache zu kleinWenn dein Cache nur Einträge enthält, aber du hast wirklich wichtige Signaturen.

  • Einheitlicher VerkehrWenn du auf einem winzigen internen System laufst, in dem fast alles läuft, dann gibt dir fast alles weniger Vorteile Der Auswahldruck hat nichts zu wählen gegen -.

  • Cold---StartproblemEin frisch eingesetztes System hat keine erlernten Gewichte....Alles ist gleich kalt....... die ersten Stunden haben höhere falsch-positive Raten, bis sich die heißen Signaturen etablieren....

Das Muster funktioniert am besten, wenn Sie hohe Kardinalität mit Power-Verteilung Viele einzigartige Signaturen, aber eine kleine Untergruppe, die den Verkehr dominiert, das ist genau das, was der Bot-Detektionsverkehr aussieht.

Cache und Datenbank in der Synchronisation behalten

Der Cache ist die Quelle der Wahrheit für reads.,, aber die Datenbank ist das dauerhafte Ledger, was passiert, wenn sie driften.

Synchronisierung verzögern

Wenn Datenbankgewichte zerfallen, muss der Cache folgen. DecayOldWeightsAsync Verfahren handhabt beides

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

Nach zerfallenden Datenbankaufzeichnungen rufen wir an _cache.Compact(0.25) Diese Kraft MemoryCache , um die am wenigsten vor kurzem benutzten ,." Der nächste Lesevorgang wird neue Werte aus der Datenbank neu laden .. .

Tag: -, basierende Räumung

Manchmal müssen Sie eine ganze Kategorie von zwischengespeicherten Einträgen für ungültig erklären, z.B. bei Umschulung eines Detektors oder wenn sich externe Daten ändern.

// 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-'-s MemoryCache doesns-'-t haben natives Tag---basierte Räumung wie Redis-,- aber Verdichtung erreicht den gleichen Effekt ,: force out stale Einträge , , , lassen Sie liest aus der Datenbank wiederbevölkern , . ,

Die Synchronisierungsstrategie

Die wichtigste Erkenntnis ist, dass perfekte Synchronisation ist notwendigDas System toleriert Drift, weil

  1. Cache verpasst Neuladen von DB Wenn ein Eintrag gelöscht wird, holt der nächste Lesevorgang frische Daten ab
  2. Sliding Ablauf behandelt Stillstand Einträge, die nicht innerhalb von 1 Minuten automatisch abgerufen werden
  3. Verdichtungskräfte Erneuerung Periodische Verdichtung schiebt alte Einträge
  4. Write--hhind coalesces Aktualisierungen Mehrmalige schnelle Updates werden zu einem DB-Schreiben

Dies ist letztendlich Konsistenz getan right..-Der Cache bleibt genug schließen "- zur Datenbank, ohne komplexe Ungültigkeitslogik erforderlich.

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

Kein Cache-Ungültigkeit höll... Kein komplexes pub./.sub. .. Nur Verdichtung und natürliche Verfallsdauer.

Das Reputationssystem wird immer tiefer

Anmerkung: Wenn Sie gerade wollten, dass die LRU + schreib's -hinter Muster ,h können Sie hier stoppen..h Der Rest dieses Artikels zeigt, wie ich die gleichen Ideen auf volle Muster Reputation anwenden -h Zustand Maschinen.MS K5h Hysterese und Zeitzerfall .h Es ist die "h extra Meile für jene Gebäude adaptive Systeme..h

Hysterese und Dekay

Für Muster-Reputation, ob eine Signatur Bot oder Mensch im Laufe der Zeit ist, gelten die gleichen Prinzipien, aber mit zusätzlicher Raffinesse.

// 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;
}

Staatliche Übergänge mit Hysterese

Muster, die direkt von Neutral zu Bestätigt kippen

// 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 ...
}

Beachten Sie die Asymmetrie.:. Es ist einfacher, blockiert zu werden als ungeblockt. ConfirmedBad → Suspect erfordert die Unterstützung von ,, während Neutral → Suspect Dies ist absichtliches Vergeben von -.' ist schwerer zu vergeben als zu vermuten.

Zeit-Dekay-Beginn des --Begangenen Vergessen

Wenn Muster leise gehen, zerfallen sie in Richtung neutral.

// 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
    };
}

Die Standard-Zeitkonstanten

  • Wertezerfall τDie Werte bewegen sich nach einer Woche Inaktivität in Richtung Neutral.
  • Unterstützung des Zerfalls τDas Vertrauen sinkt nach zwei Wochen.

Dies bedeutet, dass ein bestätigter - bad IP, der für einen Monat ruhig geht schließlich zurück auf Neutral fallen wird, nicht weil es sich reformiert hat, weil seine Beweise veraltet wurden.

Der Hintergrund-Wartungsdienst

Das ReputationMaintenanceService führt drei periodische Aufgaben aus

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

Der Müllsammler entfernt Muster, die

  • Tage alt
  • Unterstützung
  • Im neutralen Zustand

Dies verhindert, dass das In---Memory-Wörterbuch ungebunden wächst und dabei wertvolle erlernte Muster bewahrt.

SQLite ist nicht ein Witz, der hier perfekt ist

Viele Entwickler greifen reflexiv für PostgreSQL oder Redis-., aber für dieses Muster ist ,. SQLite ideal.

  1. Schreibt-hinter eliminiert Engpässe SQLite-S's single--®writer limitation doesns-SQLite-Beschränkung ist wichtig, wenn schreibt werden gechargt
  2. Örtliche Lagerung Keine Netzwerk-Latenz, keine Verbindungspools
  3. Null-Konfiguration Nur ein Dateipfad
  4. Perfekt für den Edge-Einsatz -- lauft auf einem Himbeerpi
  5. Tragbar Die Datenbank ist nur eine Datei, die Sie kopieren können.

Das Schema ist minimal.

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

Wenn Sie größere Skalierung benötigen, swap to PostgreSQL-..Wenn Sie HA oder Replikation brauchen, tauschen Sie mit Redis oder einem verteilten Cache. Die Architektur ändert nichts an ', nur die Verbindung string... SQLite ist der Standard für Edge Deployment.

Das DiSE Tie-- Im Fehler als Evolution

Wenn DiSE ist die volle Evolutions-Engine, dieses Cache-Muster ist das mitochondria, das kleinste Stück, das sich immer noch wie Evolution unter Einschränkungen verhält.

Dieses Muster implementiert die DiSE-Prinzipien auf dem minimalsten Niveau

  • Ressourcenbeschränkung Auswahldruck
  • LRU Räumung Natürliche Auswahl Überlebende sind die fittest
  • Zeitverfall Vergessen ermöglicht Anpassung
  • EMA-Updates Mutation durch Beobachtung
  • Hysterese Stabilität durch Widerstand gegen Veränderung

Ein voller Cache ist ein Fehler. evolutionären DruckDas System selbst optimiert die heißen Signaturen bleiben die kalten Signaturen und das Verhaltensgedächtnis konvergiert zu dem, was wirklich zählt

Kein ML-Training, keine externen Modelle, nur Architektur, die sich wie ein lebendes System verhält.

Fazit Einfache Strukturen – Emergent Behaviour

Das gesamte Muster kocht nach unten

  1. Cache ist die Quelle der Wahrheit für Lesungen Zugriff auf Sub--, Millisekunden
  2. Schreibt Aktualisierungscache sofort Aushalten später Keine Sperrung
  3. Sliding Ablauf bietet automatische LRU Eingebaut in .NET
  4. Gebundene Größe erzeugt Selektionsdruck Überlauf schärft Fokus
  5. Hintergrund bündig hält SQLite synchron Eventuelle Konsistenz ist in Ordnung
  6. Zeitverfall ermöglicht Vergessen --Schalbeweise verschwinden

Dein kleiner Verhaltensspeicher wirkt mehr wie ein lebendes System als CRUD-..Er erinnert sich an das, was wichtig ist.,. vergißt, was nicht funktioniert und wird besser unter Druck.

Minimale Architektur _ → emergent correctness_. Überströmung besser fokussieren Druckstabilität (MSK)

Wenn Sie dies in Aktion sehen möchten Meistlucid-.-Botdetektion und der DiSE-Architekturreihe für die tiefere Philosophie

Verknüpfungen

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.