⏱️ धडा 01 — Latency आणि percentiles: अंतिम रेषेवरची स्टॉपवॉच
📍 तुम्ही इथे आहात: 12 पैकी धडा 01 · पुढे: lesson-02-benchmarking
📦 या ब्रँचमध्ये काय आहे
Performance च्या कामाचा पहिला प्रश्न: हे खरोखर किती वेगवान आहे? आज क्रीडा दिन आहे. प्रत्येक request म्हणजे एक धावपटू, आणि कतरिना हातात स्टॉपवॉच घेऊन अंतिम रेषेवर उभी आहे. एका धावपटूला लागणारा वेळ म्हणजे latency. दर second किती धावपटू पूर्ण करतात ते म्हणजे throughput. आणि बहुतेक लोक जो एक आकडा सांगतात — सरासरी — तो सर्वात महत्त्वाच्या धावपटूंना लपवतो: हळू धावणाऱ्यांना. संपूर्ण कोर्समध्ये तुम्ही वापराल त्या खऱ्या files:
- perf/sim.py — क्रीडा दिन छोट्या deterministic models च्या रूपात, शुद्ध Python मध्ये (360 ओळी, शून्य dependencies)
- perf/demo.py — प्रत्येक धडा एक छोटा program (
python3 perf/demo.py <section>; sections:latency bench profile complexity memory caching io concurrency queueing database web budgets) - perf/test_perf.py — क्रीडा दिनाच्या नियमांवर 12 checks
- demo-output.txt — निरोगी run काय छापतो ते
🎒 सुरू करण्याआधी: तुम्हाला फक्त Python 3 लागेल, बाकी काही नाही — cloud account नाही,
pip installनाही.sim.pyमध्ये शिकवण्यासाठीचे models आहेत. त्यातली मोजमापाची साधने खरी आहेत (cProfile,tracemalloc, SQLite चेEXPLAIN QUERY PLAN), पण खर्च आणि वेळा simulated आणि seeded आहेत, म्हणून प्रत्येक run तेच आकडे छापतो. जेव्हा एखादा धडा खरी घड्याळाची वेळ दाखवतो, तेव्हा तो "तुमचे आकडे वेगळे असतील" असे सांगतो. खऱ्या account वर असे चिन्हांकित commands ना खऱ्या systems लागतात. ही शाळा एक program वेगवान बनवते; machines वाढवणे (CDNs, autoscaling, replicas) हे Scaling school चे काम आहे.
🧒 5 वर्षांच्या मुलाला समजावल्यासारखे
आज क्रीडा दिन आहे. 🏃♀️ एक हजार धावपटू प्रत्येकी एक फेरी धावतात. कतरिना अंतिम रेषेवर स्टॉपवॉच ⏱️ घेऊन उभी आहे आणि प्रत्येक वेळ लिहून ठेवते.
बहुतेक धावपटू सुमारे 40 मध्ये पूर्ण करतात (मैदानावर seconds; computer मध्ये milliseconds). काही जण अडखळतात आणि खूप जास्त वेळ घेतात. एक-दोघांचा बूट निसटतो आणि ते खूपच वेळ घेतात.
मुख्याध्यापिका विचारतात: "ते किती वेगाने धावले?" कतरिना सगळ्या वेळा बेरीज करून 1000 ने भागू शकते. ती म्हणजे सरासरी: 51. पण कोणीच 51 मध्ये धावले नाही! बहुतेक सुमारे 40 मध्ये धावले, आणि काहींना 1000 पेक्षा जास्त लागले. सरासरी त्यांना मिसळून असा आकडा बनवते जो कोणाचेच वर्णन करत नाही.
म्हणून कतरिना वेळा सर्वात जलद ते सर्वात हळू अशा रांगेत लावते आणि रांगेतल्या काही ठिकाणी वाचते:
- मधोमध असलेला धावपटू (p50): 40
- दर 100 पैकी 95 व्या जागेवरचा धावपटू (p95): 59
- दर 100 पैकी 99 व्या जागेवरचा धावपटू (p99): 260 — हळू tail ची सुरुवात
आणि एक वेगळा प्रश्न: दर second किती धावपटू रेषा ओलांडतात? तो म्हणजे throughput. एकाऐवजी चार lanes उघडा, आणि दर second चौपट धावपटू पूर्ण करतात — पण प्रत्येक धावपटूची फेरी जराही जलद होत नाही.
🗺️ आकृती
flowchart LR
runs["🏃♀️ 1000 requests<br/>timed at the finish"] --> sort["📋 sort the times<br/>fastest → slowest"]
sort --> p50["p50 = 40 ms<br/>the middle runner"]
sort --> p95["p95 = 59 ms"]
sort --> p99["p99 = 260 ms<br/>the tail starts"]
sort --> max["max = 1364 ms"]
runs --> mean["mean = 51.3 ms<br/>describes nobody"]
fan["🌐 a page waits for 100 calls"] --> tail["63.4% of page loads<br/>meet a p99-slow call"]
🗺️ काढलेली आकृती + एक lab: https://school-edh.pages.dev/performance/lesson-diagrams.html#l01
❓ काय
- Latency — एका request ला सुरुवातीपासून शेवटपर्यंत लागणारा वेळ (धावपटूची फेरीची वेळ). User ला जिथे जाणवतो तिथे मोजा: संपूर्ण request, queues मध्ये थांबण्याच्या वेळेसह.
- Throughput — दर second किती requests पूर्ण होतात (दर second रेषा ओलांडणारे धावपटू). जास्त lanes (workers, cores, machines) throughput वाढवतात; त्या एक फेरी जलद करत नाहीत. Throughput × latency सांगते की एकाच वेळी किती काम चालू आहे (Little's law, धडा 09).
- Percentile (pNN) — सगळी मोजमापे sort करा; p95 म्हणजे अशी value जिच्याइतकी किंवा जिच्यापेक्षा कमी 95% मोजमापे आहेत. p50 म्हणजे median. Lab nearest-rank पद्धत वापरते (⌈p/100 × n⌉ या स्थानावरची value घ्या). इतर tools दोन values मध्ये interpolate करतात, म्हणून लहान samples वर ते थोडे वेगळे आकडे देतात.
- Tail — हळू टोक: p99, p99.9, max. इथे 1000 पैकी 37 requests ना 100 ms किंवा जास्त लागले, आणि 7 ना 800 ms किंवा जास्त. खऱ्या systems मधली कारणे: garbage-collection pauses, थंड cache, lock, हरवलेल्या packet नंतरचा retry, त्याच machine वरचा busy शेजारी.
- Mean का नाही? Mean काही मोठ्या values मुळे वर ओढला जातो आणि अनेक लहान values मुळे खाली; तो अशा ठिकाणी बसू शकतो जिथे एकही request नाही. बेरजेसाठी (CPU seconds, खर्च) तो अजूनही उपयोगी आहे, फक्त "हे किती वेगवान वाटते" यासाठी नाही.
- Tail at scale — k स्वतंत्र calls ची वाट पाहणारे page, त्यापैकी कोणताही call हळू असला तरी हळू होते. त्यापैकी किमान एक call त्याच्या p99 पेक्षा हळू असण्याची शक्यता 1 − 0.99ᵏ: एका call साठी 1%, 10 साठी 9.6%, 100 साठी 63.4%. Jeffrey Dean आणि Luiz André Barroso यांच्या The Tail at Scale (2013) या paper चा हाच मुद्दा आहे: मोठ्या प्रमाणावर, भागांचा p99 संपूर्णाचा नेहमीचा अनुभव बनतो.
हा धडा मुद्दाम लहान आहे. Production मध्ये percentiles कसे गोळा करायचे — histograms, metrics, traces आणि SLOs — ते Observability school मध्ये आहे.
🤔 का
कारण पुढच्या प्रत्येक धड्याला "हे जलद झाले का?" याचे प्रामाणिक उत्तर हवे. तुम्ही mean सांगितलात, तर tail ला मदत करणारा उपाय काहीच नसल्यासारखा दिसू शकतो, आणि शंभरात एका user ला वाईट रीतीने त्रास देणारा बदल विजयासारखा दिसू शकतो. Users ना हळू page लक्षात राहते, आणि busy user अनेक requests करतो, म्हणून जवळजवळ प्रत्येक user कधी ना कधी तुमच्या p99 ला भेटतोच.
🔧 कसे (या repo मध्ये)
perf/sim.py मधले lap_times(n, seed) 1000 seeded latencies बनवते: 95%
20 ते 60 ms दरम्यान, 4% 100 ते 300 ms दरम्यान, आणि सुमारे 1% 800 ते 1500 ms दरम्यान.
percentile(xs, p) हा nearest-rank percentile आहे. summary(xs) mean, p50, p95,
p99 आणि max देते. fan_out_tail(k) म्हणजे 1 − 0.99ᵏ. perf/demo.py मधले latency()
हे सगळे छापते.
🧪 करून पाहा
python3 perf/demo.py latency
python3 - <<'EOF'
import sys; sys.path.insert(0, "perf"); from sim import lap_times, percentile, summary
xs = lap_times()
for p in (50, 90, 95, 99, 99.9):
print(f"p{p:<4} {percentile(xs, p):>5} ms")
print("5 slowest:", sorted(xs)[-5:])
quick = [x for x in xs if x < 100]
print(f"without the slow {1000 - len(quick)}:", summary(quick))
EOF
python3 perf/test_perf.py
✅ तपासा — तुम्हाला काय दिसायला हवे
latency हे छापते:
── 1000 requests timed by the finish-line stopwatches (ms per request = latency)
mean 51.3 · p50 40 · p95 59 · p99 260 · max 1364
37 of 1000 took 100 ms or more · 7 took 800 ms or more — the tail
the mean (51.3) describes nobody: most finish near 40, a few take far longer
── throughput = finishers per second: one lane at 40 ms a lap → 25/s · four lanes → 100/s · each lap is still 40 ms
a page that waits for 1 call(s): 1.0% of page loads meet at least one call slower than its p99
a page that waits for 10 call(s): 9.6% of page loads meet at least one call slower than its p99
a page that waits for 100 call(s): 63.4% of page loads meet at least one call slower than its p99
तुमचा snippet हे छापतो:
p50 40 ms
p90 57 ms
p95 59 ms
p99 260 ms
p99.9 1364 ms
5 slowest: [991, 1021, 1029, 1177, 1364]
without the slow 37: {'mean': 39.8, 'p50': 39, 'p95': 58, 'p99': 59, 'max': 60}
Tests शेवटी 12/12 passed छापतात.
🏁 तुम्ही आत्ताच काय सिद्ध केले
37 हळू धावपटू काढून टाका आणि mean 51.3 वरून 39.8 वर येतो — त्या 37 जणांनी mean 11 ms पेक्षा जास्त हलवला, तर median जेमतेम हलला (40 → 39). p99 नेमका जिथे हळू धावपटू सुरू होतात तिथे 59 वरून 260 वर उडी मारतो. म्हणजे mean दोन अगदी वेगळ्या गटांना मिसळतो; percentiles दोन्ही दाखवतात. आणि 100 calls च्या fan-out मध्ये, "100 पैकी 1" हळू call 63.4% page loads मध्ये दिसतो.
⚠️ नेहमीच्या चुका
- फक्त mean latency सांगणे
- percentiles ची सरासरी काढणे — दहा servers च्या p99s चा mean हा सगळ्या requests चा p99 नसतो (त्याऐवजी raw data किंवा histograms merge करा)
- 50 samples मधून काढलेला p99 — तो जवळपास फक्त सर्वात हळू value असतो; tail साठी तुम्हाला अनेक samples लागतात
- फक्त server च्या आत मोजणे, आणि त्याआधी requests queue मध्ये थांबतात तो वेळ चुकवणे
- throughput आणि latency मध्ये गल्लत करणे: जास्त workers throughput वाढवू शकतात आणि latency तशीच ठेवू शकतात
🏭 प्रत्यक्ष वापरात
खऱ्या account वर — curl ने एका request च्या टप्प्यांची वेळ घ्या:
curl -o /dev/null -s -w 'dns %{time_namelookup}s · connect %{time_connect}s · tls %{time_appconnect}s · first byte %{time_starttransfer}s · total %{time_total}s\n' https://results.school.example/
Prometheus histogram सगळ्या instances वरचे percentiles देतो — आधी buckets merge करा, मग quantile काढा:
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
Python मध्ये, standard library एका sample चे percentiles मध्ये तुकडे करू शकते (ते interpolate करते, म्हणून लहान samples वर nearest-rank पेक्षा थोडा फरक येतो):
import statistics
cuts = statistics.quantiles(latencies_ms, n=100) # 99 cut points
p50, p95, p99 = cuts[49], cuts[94], cuts[98]
🏭 Production मध्ये हे का महत्त्वाचे आहे: तुमची उद्दिष्टे (SLOs) percentiles वर ठरवा — उदाहरणार्थ "p99 300 ms च्या आत" — आणि p50, p95 आणि p99 शेजारी शेजारी chart करा. जेव्हा ते एकमेकांपासून दूर जातात, तेव्हा tail मागे शोधण्यासारखे कारण असते.
⏭️ पुढे
आता "जलद" म्हणजे काय हे तुम्ही सांगू शकता. पुढे: एखाद्या बदलाची प्रामाणिकपणे वेळ घेणे — warm-up फेऱ्या, अनेक heats, आणि एका आकड्याऐवजी पसारा (spread).
git checkout lesson-02-benchmarking