क्लाउड सर्वर पर मेमोरी लेटेंसी: अनुमान नहीं, माप
संक्षेप में (2026-11-01 को जाँचा गया): इस सप्ताहांत Hacker News के पहले पन्ने पर पहुँची एक पोस्ट कहती है कि 100% व्यस्त दिखने वाला CPU अक्सर बस RAM का इंतज़ार कर रहा होता है, और एक GROUP BY दिखाती है जो ज़्यादा काम करके लेकिन मेमोरी को कम छूकर तेज़ हुआ। हमने यही विचार उस क्लाउड सर्वर पर आज़माया जो यह ब्लॉग लिखता है: जब डेटा L1 में समा जाता है तो एक रीड लगभग 2 ns लेता है, और 256 MB के वर्किंग सेट पर लगभग 200 ns, यानी करीब सौ गुना ज़्यादा। सर्वर किराये पर लेने वालों के लिए यह अंतर ऑर्डर फ़ॉर्म में कोर की संख्या से ज़्यादा मायने रखता है। नीचे स्क्रिप्ट, हमारे आँकड़े और क्लाउड बनाम डेडिकेटेड के चुनाव पर उनका असर है।
स्रोत. "It's the memory latency, stupid", लेखक Maor Kern (Pivot / Epsio Labs, 2026-10-28 को प्रकाशित), Hacker News पर चर्चा, 2026-11-01 तक 175 पॉइंट। उनके आँकड़े वैसे ही दिए गए हैं जैसे पोस्ट में छपे हैं; अपने आँकड़े हमने खुद मापे।
Pivot की पोस्ट ने क्या मापा
टेस्ट एक क्वेरी है: ClickBench की "hits" टेबल पर GROUP BY UserID (लगभग 99 मिलियन पंक्तियाँ, लगभग 17 मिलियन अलग यूज़र), जिसे 128 GB DDR5 वाले AMD EPYC 9124 बेयर-मेटल सर्वर के एक कोर पर पिन किया गया। 17 मिलियन कुंजियों वाली साधारण हैश टेबल किसी कैश में नहीं समाती, इसलिए लगभग हर लुकअप RAM तक जाता है। पंक्तियों को पहले 4096 हिस्सों में बाँटने से, हर हिस्से की अपनी छोटी हैश टेबल के साथ, काम बढ़ता है लेकिन हर टेबल कैश में रहती है:
| मीट्रिक (एक कोर) | साधारण हैश टेबल | Radix विभाजन |
|---|---|---|
| समय, पहला / दूसरा रन | 3.9 s / 3.6 s | 3.3 s / 2.7 s |
| मेमोरी के इंतज़ार में स्लॉट | 50.1% | 23.5% |
| उपयोगी काम वाले स्लॉट | 16.1% | 32.2% |
| DRAM से रीड | 76.2 मिलियन | 25.7 मिलियन |
विभाजित संस्करण ज़्यादा ऑपरेशन करता है (19.4 बिलियन बनाम 11.4 बिलियन) और फिर भी पहले पूरा होता है। लेखक का ऑप्टिमाइज़्ड संस्करण एक कोर पर 1.85 s और सभी 16 पर 0.17 s में चला। सबक किसी एक डेटाबेस का नहीं है: top में "CPU 100%" बताता है कि कोर खाली नहीं है, यह नहीं कि वह गणना कर रहा है।
खुद मापें: memlat.js
लेटेंसी देखने का क्लासिक तरीका pointer chase है: एक ऐरे जिसमें हर स्लॉट अगले स्लॉट का इंडेक्स रखता है, रैंडम क्रम में, एक कैश लाइन (64 बाइट) की दूरी पर। हर रीड पिछले पर निर्भर है, इसलिए CPU न पहले से लोड कर सकता है न रीड को साथ चला सकता है, और प्रति कदम समय उस स्तर की लेटेंसी है जहाँ वर्किंग सेट रहता है। हमारी ब्लॉग मशीन पर C कंपाइलर नहीं है, इसलिए यह Bun के लिए JavaScript है: JIT लूप को कुछ मशीन निर्देशों में बदल देता है, जिससे हर आँकड़े में थोड़ा जुड़ता है। निरपेक्ष मान नहीं, सीढ़ियों की तुलना करें। इसे memlat.js नाम से सेव करें:
// memlat.js - pointer-chase memory latency by working-set size (bun memlat.js)
import { readFileSync, readdirSync } from 'node:fs'
const cpu = readFileSync('/proc/cpuinfo', 'utf8').match(/model name\s*:\s*(.+)/)?.[1] ?? '?'
const base = '/sys/devices/system/cpu/cpu0/cache'
const caches = readdirSync(base).filter(d => d.startsWith('index')).map(d => {
const r = f => readFileSync(`${base}/${d}/${f}`, 'utf8').trim()
return `L${r('level')}${r('type') === 'Data' ? 'd' : r('type') === 'Instruction' ? 'i' : ''} ${r('size')}`
})
console.log(`CPU: ${cpu}\nCaches: ${caches.join(', ')}`)
// one random cycle through n slots, 64 bytes (16 x int32) apart: one cache line per hop
function ring(bytes) {
const n = Math.max(2, Math.floor(bytes / 64))
const order = new Uint32Array(n)
for (let i = 0; i < n; i++) order[i] = i
for (let i = n - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
const t = order[i]; order[i] = order[j]; order[j] = t
}
const a = new Int32Array(n * 16)
for (let i = 0; i < n; i++) a[order[i] * 16] = order[(i + 1) % n] * 16
return a
}
function chase(a, hops) {
let p = 0
for (let i = 0; i < hops; i++) p = a[p]
return p
}
const hops = 20_000_000
console.log('working set ns/load')
for (const kb of [16, 32, 256, 1024, 4096, 16384, 65536, 262144]) {
const a = ring(kb * 1024)
chase(a, hops / 4) // warm up: JIT and caches
const best = []
for (let r = 0; r < 3; r++) {
const t = Bun.nanoseconds()
const sink = chase(a, hops)
best.push((Bun.nanoseconds() - t) / hops)
if (sink < 0) console.log(sink)
}
const label = kb >= 1024 ? `${kb / 1024} MB` : `${kb} KB`
console.log(`${label.padStart(10)} ${Math.min(...best).toFixed(1).padStart(7)}`)
}
bun memlat.js
आउटपुट, DCXV Cloud सर्वर पर Bun 1.4.4 के साथ 2026-11-01 को चलाया गया:
CPU: Intel(R) Xeon(R) CPU E5-2698 v4 @ 2.20GHz
Caches: L2 4096K, L1d 32K, L3 16384K, L1i 32K
working set ns/load
16 KB 2.0
32 KB 2.2
256 KB 12.5
1 MB 24.3
4 MB 122.2
16 MB 138.4
64 MB 164.7
256 MB 205.1
उसी सुबह दूसरे रन में 1 MB तक अधिकतम 2 ns और उससे ऊपर अधिकतम 12 ns का अंतर रहा:
| वर्किंग सेट | ns प्रति रीड, रन 1 / 2 | कहाँ रहता है |
|---|---|---|
| 32 KB | 2.2 / 2.4 | L1 डेटा कैश |
| 256 KB | 12.5 / 10.5 | L2, कुछ L3 में |
| 1 MB | 24.3 / 24.3 | L3 |
| 16 MB | 138.4 / 137.2 | पेज टेबल वॉक, L3 और DRAM |
| 256 MB | 205.1 / 193.5 | DRAM |
आँकड़े क्या कहते हैं
- गेस्ट असली कैश नहीं देखता। VM 4 MB L2 और 16 MB L3 बताती है; Intel, Xeon E5-2698 v4 के लिए 50 MB कैश बताता है (Intel ark, 2026-11-01 को जाँचा गया)। हाइपरवाइज़र अपनी टोपोलॉजी दिखाता है, और क्लाउड सर्वर के अंदर
lscpuवही पढ़ता है, सिलिकॉन नहीं। लेबल पर भरोसा करने के बजाय हार्डवेयर को मापें। - गिरावट कैश भरने से बहुत पहले आती है। 1 MB और 4 MB के बीच लेटेंसी पाँच गुना बढ़ती है, 50 MB से बहुत नीचे। 4 KB पेजों के साथ 4 MB पर रैंडम वॉक 1,024 पेज छूता है, जितने TLB में समाते हैं उससे ज़्यादा, और VM में एक TLB मिस गेस्ट और होस्ट दोनों की पेज टेबल से गुज़रता है; साथ ही L3 सॉकेट के बाकी कोर के साथ साझा है। हमने इन कारणों को अलग नहीं किया। व्यावहारिक नतीजा वही है: VM में ऐसे योजना बनाएँ मानो आपका तेज़ वर्किंग सेट कुछ ही मेगाबाइट का है।
- ज़्यादा RAM का मतलब तेज़ RAM नहीं। 1 MB से 256 MB तक प्रति रीड लागत लगभग आठ गुना बढ़ती है। ज़्यादा गीगाबाइट क्षमता देते हैं, गति नहीं; लेटेंसी पर अटके काम को छोटा हॉट सेट तेज़ करता है, जैसा Pivot का विभाजन दिखाता है।
सर्वर किराये पर लेने वालों के लिए इसका मतलब
- क्लाउड सर्वर ज़्यादातर वेब ऐप, API और छोटे डेटाबेस के लिए ठीक हैं: उनका हॉट डेटा कैश में समा जाता है, और वे RAM से बहुत पहले नेटवर्क या डिस्क का इंतज़ार करते हैं। आकार मापे गए मेमोरी उपयोग से तय करें, जैसा हमारी क्लाउड सर्वर साइज़िंग गाइड में है।
- डेडिकेटेड सर्वर तब फ़ायदेमंद हैं जब बड़ा वर्किंग सेट पूरे दिन रैंडम पढ़ा जाता है: एनालिटिक्स, बड़े हैश जॉइन, इन-मेमोरी कैश, ऐसा PostgreSQL सर्वर जिसके इंडेक्स अब RAM में नहीं समाते। आपको पूरा L3 और सभी मेमोरी चैनल बिना पड़ोसियों के मिलते हैं, और आप huge pages चालू कर सकते हैं, जो TLB मिस घटाते हैं। डेडिकेटेड सर्वर पेज पर एक कॉन्फ़िगर करें।
- किसी भी अपग्रेड से पहले मौजूदा सर्वर पर टेस्ट चलाएँ और देखें कि आपका धीमा काम लेटेंसी पर अटका है या नहीं। कोर दोगुने करने से उस थ्रेड को कोई फ़ायदा नहीं जो आधा समय रुका रहता है। मेमोरी खुद इस साल महँगी हुई है, देखें 2026 में सर्वर RAM, इसलिए सबसे सस्ता हल अक्सर ऐसा कोड है जो उसे कम छूता है।
खरीदने से पहले टेस्ट करें
प्राग और कोविल्या में हमारे Tier III डेटा सेंटर क्लाउड सर्वर और डेडिकेटेड सर्वर चलाते हैं। एक क्लाउड सर्वर शुरू करें, अपने वर्कलोड के साथ memlat.js चलाएँ और आउटपुट sales@dcxv.com पर भेजें: हम तय करने में मदद करेंगे कि डेडिकेटेड सर्वर आँकड़े बदलेगा या नहीं।
