LRU:iden oppiminen - Kun suurempi kapasiteetti tekee järjestelmästäsi paremman (Suomi (Finnish))

LRU:iden oppiminen - Kun suurempi kapasiteetti tekee järjestelmästäsi paremman

Tuesday, 09 December 2025

//

17 minute read

Useimmat järjestelmät heikkenevät ylikuormittuessa, muisti täyttyy, kyselyt hidastuvat, käyttäjät valittavat, palvelimet kaatuvat.

Muutama epätavallinen saa parempi.

Tämä artikkeli osoittaa, kuinka LRU-pohjainen käyttäytymismuisti tulee itsekeskeiseksi, kun se osuu kapasiteettiin - ja miten tämä kuvio voimistaa oppimisjärjestelmää minun BOT-ilmaisinmoottoriTämä on myös pienin mahdollinen versio DiSE-arkkitehtuuri - kontrolloinut evoluutiota resurssipaineen avulla.

Jos olet lukenut artikkelini aiheesta CQRS ja tapahtumahankintaTunnistat osan kuvioista täällä, mutta tämä on CQRS, joka on irrotettu luuhun - ei tapahtumakauppaa, ei ennusteita, ei Martenia, vain muistilokero, taustatyöntekijä ja SQLite.

Perusidea - Käyttäytymismuisti budjetista

Ennen kuin sukellamme, määrittelen yhden termin, jota käytän kautta: allekirjoitus. Allekirjoitus on mikä tahansa vakaa avain, joka edustaa käyttäytymismallia - IP + User-Agentin hash-numeroa, otsikkoyhdistelmien sormenjälkiä, ilmaisimen luokitus "Tämä pyyntö näyttää X:ltä". Välimuisti tallentaa nämä allekirjoitukset sekä ajan myötä kehittyvät opitut painot.

Entä jos voisit rakentaa järjestelmän, joka:

  • Vastauksia alle 1 ms:ssä
  • Älä koskaan estä tietokantakirjoituksia
  • Itseoptimismi paineen alla
  • Unohtaa sen, mikä ei ole tärkeää
  • Muistaa mitä tekee

Juuri niin. IMemoryCache liukuvan vanhenemisen myötä saat - jos ymmärrät, mitä olet rakentamassa.

Mitä LRU Cache todellisuudessa tekee

LRU:n (viimeisin käytetty) välimuistit häätävät tietueita, joihin ei ole päästy käsiksi viime aikoina. Kun välimuisti täyttyy, kylmimmät kirjaukset heitetään pois, jotta kuumille voisi tehdä tilaa.

Useimmat kehittäjät pitävät tätä rajoituksena. "Voi ei, kätköni on täynnä, dataa menetetään!"

Mutta käyttäytymisjärjestelmille tämä on ominaisuus.

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

Tuo SizeLimit Se ei ole pelkkä muistirajoitus, vaan valintapaineetSe määrittää, kuinka paljon järjestelmä "muistaa" ja pakottaa sen keskittymään tärkeisiin asioihin.

Liukas vanheneminen - kello joka unohtaa

Yhdessä liukuvan vanhenemisen kanssa saat automaattisen unohtamisen:

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

Jos allekirjoitukseen ei pääse käsiksi puolessa tunnissa, se häädetään, ei siksi, että se olisi väärin - koska sillä ei ole enää merkitystä.

Tämä luo luonnollinen unohtaminen:

  • IP:t siirretään muualle
  • Botit kiertävät nimikirjoituksia
  • Liikennetottumusten muutos
  • Eiliset hyökkäykset eivät ole tämän päivän

Staattinen blokkilista on tunkkainen ja liukuva vanheneminen pitää muistin tuoreena.

ImemoryCache -malli - Pieni CQRS sanomatta CQRS

Tässä kuvio, joka saa sen toimimaan. SqliteWeightStore luokka:

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

Tämä on Epävirallinen CQRS - mutta ilman tylsää välimuistin mitätöintiä. Sen sijaan, että se mitätöisi välimuistin kirjaukset kirjoitusten jälkeen, välimuisti IS:n kirjoitusmalliSQLite on vain kestävä tilikirja.

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

Avainymmärrys: lukevat ja kirjoittavat menevät muistiinTietokanta on lopulta johdonmukainen - ja se on hyvä.

Lue polku - Välipala ensin, aina

