The Four-Step Pattern
दोष संरचना चार क्रमिक, गैर-आणविक चरणों में है:
// DEFECT
Value value = cache.get(key);
if (value == null) {
value = expensiveCompute(key);
cache.put(key, value);
}
return value;
चरण 1: चेक कैश। चरण 2: मिस। चरण 3: compute। चरण 4: स्टोर। सभी चार चरण: गैर-आणविक। चरण 1 और चरण 4 के बीच किसी भी संख्या में थ्रेड्स को चरण 1 का पालन कर सकते हैं और सभी को नुल देखते हैं।
The Idempotency Trap
इस पैटर्न की सुरक्षा के लिए तर्क: 'दो थ्रेड्स के समान मान को कम्प्यूट और स्टोर कर सकते हैं। परिणाम idempotent होता है। कोई डेटा खराबी नहीं होती है।'
यह तर्क: सटीकता के बारे में सही है, लेकिन लागत के बारे में घातक है।
1,000 थ्रेड्स पर 1,000 थ्रेड्स के साथ एक कैश मिस होता है: कैश की क्षमता के लिए 1,000 थ्रेड्स प्रत्येक expensiveCompute(key) को निष्पादित करते हैं। अगर expensiveCompute डेटाबेस से पूछता है, तो 1,000 सिमुलतान डेटाबेस क्वेरी फायर होती हैं। अगर यह एक बाह्य सेवा को कॉल करता है, तो 1,000 सिमुलतान HTTP अनुरोध फायर होते हैं। सही परिणाम प्राप्त करने वाला सिस्टम कीमत पर काम करने के लिए काम कर रहा है।
Three Triggers
एक Thundering Herd तब फायर होता है जब कैश की कुंजी एक साथ कई थ्रेड्स के लिए गरम से ठंडा हो जाती है:
Cold start: सेवा रीस्टार्ट होती है और खाली कैश के साथ। पहला अनुरोध वेव: हर कुंजी मिस करती है। सभी सिमुलतान रूप से compute करते हैं।
Service restart: घूमता रीस्टार्ट कैश को सभी उदाहरणों में रीसेट करता है। ट्रैफ़िक को कोल्ड इंस्टेंस पर वितरित करता है।
TTL expiry: एक हाई-ट्रैफ़िक की कुंजी समाप्त होती है। N थ्रेड सभी चेक करते हैं, सभी मिस करते हैं, सभी परिणाम के पहले पहले थ्रेड स्टोर करते हुए compute करते हैं।
सभी तीन ट्रिगर: ट्रैफ़िक स्पाइक के साथ सहसंबद्ध। गैंडे की दहाड़ तब फायर होती है जब ट्रैफ़िक की ऊंचाई होती है और कैश ठंडा होता है। सबसे खराब समय।
The Elasticsearch EnrichCache Example
Elasticsearch EnrichCache: documented comment पढ़ता है 'सादगी के लिए'intentionally non-locking...OK if we re-put the same key/value in a race condition।' 10,000 दस्तावेज़ प्रति सेकंड के साथ एक ठंडा enrich कैश पर: सभी 10,000 अनुरोधों ने enrich इंडेक्स को हिट किया। enrich इंडेक्स, occasional लुकअप के लिए डिज़ाइन किया गया, 10,000 समकालीन क्वेरी का सामना करता है। क्लस्टर अस्थिर हो जाता है।
The idempotency reasoning: कोड कमेंट में सही है। 10,000 दस्तावेज़ प्रति सेकंड पर घातक है।
Coupling to MOAD-0001
MOAD-0001 (सेडिमेंट्री दोष) उच्च-आवृत्ति प्रणालियों में ओ (एन²) बोतलनेक का निर्माण करता है। MOAD-0001 (ओ (एन²) को ओ (एन) में सुधार) उस कामस्थल को अवरुद्ध करता है। तेज़ प्रवाह अधिक अनुरोध नीचे की ओर भेजता है। नीचे के कैश, पहले MOAD-0001 बोतलनेक द्वारा सुरक्षित, अब संबंधित ट्रैफिक स्पाइक्स प्राप्त करते हैं। MOAD-0005 उन कैश में जल्दी करता है जो पहले कभी इसका सामना नहीं किया। MOAD-0001 को सुधारें; दूसरे को अन्य चरण में रखें।
विनाशकारी निरपेक्षता का क्या गलत हो रहा है
ईलास्टिक सर्च कमेंट सावधान इंजीनियरिंग तर्क का प्रतिनिधित्व करता है जो गलत प्रश्न पर लागू होता है। निरपेक्षता: एक वास्तविक गुण जिसे तर्क करने योग्य है। फंसा: विशेषता का विश्लेषण रोकना बिना लागत पर जारी रखें।
computeIfAbsent & singleflight
सुधार: जाँच-और-गणना अणु बनाएं। एक धागा गणना करता है। अन्य सभी धागे उस परिणाम के लिए प्रतीक्षा करते हैं।
जावा: computeIfAbsent
// DEFECT: चार अणु चरण
Value value = cache.get(key);
if (value == null) {
value = expensiveCompute(key);
cache.put(key, value);
}
return value;
// FIX: अणु जाँच-और-गणना
return cache.computeIfAbsent(key, k -> expensiveCompute(k));
computeIfAbsent: यदि कुंजी अनुपस्थित है, तो एक बार संगत रूप से गणना करता है, संग्रहीत करता है, वापस लेता है। अन्य सभी धागे computeIfAbsent को उसी कुंजी के लिए कॉल करते हैं, पहली गणना के लिए प्रतीक्षा करते हैं। एन-फोल्ड गणना नहीं। स्टैम्पेड नहीं।
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: यदि किसी कुंजी के लिए कंप्यूटेशन पहले से चल रहा है, तो उसी कुंजी के लिए सभी कॉलर वेट और एक ही परिणाम साझा करते हैं। एक कंप्यूट, एन वेटिंग, एक ही परिणाम। 'फ्लाइट' अभिव्यक्ति: वायु यात्रा में दोहराव को समाप्त करता है।
लॉक vs singleflight
एक अनुभवी प्रति-कुंजी लॉक सीरियलाइज़ करता है: धागा 1 कंप्यूट करता है, धागा 2 प्रतीक्षा करता है, धागा 3 प्रतीक्षा करता है। धागा 1 समाप्त होने के बाद, धागा 2 प्रवेश करता है और कैश (हिट) चेक करता है। धागा 3 प्रवेश करता है और कैश (हिट) चेक करता है। एन-१ लॉक अधिग्रहण और कैश पढ़ें।
singleflight दोहराव समाप्त करता है: धागा 1 कंप्यूट करता है, धागे 2 से एन सभी धागे 1 के परिणाम की प्रतीक्षा करते हैं। अलग-अलग लॉक अधिग्रहण नहीं। अलग-अलग कैश पढ़ नहीं। एक कंप्यूट, एक परिणाम, एन वेटिंग को वितरित करता है। लॉक के मुकाबले कम संचालन।
दोनों स्टैम्पेड को रोकते हैं। singleflight अधिक पूरी तरह से दोहराव को रोकता है।
पैटर्न को फिर से लिखें
एक विशिष्ट स्केनरियो पर फिक्स को लागू करें।
// एक हाई-ट्रैफिक जावा सेविस में यूजर प्रोफाइल कैश
public UserProfile getProfile(String userId) {
UserProfile profile = profileCache.get(userId);
if (profile == null) {
profile = database.loadProfile(userId); // महंगा: 50ms डीबी क्वेरी
profileCache.put(userId, profile);
}
return profile;
}
सेविस प्रत्येक सुबह 2 बजे रिबूट होती है। सुबह 8 बजे, 10,000 यूजर्स अपने प्रोफाइल की मांग करते हैं।