⚖️ धडा 05 — Stateless APIs + load balancers: कोणतीही खिडकी कोणत्याही पालकाला सेवा देऊ शकते
📍 तुम्ही इथे आहात: 13 पैकी धडा 05 · मागे: lesson-04-dynamic-at-the-edge · पुढे: lesson-06-auto-scaling
📦 या ब्रँचमध्ये काय आहे
धडे 01–04, आणि त्याशिवाय scale out शक्य करणारा नियम: stateless API.
Sessions, uploads आणि caches server च्या बाहेर राहतात, म्हणून load balancer
प्रत्येक request कोणत्याही चालू (healthy) server कडे पाठवू शकतो. scale/demo.py मधले balance()
कतरिनाच्या सहा clicks चा तीन servers वरून मागोवा घेते — एकदा तिचे session एका
server च्या memory मध्ये असताना, एकदा shared store मध्ये असताना.
🧒 5 वर्षांच्या मुलाला समजावल्यासारखे
कतरिना जत्रेत येते आणि खिडकी 1 वर sign in करते. खिडकी 1 वरची कारकून तिच्या स्वतःच्या वहीत 📓 "कतरिना — signed in" असे लिहिते.
फाटकावरचा मदतनीस प्रत्येक पुढची भेट पुढच्या खिडकीकडे पाठवतो: 1, 2, 3, 1, 2, 3. कतरिनाची दुसरी भेट खिडकी 2 कडे जाते. ती कारकून आपल्या वहीत पाहते: कतरिना नाही. "कृपया पुन्हा sign in करा." कतरिना वैतागते. तिच्या सहा पैकी चार भेटी अशाच संपतात.
दीपिका हे एका सामायिक वहीने दुरुस्त करते, जी प्रत्येक खिडकीला पोहोचता येईल अशा टेबलावर असते (Redis). आता कोणतीही कारकून "कतरिना — signed in" तपासू शकते. प्रत्येक खिडकी प्रत्येक पालकाला सेवा देऊ शकते. आता दीपिका 3 खिडक्या उघडू शकते किंवा 30.
फाटकावरचा मदतनीस दर काही सेकंदांनी प्रत्येक खिडकीजवळून जातो आणि विचारतो "सगळे ठीक आहे ना?". कारकुनाने उत्तर दिले नाही तर मदतनीस तिथे पालक पाठवणे थांबवतो — हा health check.
🗺️ आकृती
flowchart LR
k["🙋 Katrina<br/>6 clicks"] --> alb["⚖️ load balancer<br/>round robin · health checks"]
alb --> s1["🖥️ srv-1"]
alb --> s2["🖥️ srv-2"]
alb --> s3["🖥️ srv-3"]
s1 --> r["📓 shared session store<br/>ElastiCache (Redis/Valkey) · DynamoDB"]
s2 --> r
s3 --> r
m["❌ session in srv-1 memory<br/>clicks 2, 3, 5, 6 → login page"]
🗺️ काढलेली आकृती + एक lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l05
❓ काय
- Stateless server — requests च्या दरम्यान user बद्दल काहीच ठेवत नाही. त्याला हवे असलेले सगळे request सोबत (token) किंवा shared store मधून येते. कोणतीही प्रत कोणत्याही request ला उत्तर देऊ शकते, आणि कधीही प्रत जोडता किंवा काढता येते.
- बाहेर हलवायलाच हवी अशी state — sessions (ElastiCache for Redis किंवा Valkey, किंवा DynamoDB कडे), uploaded files (S3 कडे), caches (shared cache कडे, धडा 09), scheduled jobs (एका scheduler कडे, "प्रत्येक server चालवतो" असे नाही).
- Signed tokens (JWT) — stateless होण्याचा दुसरा मार्ग: user ची ओळख एका signed token मध्ये असते, जी कोणताही server lookup न करता तपासू शकतो. Tokens अल्पायुषी ठेवा.
- Load balancer — requests अनेक servers वर पसरवतो. AWS वर Application Load Balancer (ALB) HTTP सोबत काम करतो. तो requests एका target group कडे पाठवतो.
- Routing algorithm — round robin (क्रमाने पुढचा server, ALB चा default) किंवा least outstanding requests (सर्वात कमी requests चालू असलेला server — काही requests हळू असतात तेव्हा हे चांगले).
- Health check — ALB दर काही सेकंदांनी प्रत्येक target वर
/healthसारखा path call करतो. ठरावीक संख्येने अपयश आल्यावर target unhealthy होतो आणि त्याला नव्या requests मिळत नाहीत; ठरावीक संख्येने यश मिळाल्यावर तो पुन्हा healthy होतो. - Sticky sessions (stickiness) — ALB cookie वापरून user ला एकाच server ला बांधू शकतो. ही सोय अस्तित्वात आहे, आणि जुन्या apps ना मदत करते. पण ती load असमान पसरवते, आणि तो server गेला तर user चे session तरीही जाते. Stateless servers ला प्राधान्य द्या.
- Deregistration delay (connection draining) — server काढला जातो तेव्हा ALB नव्या requests पाठवणे थांबवतो, पण चालू असलेल्या requests पूर्ण होऊ देतो.
🤔 का
कारण servers जोडण्याचा पुढचा प्रत्येक मार्ग — Auto Scaling (धडा 06), Kubernetes (धडा 07), Lambda (धडा 08) — प्रती सतत जोडतो आणि काढतो. एखाद्या प्रतीकडे इतर कुठेच नसलेली गोष्ट असेल, तर ती प्रत काढल्यावर ती गोष्ट हरवते: login, upload, अर्धवट बनलेला report. आधी stateless; मग scale out म्हणजे फक्त एक आकडा.
🔧 कसे (या repo मध्ये)
balance() कतरिनाचे clicks round robin ने srv-1, srv-2, srv-3 कडे पाठवते. तिचे session
फक्त memory["srv-1"] मध्ये असते, आणि shared dictionary मध्ये (Redis च्या जागी).
प्रत्येक click साठी प्रत्येक जागा काय सांगते ते ते print करते. हा section साधा Python आहे — ते दाखवायला
sim.py model ची गरज नाही. तुमचा snippet एक failed health check आणि दुसरा user जोडतो.
🧪 करून पाहा
python3 scale/demo.py balance
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import servers_needed
servers = ["srv-1", "srv-2", "srv-3", "srv-4"]
healthy = {"srv-1": True, "srv-2": False, "srv-3": True, "srv-4": True} # srv-2 failed its health check
sessions = {"katrina": "logged in", "dipika": "logged in"} # shared store (Redis / DynamoDB)
live = [s for s in servers if healthy[s]]
for i, user in enumerate(["katrina", "dipika", "katrina", "dipika", "katrina", "dipika"]):
s = live[i % len(live)]
print(f"click {i + 1}: {user:<8} → {s} session: {sessions[user]}")
print("healthy targets:", live)
print("400 req/s needs", servers_needed(400, 150), "servers at 60% · healthy capacity now", len(live) * 150, "req/s")
EOF
✅ तपासा — तुम्हाला काय दिसायला हवे
balance सहा ओळी print करते. Clicks 1 आणि 4 srv-1 वर पडतात आणि logged in म्हणतात; clicks 2,
3, 5 आणि 6 "server memory" column मध्ये NOT FOUND → login page म्हणतात — आणि प्रत्येक ओळ
Redis column मध्ये logged in म्हणते:
1 srv-1 logged in logged in
2 srv-2 NOT FOUND → login page logged in
तुमचा snippet हे print करतो:
click 1: katrina → srv-1 session: logged in
click 2: dipika → srv-3 session: logged in
click 3: katrina → srv-4 session: logged in
click 4: dipika → srv-1 session: logged in
click 5: katrina → srv-3 session: logged in
click 6: dipika → srv-4 session: logged in
healthy targets: ['srv-1', 'srv-3', 'srv-4']
400 req/s needs 5 servers at 60% · healthy capacity now 450 req/s
🏁 तुम्ही आत्ताच काय सिद्ध केले
Session एका server च्या memory मध्ये असताना, round robin कतरिनाला 6 clicks मध्ये 4 वेळा log out करतो. Shared store असताना प्रत्येक server उत्तर देऊ शकतो, आणि health check मध्ये अपयशी ठरलेल्या server ला फक्त clicks मिळत नाहीत. शेवटची ओळ इशारा आहे: 3 healthy servers 450 req/s पेलू शकतात, पण 400 req/s ला ते 60% च्या खूप वर आहे — आणखी एक अपयश आणि बाकीच्यांवर जास्त भार पडतो. जादा servers ठेवा.
⚠️ नेहमीच्या चुका
- हरवणारे logins "दुरुस्त" करायला stickiness चालू करणे, आणि मग एक server व्यस्त आणि बाकीचे रिकामे का असा प्रश्न पडणे
- uploads server च्या स्वतःच्या disk वर save करणे
- database ला call करणारा
/healthcheck — database हळू झाला की प्रत्येक server एकदम check मध्ये अपयशी ठरतो आणि ALB ला traffic पाठवायला कुठेच जागा उरत नाही. Check उथळ ठेवा - प्रत्येक server वरचा in-memory cache, जो write नंतर इतरांशी जुळत नाही
- deregistration delay नसणे: server काढला जातो तेव्हा चालू requests कापल्या जातात
🏭 प्रत्यक्ष वापरात
On a real account — उथळ health check, least outstanding requests, आणि stickiness बंद असलेला target group (Terraform):
resource "aws_lb_target_group" "api" {
name = "school-api"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
load_balancing_algorithm_type = "least_outstanding_requests"
deregistration_delay = 30
health_check {
path = "/health"
interval = 10
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 3
matcher = "200"
}
stickiness {
type = "lb_cookie"
enabled = false
}
}
आत्ता कोणते targets healthy आहेत ते पाहा:
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:ap-south-1:111122223333:targetgroup/school-api/6d0ecf831eec9f09
ElastiCache मधले sessions (Python, redis client) — कोणताही server ते वाचू शकतो:
import json, redis
r = redis.Redis(host="school-sessions.abc123.cache.amazonaws.com", port=6379, ssl=True)
r.set(f"session:{sid}", json.dumps({"user": "katrina"}), ex=3600) # 1 hour
user = json.loads(r.get(f"session:{sid}") or "null")
🏭 प्रत्यक्ष वापरात हे का महत्त्वाचे: ते test करा. Load balancer मागे दोन प्रती चालवा, log in करा, आणि ज्या प्रतीवर log in केले ती थांबवा. तुम्ही अजूनही logged in असाल, तर API stateless आहे.
⏭️ पुढे
कोणतीही खिडकी कोणत्याही पालकाला सेवा देऊ शकते. आता: किती खिडक्या, आणि रांग वाढली की जास्त खिडक्या कोण उघडतो? Auto Scaling.
git checkout lesson-06-auto-scaling