Kun ilmaisin tarvitsee opittua painoa, se osuu välimuistiin:

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

Kuumat polut koskaan ei osunut tietokantaanVälimuisti on totuuden lähde luettavaksi. SQLite on vain varmuuskopiointivarasto.

Kirjoituspolku - Välipala heti, jatka myöhemmin

Kun järjestelmä oppii jotain uutta, se päivittää välimuistin välittömästi ja jonottaa tietokantaan kirjoittamista:

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

Huomautus: UpdateWeightAsync palautukset Task.CompletedTask Kirjoitus on jonotettu, ei toteutettu.

  • Alimillisekunnin kirjoitusviivästys
  • I/O:ssa ei ole estoja
  • Kirjoitukset ovat hiiltyneitä (viimeiset kirjoitusvoitot)

Tausta flusher - 500 ms ikävystyttävää magiaa

SQLitelle huuhtotaan 500 millimetrin välein yhdessä erässä:

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

Tämä on Tapahtumien hankinta-valo. Saat:

  • Batched kirjoittaa (tehokas I/O)
  • Transaktioiden johdonmukaisuus
  • Hiilipitoiset päivitykset (jos sama allekirjoitus päivittyy 10 kertaa 500 ms:ssa, vain lopullinen arvo on kirjoitettu)
  • SQLite on täysin tyytyväinen tähän kulkukuvioon

EMA-päivitykset - Oppiminen Exponential Moving Averages

Kun uusi havainto saapuu, järjestelmä päivittää painoja eksponentiaalisilla liikkuvilla keskiarvoilla:

// 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:n kaava helpottaa oppimista: new_weight = old_weight × (1 - α) + new_value × α

jossa α = 0,1:

  • Uusia todisteita on 10 prosenttia
  • Historiallisten todisteiden osuus on 90 prosenttia
  • Tämä estää hurjat heilahtelut yksittäisistä havainnoista

Miksi ylivirtaus tekee järjestelmästä järjestelmän Parempaa

Tässä on avainymmärrys, jota useimmat kaipaavat.

Kun välimuisti täyttyy:

  • Matalataajuiset allekirjoitukset jäävät pois
  • Vain kuumat (usein käytetyt) allekirjoitukset pysyvät muistissa
  • Tietokanta laahaa yhden huuhtelusyklin verran - ja se on hyvä
  • Järjestelmä keskittää paineen alla

Ajattele asiaa: jos 50 000 ainutlaatuista allekirjoitusta osuu bottitunnistimeesi, mutta muistisi on vain 10 000, millä allekirjoituksilla on merkitystä?

Kuumin 10 000 - jotka edustavat tyypillisesti 99 prosenttia todellisesta liikenteestä.

Tuotannossa näen noin 40 000 yksittäistä allekirjoitusta päivässä (haastajat yrittävät kerran, satunnaisia luotaimia, laillisia käyttäjiä, jotka eivät koskaan palaa) ja ehkä 5–10 000, jotka toistuvat jatkuvasti. Nuo 5–10 000 ovat niitä, joissa 99 prosenttia riskihenkilöistä elää. Pitkän aikavälin allekirjoitukset? Melu. Niiden poistaminen ei vahingoita havaintotarkkuutta - se voi jopa parantaa sitä vähentämällä vääriä positiivisia puolia alhaisen itseluottamuksen jäljiltä.

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

Ylivirtaus teroitusaineet Käyttäytymismuisti, järjestelmä itseoptimoi.

Kun ylivirtaus ei auta

LRU:n paine toimii sinua vastaan.

  • Välipala liian pieniJos välimuistissasi on vain 100 merkintää, mutta sinulla on 1 000 aidosti tärkeää allekirjoitusta, kirjoittelet jatkuvasti ja menetät hyödylliset kuviot ennen kuin ne keräävät tarpeeksi todisteita. Kokoa välimuistisi mukavasti pitämään "kuumaa settiäsi".

  • Yhdenmukainen liikenne: Jos käytössä on pieni sisäinen järjestelmä, jossa lähes kaikki on "kuumaa" (muutamia ainutlaatuisia nimikirjoituksia, kaikki toistuvat), ylikuormituksesta on vähemmän hyötyä. Valintapaineella ei ole mitään valittavaa.

  • Kylmäkäynnistysongelma: Vasta käyttöön otetussa järjestelmässä ei ole opittuja painoja. Kaikki on yhtä kylmää. Ensimmäisillä tunneilla väärät positiiviset prosentit ovat korkeammat, kunnes kuumat merkit alkavat toimia.

