English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

gäst
1 / ?

Fyra-stegs-mönstret

Defekten finns i fyra sekventiella, icke-atomära steg:

// DEFECT
Value value = cache.get(key);
if (value == null) {
value = expensiveCompute(key);
cache.put(key, value);
}
return value;

Steg 1: kontrollera cache. Steg 2: miss. Steg 3: beräkna. Steg 4: lagra. Alla fyra steg: icke-atomära. Mellan steg 1 och steg 4 kan valfritt antal trådar utföra steg 1 och alla ser null.

The Idempotency Trap

The reasoning that protects this pattern: 'it is OK if two threads compute & store the same value. The result is idempotent. No data corruption occurs.'

This reasoning: correct about correctness. Fatal about cost.

At 1,000 threads on a cache miss: 1,000 threads each execute expensiveCompute(key). If expensiveCompute queries a database, 1,000 database queries fire simultaneously. If it calls an external service, 1,000 HTTP requests fire simultaneously. The system producing correct results collapses under the cost of producing them.

Three Triggers

A Thundering Herd fires when a cache key transitions from warm to cold simultaneously across many threads:

Cold start: service restarts with an empty cache. First request wave: every key misses. All compute simultaneously.

Service restart: rolling restart resets cache across instances. Traffic redistributes to cold instances.

TTL expiry: a high-traffic key expires. N threads all check, all miss, all compute before the first thread stores the result.

Alla tre triggers: korrelerade med trafiktoppar. Herden aktiveras när trafiken når sin topp och cachen är kall. Värsta tänkbara tillfälle.

Elasticsearch Exempel med EnrichCache

Elasticsearch EnrichCache: dokumenterad kommentar lyder 'intentionally non-locking for simplicity...OK if we re-put the same key/value in a race condition.' Vid 10 000 dokument per sekund med en kall enrich-cache: alla 10 000 förfrågningar träffar enrich-indexet samtidigt. Enrich-indexet, som är designat för sporadiska uppslag, utsätts för 10 000 samtidiga frågor. Klustret blir instabilt.

Idempotensresonemanget: korrekt i kodkommentaren. Katastrofalt vid 10 000 dokument per sekund.

Koppling till MOAD-0001

MOAD-0001 (Sedimentary Defect) skapar en O(N²)-flaskhals i system med hög genomströmning. Att åtgärda MOAD-0001 (O(N²) till O(N)) frigör arbetsstationen. Den högre genomströmningen skickar fler förfrågningar vidare. Nedströms-cacher, som tidigare skyddats av MOAD-0001-flaskhalsen, får nu korrelerade trafiktoppar. MOAD-0005 aktiveras i cacher som aldrig tidigare drabbats. Åtgärda ett MOAD; skapa förutsättningar för det andra.

Vad Idempotensfällan får fel

Elasticsearch-kommentaren representerar noggrant ingenjörsresonemang tillämpat på fel fråga. Idempotens: en verklig egenskap värd att resonera kring. Fällan: att stanna analysen vid korrekthet utan att fortsätta till kostnad.

Varför leder resonemanget "det är OK om två trådar skriver samma resultat" till defekten? Vad får det rätt, och vad missar det?

computeIfAbsent & singleflight

Lösningen: gör check-and-compute atomär. En tråd beräknar. Alla andra trådar väntar på det resultatet.

Java: computeIfAbsent

// DEFECT: fyra icke-atomära steg
Value value = cache.get(key);
if (value == null) {
value = expensiveCompute(key);
cache.put(key, value);
}
return value;

// FIX: atomisk kontroll-och-beräkning
return cache.computeIfAbsent(key, k -> expensiveCompute(k));

computeIfAbsent: om nyckeln saknas, beräknas exakt en gång, lagras och returneras. Alla andra trådar som anropar computeIfAbsent för samma nyckel väntar på den första beräkningen. Ingen N-faldig beräkning. Ingen stampede.

Go: singleflight.Group

var g singleflight.Group

func getOrCompute(key string) (Value, error) {
v, err, _ := g.Do(key, func() (interface{}, error) {
return expensiveCompute(key)
})
return v.(Value), err
}

singleflight: om en beräkning för en nyckel redan körs, väntar alla anropare för samma nyckel och delar det enda resultatet. En beräkning, N väntare, ett resultat delas. 'flight'-abstraktionen: deduplicera pågående förfrågningar.

Lås vs singleflight

Ett naivt per-nyckel-lås serialiserar: tråd 1 beräknar, tråd 2 väntar, tråd 3 väntar. När tråd 1 är klar går tråd 2 in och kontrollerar cachen (träff). Tråd 3 går in och kontrollerar cachen (träff). N-1 låsförvärv och cacheläsningar.

singleflight deduplicerar: tråd 1 beräknar, tråd 2 till N väntar alla på tråd 1:s resultat. Inga separata låsförvärv. Inga separata cacheläsningar. En beräkning, ett resultat, distribuerat till N väntare. Färre operationer än ett per-nyckel-lås.

Båda förhindrar stampede. singleflight förhindrar redundant arbete mer fullständigt.

Skriv om mönstret

Tillämpa fixen på ett konkret scenario.

// En användarprofil-cache i en Java-tjänst med hög trafik
public UserProfile getProfile(String userId) {
UserProfile profile = profileCache.get(userId);
if (profile == null) {
profile = database.loadProfile(userId);  // dyrt: 50 ms DB-fråga
profileCache.put(userId, profile);
}
return profile;
}

Tjänsten startar om varje morgon kl. 02:00. Klockan 08:00 begär 10 000 användare sina profiler samtidigt.

Identifiera defekten, ange när den utlöses och skriv om med computeIfAbsent.