Malli toimii parhaiten, kun sinulla on suuri kardinaali valtalain jakelulla Paljon ainutlaatuisia nimikirjoituksia, mutta pieni joukko, joka hallitsee liikennettä. Juuri siltä bottihavaitsemisliikenne näyttää.

Pitämällä välimuistin ja tietokannan synkronoituna - Tag-Based Invalidation

Kätkö on totuuden lähde luettavaksi, mutta tietokanta on kestävä tilikirja. Mitä tapahtuu, kun ne ajelehtivat?

Decay-synkronointi

Kun tietokannan painot rapistuvat, välimuistia on seurattava. DecayOldWeightsAsync menetelmä käsittelee molempia:

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

Hajoavien tietokantatietojen jälkeen soitamme _cache.Compact(0.25) - tämä pakottaa MemoryCache Häädetään 25 % tiedoista ja asetetaan etusijalle vähiten käytetyt. Seuraava luku lataa tuoreet arvot tietokannasta.

Tagipohjainen Eviction

Joskus täytyy mitätöidä kokonainen sarja välimuistiin tallennettuja tietueita, esimerkiksi anturin uudelleenkouluttamisessa tai ulkoisten tietojen muuttuessa:

// 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 Redisin tapaista kotimaista leimallista häätöä ei ole, mutta tiivistymisellä saavutetaan sama vaikutus: pakotetaan tunkkaiset kirjaukset pois, annetaan lukujen asettua uudelleen tietokantaan.

Synkronointistrategia

Avainnäkemys on, että täydellinen synkronointi ei ole tarpeenJärjestelmä sietää ajelehtimista, koska:

  1. Välimuistin missaukset ladataan uudelleen DB:stä - Jos merkintä häädetään, seuraava luku tuo uusia tietoja
  2. Liu'uvan kulumisen kahvat ovat tunkkaiset - sisäänpääsyä ei ole tehty 30 minuutin kuluessa automaattisesta evictistä
  3. Kompensaation voimat uudistuvat - jaksottainen tiivistys työntää vanhat merkinnät pois
  4. Brit-blues-päivitysten takana - useista nopeista päivityksistä tulee yksi DB-kirjoitus

Tämä on lopulta oikein tehty johdonmukaisuus. Välimuisti pysyy "riittävän lähellä" tietokantaa vaatimatta monimutkaista invaliditeettilogiikkaa.

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

Ei välimuistin mitätöintihelvettiä, ei monimutkaista pubia/subpia, vain tiivistystä ja luonnollista vanhenemista.

Syvempi: Mainejärjestelmä

Huomaa: Jos haluat vain LRU:n + kirjain-taustakuvion, voit lopettaa tähän. Loput artikkelista osoittavat, kuinka sovellan samoja ideoita täyteen mallimaineeseen - valtion koneisiin, hystereesiin ja aikahajoamiseen. Se on "ylimääräinen maili" niille, jotka rakentavat adaptiivisia järjestelmiä.

Hystereesi ja rappeuma

Mallimaiseen maineeseen (jäljitetään, onko allekirjoitus ajan myötä botti vai inhimillinen) sovelletaan samoja periaatteita, mutta lisäksi hienostuneesti:

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

Valtion siirtymävaiheet hysteerisesti

Mallit eivät käänny suoraan Neutraalista VahvistettuunBadiin. Hystereesi estää räpyttelyn:

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

Huomaa epäsymmetria: estäminen on helpompaa kuin estäminen. ConfirmedBad → Suspect vaatii sata tukea, kun taas Neutral → Suspect Tämä on tahallista - sitä on vaikeampi antaa anteeksi kuin epäillä.

Aikahukkaa - eksponentiaalista unohtamista

Kun kuviot hiljenevät, ne rapistuvat kohti neutraalia:

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

Oletusaikavakiot:

  • **Pisteiden pieneneminen **: 168 tuntia (7 päivää) - pisteet etenevät 63 prosenttia kohti neutraalia viikon poissaolon jälkeen
  • Tue rapistumista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .: 336 tuntia (14 päivää) - itseluottamus laskee 63 prosenttia kahden viikon jälkeen

Tämä tarkoittaa, että kuukauden ajan hiljaiseksi mennyt huono IP palaa lopulta takaisin Neutraaliin, ei siksi, että se olisi uudistunut - koska sen todisteet muuttuivat tunkkaisiksi.

Taustahuoltopalvelu

Erytropoietiini ReputationMaintenanceService hoitaa kolme jaksollista tehtävää:

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

Roskankeräilijä poistaa kaavoja, jotka ovat:

  • Yli 90 päivän ikäiset
  • Tuki ≤ 1,0
  • Puolueettomassa tilassa

Tämä estää muistinsisäistä sanakirjaa kasvamasta rajattomaksi säilyttäen samalla arvokkaat opitut mallit.

SQLite ei ole vitsi - Se on täydellinen täällä

Monet kehittäjät tavoittavat refleksisesti PostgreSQL:n tai Redisin. SQLite on kuitenkin ihanteellinen malli:

  1. Kirjallisuuden takaa poistetaan pullonkaulat - SQLiten yksikirjaimisella rajoituksella ei ole merkitystä, kun kirjoituksista tehdään eriä
  2. Paikallinen säilytys - ei verkon latenssia, ei yhteysaltaita
  3. Nolla-asetukset - vain tiedostopolku
  4. Täydellinen reunan käyttöön - juoksee vadelmapiillä
  5. Kannettava - tietokanta on vain tiedosto, jota voit kopioida ympärillesi

Skema on minimaalinen:

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

Jos tarvitset suuremman mittakaavan, vaihda PostgreSQL:ään. Jos tarvitset HA:ta tai kopiointia, vaihda Redisiin tai jaettuun välimuistiin. Arkkitehtuuri ei muutu - vain yhteysjono. SQLite on oletus reunan käyttöönotolle, ei uskonto.

DiSE:n sidonta - epäonnistuminen kehityksenä

Jos DiSE on täysi evoluution moottori, tämä välimuistikuvio on mitokondrio - pienin kappale, joka yhä käyttäytyy evoluution tavoin rajoitteen alla.

Tämä malli toteuttaa DiSE:n periaatteita mitä minimaalisimmalla tasolla:

  • Resurssirajoitukset → Valintapaine
  • LRU:n häätö → Luonnonvalinta (selviytyjät ovat vahvimpia)
  • Aikahajoaminen → Unohtaminen mahdollistaa sopeutumisen
  • EMA-päivitykset → Mutaatio havainnon kautta
  • Hystereesi → Vakaus muutoskestävyyden kautta

Täysi välimuisti ei ole epäonnistuja. evoluutiopaineetJärjestelmän itseoptimismi: kuumat allekirjoitukset jäävät, kylmät allekirjoitukset häätävät, ja käyttäytymismuisti lähentelee sitä, mikä on oikeasti tärkeää.

Ei ML-koulutusta, ei ulkoisia malleja, vain arkkitehtuuria, joka käyttäytyy kuin elävä järjestelmä.

Johtopäätös - yksinkertaiset rakenteet, kehittymätön käytös

Koko kuvio kiteytyy seuraavasti:

  1. Cache on totuuden lähde luettavaksi - alimillisekunnin pääsy
  2. Kirjoittaa päivitä välimuistin välittömästi, keskeytä myöhemmin - ei estä I/O:ta
  3. Sliding Emission tarjoaa automaattisen LRU:n - sisäänrakennettu .netiin
  4. Rajattu koko luo valintapainetta - ylivuoto terävöittää keskittymistä
  5. Taustaväri pitää SQLiten ajan tasalla - mahdollinen yhtenäisyys on hyvä
  6. Aikahajoaminen mahdollistaa unohtamisen - tunkkaiset todisteet katoavat

Pieni käyttäytymisliikkeesi muistuttaa enemmän elävää järjestelmää kuin CRUDia. Se muistaa, mikä on tärkeää, unohtaa sen, mikä ei ole, ja paranee paineen alla.

Minimaalinen arkkitehtuuri → emergentti oikeellisuus. Ylivirtaus → Keskittykää paremmin. Painosta → vakautta.

Jos haluat nähdä tämän toiminnassa, katso Enimmäkseen lucid.bottihavaitseminen - ja DiSE-arkkitehtuurisarja Syvemmälle filosofialle.

Linkkejä

Finding related posts...
logo

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