এই পর্বে যা শিখবেন
- Forward আর reverse proxy-র পার্থক্য — কার পক্ষে, কে বসায়, কে জানে
- Reverse proxy-র নয়টি কাজ, ধীর client-এর হিসাব আর একটি Nginx-ধাঁচের config পড়া
- API gateway-এর অংশগুলো — auth, API key, quota, transformation, version ও canary routing — আর কোনটি gateway-তে, কোনটি সার্ভিসে
- BFF pattern: কেন, কখন, কত দামে; latency-র হিসাব আর hash দিয়ে sticky canary
- Gateway-এর ঝুঁকি — SPOF, bottleneck, hop-এর latency, smart gateway, config drift — আর edge gateway, internal gateway ও service mesh-এর ভাগ
১ · Proxy মানে প্রতিনিধি — forward proxy বনাম reverse proxy
রাত ১০টায় একটি অনলাইন দোকানের অ্যাপে (কাল্পনিক) আপনি অর্ডার দিলেন। ফোন জানে শুধু একটি নাম — api.shop.example. DNS সেই নামের একটি ঠিকানা দিল, ফোন সেখানে TLS সংযোগ খুলে অনুরোধ পাঠাল (পর্ব ৭)। কিন্তু ওই ঠিকানার পেছনে আছে অর্ডার, পণ্য, পেমেন্ট আর নোটিফিকেশনের আলাদা সার্ভিস, প্রত্যেকটির কয়েকটি কপি। ফোন প্রথম যার সাথে কথা বলে, সে এদের কেউ নয়; সে একটি reverse proxy বা API gateway — সার্ভারগুলোর সামনের দরজা। পর্ব ৮-এ নকশা করা endpoint-গুলোর সামনে কে দাঁড়ায়, কী করে, আর কোথায় সে নিজেই বিপদ — এই পর্বের বিষয়।
Proxy শব্দের অর্থ প্রতিনিধি। প্রযুক্তির ভাষায়, proxy এমন একটি মধ্যস্থ প্রোগ্রাম, যে একদিকের অনুরোধ নিজে গ্রহণ করে, তারপর নিজের নামে আরেকটি অনুরোধ অন্যদিকে পাঠায়। তাই proxy থাকলে একটি নয়, দুটি আলাদা সংযোগ থাকে: client ↔ proxy আর proxy ↔ server. প্রশ্ন একটাই — প্রতিনিধিটি কার পক্ষে?
দুটি দৃশ্য ভাবুন। অফিসের পিয়ন: বিশজন কর্মীর চা-নাস্তা কিনতে একজন পিয়ন বাজারে যান। দোকানদার দেখেন শুধু পিয়নকে, কোন কর্মী কী চেয়েছেন জানেন না; আর অফিস চাইলে পিয়নকে বলে রাখতে পারে, “সিগারেট কেনা যাবে না।” এটি forward proxy — client-দের পক্ষের প্রতিনিধি। রেস্তোরাঁর ওয়েটার: খদ্দের কথা বলেন শুধু ওয়েটারের সাথে; রান্নাঘরে কতজন রাঁধুনি, কে তন্দুর আর কে ডাল সামলান — খদ্দের জানেন না, জানার দরকারও নেই। রাঁধুনি বদলালে বা নতুন চুলা বসালেও খদ্দেরের অভিজ্ঞতা বদলায় না। এটি reverse proxy — সার্ভারের পক্ষের প্রতিনিধি।
| প্রশ্ন | Forward proxy | Reverse proxy |
|---|---|---|
| কার পক্ষে কাজ করে | একটি প্রতিষ্ঠানের client-দের | একটি সেবার সার্ভারগুলোর |
| কে বসায় ও চালায় | client-এর প্রতিষ্ঠান — অফিস, বিশ্ববিদ্যালয়, কখনো ISP | সেবাদাতা নিজে |
| কে জানে এটি আছে | client — ব্রাউজার বা OS-এ সেট করা, বা নেটওয়ার্কে বাধ্যতামূলক | client সাধারণত জানে না; DNS নামটি proxy-র ঠিকানাই দেয় |
| কে লুকানো থাকে | আসল ব্যবহারকারী (সার্ভার দেখে proxy-র ঠিকানা) | আসল সার্ভার (client দেখে proxy-র ঠিকানা) |
| সাধারণ কাজ | ওয়েব ফিল্টার, নিরীক্ষার লগ, cache, বাইরে যাওয়ার নিয়ম | TLS শেষ করা, routing, load balancing, cache, সুরক্ষা |
| উদাহরণ | অফিসের corporate proxy, Squid | Nginx, HAProxy, Envoy; বিশ্বজুড়ে ছড়ালে CDN |
প্রযুক্তিগতভাবে দুটি প্রায় একই যন্ত্র — একই software (যেমন Squid) দুই ভূমিকাতেই চলতে পারে; পার্থক্য পক্ষ আর মালিকানায়। HTTP পড়ে ভার ভাগ করা load balancer (L7) আসলে একটি reverse proxy; আর L4 load balancer শুধু TCP সংযোগ এগিয়ে দেয়, HTTP খোলে না — বিস্তারিত পর্ব ১৪ (Load balancing)-এ। CDN হলো পৃথিবীজুড়ে ছড়ানো reverse proxy আর cache-এর জাল (পর্ব ১৭)। Proxy firewall আর WAF নিয়ে Computer Networks কোর্সের Firewall আর VPN পর্ব, আর proxy cache নিয়ে Web cache, proxy আর CDN পর্ব দেখুন।
১৯৯০-এর দশকে, ব্যান্ডউইথ যখন দামি, প্রতিষ্ঠানগুলো Squid-এর মতো caching forward proxy বসাত। ২০০১ সালের দিকে Willy Tarreau প্রকাশ করেন HAProxy; ২০০৪ সালে Igor Sysoev প্রকাশ করেন Nginx, যা লেখা শুরু হয়েছিল একটি ব্যস্ত ওয়েবসাইটের হাজারো একসাথের সংযোগ সামলাতে; আর ২০১৬ সালে Lyft open source করে Envoy, microservice-এর যুগের proxy.
২ · Reverse proxy কী কী করে — এক দরজায় নয়টি কাজ
অ্যাপ সার্ভার বানানো হয় ব্যবসার কাজের জন্য — অর্ডার নেওয়া, দাম হিসাব। কিন্তু ইন্টারনেটের মুখোমুখি দাঁড়ানোর আরও অনেক কাজ আছে, যা প্রতিটি অ্যাপে আলাদা করে লেখা অপচয় আর ঝুঁকি। Reverse proxy সেগুলো এক জায়গায় নেয়:
| কাজ | কী করে | লাভ |
|---|---|---|
| TLS termination | certificate আর key থাকে proxy-তে; client-এর সাথে TLS এখানেই শেষ | certificate এক জায়গায়; proxy HTTP পড়তে পারে বলেই বাকি কাজ সম্ভব |
| Host আর path দেখে routing | Host আর path মিলিয়ে backend বাছাই — /static/ এক জায়গায়, /v1/orders/ আরেক জায়গায় | এক ঠিকানার পেছনে অনেক সার্ভিস |
| Compression | text উত্তর gzip বা Brotli করে পাঠায় | ধীর link-এ কম byte; JSON প্রায়ই কয়েক গুণ ছোট হয় |
| Static file | ছবি, CSS, JavaScript সরাসরি disk বা memory থেকে দেয় | অ্যাপ সার্ভার এসব ছুঁয়েও দেখে না |
| Cache | একই উত্তর কিছুক্ষণ রেখে বারবার দেয় (পর্ব ২৮–২৯) | অ্যাপ আর database-এর চাপ কমে |
| ধীর client-এর buffering | ধীর client-এর অনুরোধ পুরো জমিয়ে তারপর backend-এ; উত্তর দ্রুত নিয়ে নিজে ধীরে পাঠায় | অ্যাপের worker আটকে থাকে না — নিচের হিসাব দেখুন |
| Connection pool | backend-এর সাথে অল্প কয়েকটি সংযোগ খোলা রেখে বারবার ব্যবহার | প্রতি অনুরোধে নতুন TCP আর TLS handshake লাগে না |
| আকার আর সময়ের সীমা | body-র সর্বোচ্চ আকার, header-এর দৈর্ঘ্য, পাঠানোর সময়সীমা | বিশাল বা অর্ধেক-পাঠানো অনুরোধ অ্যাপে পৌঁছায় না |
| ভেতরের গঠন লুকানো | client দেখে শুধু proxy-র ঠিকানা; ভেতরের IP আর software-এর সংস্করণ লুকানো | backend private নেটওয়ার্কে; আক্রমণের সুযোগ কম |
ধীর client-এর হিসাব — buffering কেন এত জরুরি
ধরা যাক একটি উত্তর ২ MB (যেমন একটি PDF রসিদ), অর্থাৎ ১৬ মেগাবিট। ব্যবহারকারীর mobile link ধরা যাক ১ Mbps — উত্তর পৌঁছাতে ১৬ সেকেন্ড। Proxy আর অ্যাপ সার্ভার একই ডেটা সেন্টারে, মাঝে ১ Gbps — একই উত্তর পার হতে ১৬ ms. Proxy না থাকলে অ্যাপের একটি worker (thread বা process) পুরো ১৬ সেকেন্ড শুধু byte ঠেলে; proxy থাকলে ১৬ ms পরেই সে মুক্ত। পর্ব ৪-এর Little-এর সূত্র দিয়ে হিসাব করুন — প্রতি সেকেন্ডে এমন ৫০টি অনুরোধ এলে একসাথে আটকে থাকা worker:
Proxy ছাড়া: ৫০/s × ১৬ s = ৮০০টি worker · buffering proxy সহ: ৫০/s × ০.০১৬ s = ০.৮টি
হাজার গুণ পার্থক্য। অ্যাপের thread pool-এ ২০০টি thread থাকলে proxy ছাড়া এই ধীর client-রাই সবগুলো দখল করবে, আর দ্রুত link-এর ব্যবহারকারীও সারিতে দাঁড়াবেন। ধীরে upload হওয়া body-ও proxy পুরোটা জমিয়ে এক টানে পাঠায়। তবে SSE, WebSocket বা চলমান stream-এ (পর্ব ১০) উত্তর জমালে real-time ভাবটাই নষ্ট — সেসব route-এ buffering বন্ধ, আর timeout দীর্ঘ সংযোগের উপযোগী।
TLS কোথায় শেষ হবে — তিনটি বিকল্প
- Termination: proxy-তে TLS খোলে, ভেতরে সাধারণ HTTP. সহজ, কিন্তু ধরে নেয় ভেতরের নেটওয়ার্ক বিশ্বস্ত।
- Re-encryption: proxy-তে খুলে কাজ সেরে নতুন TLS-এ backend-এ পাঠায়; ভেতরে প্রায়ই mTLS, যাতে দুই পক্ষই certificate দেখায়। CPU খরচ একটু বেশি, কিন্তু ভেতরের নেটওয়ার্ককে অন্ধ বিশ্বাস করতে হয় না।
- Passthrough: proxy TLS খোলেই না। Encryption শেষ পর্যন্ত অটুট, কিন্তু path দেখে routing, cache, header যোগ — কিছুই সম্ভব নয়; শুধু handshake-এ client-এর জানানো host নাম (SNI) দেখে ভাগ করা যায়।
Handshake আর certificate-এর বিস্তারিত Computer Networks কোর্সের TLS আর HTTPS পর্বে।
আসল client কে — X-Forwarded-For আর বিশ্বাসের নিয়ম
Proxy নিজের নামে নতুন সংযোগ খোলে, তাই backend দেখে proxy-র IP. আসল client-এর IP আর মূল protocol জানাতে proxy header যোগ করে — প্রচলিত X-Forwarded-For আর X-Forwarded-Proto; মানসম্মত রূপ Forwarded header (RFC 7239, ২০১৪)। কিন্তু client নিজেও এই header বানিয়ে পাঠাতে পারে। তাই backend মানটি বিশ্বাস করবে শুধু চেনা proxy থেকে এলে, আর একেবারে বাইরের proxy বাইরে থেকে আসা মান মুছে নিজের দেখা মান বসাবে। L4 load balancer HTTP পড়ে না, তাই সেখানে HAProxy-র PROXY protocol-এর মতো আলাদা ব্যবস্থা লাগে।
৩ · একটি Nginx-ধাঁচের config পড়া — উদাহরণ
নিচের config একটি সরল reverse proxy-র — HTTPS, দুটি path-এর routing, timeout আর একটি rate-limit-এর খসড়া। এটি পড়ে বোঝার জন্য; বাস্তবে নিজের Nginx-সংস্করণের অফিসিয়াল ডকুমেন্টেশনে প্রতিটি লাইন মিলিয়ে নিতে হবে।
# Illustrative only: a simplified reverse proxy for api.shop.example.
# Directive names and defaults differ between Nginx versions - check the docs.
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s; # rate-limit stub
upstream orders_v1 {
server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=10s;
keepalive 32; # idle connections kept open to backends
}
upstream catalog_v1 {
server 10.0.2.21:8080;
keepalive 16;
}
server { # plain HTTP: only redirect to HTTPS
listen 80;
server_name api.shop.example;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on; # before 1.25.1: "listen 443 ssl http2;"
server_name api.shop.example;
ssl_certificate /etc/nginx/tls/api.shop.example.crt;
ssl_certificate_key /etc/nginx/tls/api.shop.example.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 1m; # bigger request body -> 413
proxy_http_version 1.1; # needed for the keepalive pool
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-ID $request_id;
proxy_connect_timeout 2s;
proxy_read_timeout 5s; # max gap between two reads -> else 504
location /v1/orders/ {
limit_req zone=per_ip burst=20 nodelay;
limit_req_status 429; # default would be 503
proxy_pass http://orders_v1;
}
location /v1/ {
proxy_pass http://catalog_v1;
}
location / {
return 404;
}
}প্রতিটি অংশ কী বোঝায়, আর কোথায় মানুষ (এবং AI) প্রায়ই ভুল করে:
| লাইন | কী বোঝায় | সাধারণ ভুল বা সতর্কতা |
|---|---|---|
limit_req_zone, limit_req | প্রতি IP-তে সেকেন্ডে গড়ে ১০টি, হঠাৎ ২০টি বাড়তি পর্যন্ত ছাড় | default উত্তর 503; 429 চাইলে limit_req_status লাগে। গণনা একটি Nginx instance-এর ভেতরেই (তার সব worker মিলে) — তিনটি instance মানে কার্যত তিন গুণ সীমা |
keepalive 32 | প্রতিটি worker backend-এর সাথে সর্বোচ্চ ৩২টি অলস সংযোগ খোলা রাখে — connection pool | সাথে proxy_http_version 1.1 আর খালি Connection header না দিলে pool কাজই করে না |
proxy_read_timeout 5s | backend থেকে পরপর দুটি read-এর মাঝে সর্বোচ্চ ৫ সেকেন্ড; পেরোলে 504 | এটি পুরো উত্তরের মোট সময়সীমা নয়; একটু একটু করে byte পাঠানো backend অনেকক্ষণ টিকে যেতে পারে |
location /v1/orders/ আর location /v1/ | prefix location-গুলোর মধ্যে সবচেয়ে লম্বা মিল জেতে (longest prefix match) | /v1/orders (শেষে / নেই) এই location-এ পড়ে না। proxy_pass-যুক্ত এমন location-এর জন্য Nginx নিজে থেকে 301 দিয়ে /v1/orders/-এ পাঠায় (অফিশিয়াল ডকুমেন্টেশনে বর্ণিত) — POST-এর জন্য ঝুঁকির; দরকারে location = /v1/orders আলাদা লিখুন |
max_fails=3 fail_timeout=10s | ১০ সেকেন্ডে ৩টি ব্যর্থ চেষ্টা হলে সেই সার্ভার ১০ সেকেন্ড বাদ — passive health check | নিয়মিত probe পাঠানো active health check লেখার সময়ে open-source Nginx-এ নেই (বাণিজ্যিক সংস্করণে বা HAProxy, Envoy-তে আছে) — সংস্করণভেদে যাচাই করুন |
http2 on; | client-এর সাথে HTTP/2 | 1.25.1-এর আগে লেখা হতো listen 443 ssl http2; — পুরোনো টিউটোরিয়াল আর AI প্রায়ই দুটি মেশায় |
client_max_body_size 1m | body ১ MB-এর বেশি হলে 413 | ছবি upload-এর route-এ আলাদা, বড় মান লাগবে |
Config-ও কোড। একটি ভুল location বা ভুল timeout সব API একসাথে ভাঙতে পারে। তাই config থাকবে version control-এ, পরিবর্তন যাবে review-র মধ্য দিয়ে, প্রয়োগের আগে nginx -t-এর মতো syntax পরীক্ষা আর পরীক্ষামূলক traffic, আর প্রয়োগ হবে ধাপে ধাপে — অংশ ৮-এ এর কারণ দেখবেন।
৪ · API gateway — API-র জন্য বিশেষ reverse proxy
সার্ভিস যখন অনেক (microservices, পর্ব ৪৭), প্রতিটিকে token যাচাই, quota গণনা আর log লেখা নিজে করতে হলে একই কোড বিশ জায়গায়, বিশ রকম বাগ নিয়ে। এমন সাধারণ চাহিদার নাম cross-cutting concern. API gateway এগুলো এক জায়গায় আনে। প্রযুক্তিগতভাবে এটি একটি L7 reverse proxy, কিন্তু ভাবে API, client-অ্যাপ, API key আর quota-র ভাষায়। Envoy-ভিত্তিক gateway, Kong-এর মতো open-source gateway বা cloud-এর managed gateway — সবাই এই কাজ করে।
ব্যাংকের অভ্যর্থনা ডেস্ক আর টোকেন মেশিনের কথা ভাবুন। ডেস্ক পরিচয়পত্র দেখে (authentication), টোকেন নম্বর দেয় (request ID), ঠিক কাউন্টারে পাঠায় (routing), “একজন দিনে একবারই এই সেবা” মানায় (quota), পুরোনো ফর্মের তথ্য নতুন ফর্মে তোলে (transformation). কিন্তু ডেস্ক কারও ঋণ মঞ্জুর করে না — সেটা ভেতরের কর্মকর্তার কাজ। Gateway-ও তেমনি: দরজার নিয়ম তার, ব্যবসার সিদ্ধান্ত সার্ভিসের।
| কাজ | Gateway কী করে | বিস্তারিত |
|---|---|---|
| Authentication | Authorization header-এর access token (প্রায়ই JWT) যাচাই — স্বাক্ষর, মেয়াদ, কার জন্য ইস্যু, অনুমতির পরিধি; ব্যর্থ হলে 401 | পর্ব ৫২ |
| API key | কোন অ্যাপ বা পার্টনার ডাকছে চেনা — quota আর বিলের জন্য; ব্যবহারকারীর পরিচয় নয় | পর্ব ৫২ |
| Rate limit আর quota | সেকেন্ড বা মিনিটের সীমা (হঠাৎ ভিড় ঠেকাতে), মাসের quota (চুক্তি অনুযায়ী); ছাড়ালে 429 আর Retry-After | algorithm পর্ব ৪৯-এ |
| Transformation | header যোগ-বাদ, field-এর নাম বদল, পুরোনো client-এর জন্য উত্তর মানিয়ে দেওয়া, JSON ↔ gRPC অনুবাদ | পর্ব ৯, পর্ব ১২ |
| Version routing | /v1/orders/ → orders-v1, /v2/orders/ → orders-v2 | পর্ব ৮, অংশ ৭ |
| Canary routing | নির্দিষ্ট header বা ব্যবহারকারীদের ছোট অংশকে নতুন সংস্করণে | অংশ ৭, পর্ব ৫৩ |
| Aggregation | কয়েকটি সার্ভিসের উত্তর জুড়ে একটি উত্তর | অংশ ৬ (BFF) |
| Observability | প্রতিটি অনুরোধে request ID, access log, route-ভিত্তিক latency ও error-এর metric | পর্ব ৫০ |
| WAF ও bot-সুরক্ষা | পরিচিত ক্ষতিকর অনুরোধ আটকানো, সন্দেহজনক স্বয়ংক্রিয় traffic যাচাই — প্রতিরক্ষার বাড়তি স্তর, নিরাপদ কোডের বিকল্প নয় | Firewall আর VPN |
পুরো ছবিটি একসাথে দেখুন — এই পর্বের বাকি সবকিছু এই নকশার কোনো না কোনো অংশ:
সামনের দরজার পুরো নকশা
Clients → edge L4 load balancer → stateless gateway instances → BFFs → services
একটি অনুরোধ gateway-এর ভেতর দিয়ে
অ্যাপ পাঠাল GET /v1/orders/42. Gateway-এর ভেতরে অনুরোধটি ধাপে ধাপে এগোয়, আর প্রতিটি ধাপ সেখানেই থামিয়ে উত্তর দিতে পারে। “পরে” চাপুন; “ব্যর্থ হলে” সারিতে দেখুন কোন উত্তর ফেরত যেত:
- TLS শেষ
:443ID দেওয়া - Auth
JWTকে ডাকছে - Rate limit
58/60quota - Route
/v1/orders/কোন সার্ভিস - Transform
headersপাঠানো - Response
200gzip, log
| অনুরোধ / উত্তর | |
| এই ধাপের কাজ | |
| পরিচয় | |
| ব্যর্থ হলে | |
| Gateway-এর মোট খরচ |
- TLS শেষ, ID দেওয়া। ফোন gateway-এর সাথে TLS সংযোগ খোলে (আগের session আবার ব্যবহার হলে খরচ কম — ধরা যাক ০.৩০ ms CPU লাগে)। এখন gateway প্রথমবার পরিষ্কার HTTP দেখে, আর অনুরোধটিকে একটি ID দেয় —
r-7f3a— যা প্রতিটি log আর নিচের প্রতিটি সার্ভিসে সাথে যাবে। - Auth — কে ডাকছে? Token-এর স্বাক্ষর public key দিয়ে যাচাই হয়, মেয়াদ আর কার জন্য ইস্যু (audience) মেলানো হয়। ফল: ব্যবহারকারী
u-1029, অনুমতিorders:read. Token না থাকলে বা ভুল হলে এখানেই401— অর্ডার সার্ভিস কিছুই টের পায় না। - Rate limit আর quota. পরিচয় জানা, তাই ব্যবহারকারী-ভিত্তিক গণনা সম্ভব: এই মিনিটে u-1029-এর ৫৮তম অনুরোধ, সীমা ৬০ — চলবে। ৬১তম হলে
429আরRetry-After. IP-ভিত্তিক মোটা দাগের একটি সীমা প্রায়ই auth-এর আগেই বসে, যাতে token যাচাইয়ের ধাপ নিজেই ভিড়ে ডুবে না যায়। - Route — কোন সার্ভিস? Host
api.shop.exampleআর path/v1/orders/42-এর সাথে সবচেয়ে লম্বা prefix মেলে/v1/orders/→ orders-v1. তারপর সেই সার্ভিসের সুস্থ কপিগুলোর একটি বেছে নেওয়া হয় — এখানে10.0.1.12:8080(কোন নিয়মে বাছাই, তা পর্ব ১৪-এ)। কোনো route না মিললে404, method না মিললে405. - Transform আর পাঠানো। Gateway অনুরোধে
X-User-IdআরX-Request-IDযোগ করে, বাইরের token বাদ দেয় (অনেক নকশায় token রেখেও দেওয়া হয়, যাতে সার্ভিস নিজে যাচাই করতে পারে — ভাবনার প্রশ্ন ০২)। তারপর pool-এর একটি খোলা সংযোগে পাঠায়। Backend সংযোগ না নিলে502, সময় পেরোলে504. - উত্তর ফেরত। orders-v1 ধরা যাক ১৮ ms-এ ৬.২ KB JSON দিল। Gateway ভেতরের
Serverheader মুছে দেয়, উত্তর gzip করে ১.৯ KB বানায়, আর access log-এ এক লাইন লেখে। Gateway-এর নিজের মোট খরচ ধরা যাক ০.৯০ ms — সার্ভিসের ১৮ ms-এর পাশে সামান্য, কিন্তু শূন্য নয় (অংশ ৮)।
Gateway নিজে যেসব status code দেয়
উত্তরটি সার্ভিসের, নাকি পথেই gateway দিয়েছে — status code দেখে আন্দাজ করা যায় (সাধারণ নিয়ম পর্ব ৮-এ):
| Code | Gateway-তে কখন |
|---|---|
401 Unauthorized | পরিচয় প্রমাণ হয়নি — token নেই, মেয়াদ শেষ, স্বাক্ষর ভুল (নামের “authorized” শব্দটি ঐতিহাসিক বিভ্রান্তি) |
403 Forbidden | পরিচয় জানা, কিন্তু অনুমতি নেই — scope-এ orders:write নেই, বা ঠিকানা allowlist-এ নেই |
404 / 405 | কোনো route মেলেনি / route আছে, কিন্তু method অনুমোদিত নয় |
413 Content Too Large | body সীমার বেশি |
429 Too Many Requests | rate বা quota ছাড়িয়েছে; সাথে Retry-After |
502 Bad Gateway | backend থেকে বৈধ উত্তর আসেনি — সংযোগ প্রত্যাখ্যাত, অসম্পূর্ণ উত্তর |
503 Service Unavailable | কোনো সুস্থ backend নেই, বা ইচ্ছাকৃত load shedding (পর্ব ১৮) |
504 Gateway Timeout | backend gateway-এর timeout-এর মধ্যে উত্তর দেয়নি |
নিজে চালিয়ে দেখুন — একটি ছোট gateway router
নিচের simulator-এ route table (host আর path-prefix → সার্ভিস, অনুমোদিত method, token লাগবে কি না) আর প্রতিটি client-এর জন্য একটি ছোট quota রাখা আছে। এগারোটি নমুনা অনুরোধে gateway কী সিদ্ধান্ত নেয়, দেখুন:
// A tiny API-gateway router: host + LONGEST path prefix -> service,
// method rules, a token check and a per-client quota for one time window.
const routes = [ // [host, path prefix, service, allowed methods, token needed?]
["api.shop.example", "/v1/", "catalog-v1", "GET", false],
["api.shop.example", "/v1/orders/", "orders-v1", "GET POST", true],
["api.shop.example", "/v2/orders/", "orders-v2", "GET POST", true],
["admin.shop.example", "/", "admin-ui", "GET", true],
];
const tokens = { "tok-rahim": "android-app", "tok-mitu": "web-app" }; // token -> client
const QUOTA = 3, used = {}; // tiny per-client quota per window; client -> count so far
function handle(method, host, path, token, ip) {
const hits = routes.filter(([h, p]) => h === host && path.startsWith(p));
if (!hits.length) return "404 no route";
const [, prefix, svc, methods, auth] = hits.reduce((a, b) => (b[1].length > a[1].length ? b : a));
if (!methods.split(" ").includes(method)) return "405 method not allowed on " + prefix;
const client = auth ? tokens[token] : "ip:" + ip; // anonymous callers counted by IP
if (!client) return "401 missing or invalid token";
if ((used[client] || 0) >= QUOTA) return "429 quota used up: " + client;
used[client] = (used[client] || 0) + 1;
return "200 " + svc + " [" + prefix + "] " + client + " " + used[client] + "/" + QUOTA;
}
const requests = [ // method host path token ip ("-" = no token)
"GET api /v1/products/42 - 10.0.0.5", "GET api /v1/orders/77 tok-rahim 10.0.0.6",
"POST api /v1/orders/ - 10.0.0.6", "DELETE api /v1/products/42 - 10.0.0.5",
"GET api /v1/orders tok-rahim 10.0.0.6", "GET api /v3/orders/1 tok-rahim 10.0.0.6",
"GET pay /v1/orders/77 tok-rahim 10.0.0.6", "POST api /v2/orders/ tok-mitu 10.0.0.9",
"GET api /v1/orders/78 tok-rahim 10.0.0.6", "GET api /v1/orders/79 tok-rahim 10.0.0.6",
"GET api /v1/orders/80 tok-rahim 10.0.0.6",
];
for (const line of requests) {
const [m, h, p, t, ip] = line.split(" ");
console.log((m + " " + h + " " + p).padEnd(27) + handle(m, h + ".shop.example", p, t, ip));
}আউটপুট:
GET api /v1/products/42 200 catalog-v1 [/v1/] ip:10.0.0.5 1/3
GET api /v1/orders/77 200 orders-v1 [/v1/orders/] android-app 1/3
POST api /v1/orders/ 401 missing or invalid token
DELETE api /v1/products/42 405 method not allowed on /v1/
GET api /v1/orders 200 catalog-v1 [/v1/] ip:10.0.0.6 1/3
GET api /v3/orders/1 404 no route
GET pay /v1/orders/77 404 no route
POST api /v2/orders/ 200 orders-v2 [/v2/orders/] web-app 1/3
GET api /v1/orders/78 200 orders-v1 [/v1/orders/] android-app 2/3
GET api /v1/orders/79 200 orders-v1 [/v1/orders/] android-app 3/3
GET api /v1/orders/80 429 quota used up: android-app
প্রথম অনুরোধ token ছাড়াই চলল, কারণ /v1/ route-এ token লাগে না; গণনা হলো IP দিয়ে। তৃতীয়টি token ছাড়া অর্ডার দিতে চাইল — 401, অর্ডার সার্ভিস টেরই পেল না। চতুর্থটি 405. পঞ্চম লাইনটি সবচেয়ে শিক্ষণীয়: /v1/orders-এর শেষে / নেই, তাই মিলেছে /v1/ — অর্ডারের অনুরোধ চলে গেল catalog-v1-এ, token যাচাই ছাড়াই। অংশ ৩-এর Nginx এই বিশেষ ক্ষেত্রে নিজে থেকে 301 redirect দেয়, কিন্তু সব gateway বা নিজের লেখা router তা করে না; প্রতিকার segment ধরে মেলানো, বা /v1/orders-এর জন্য exact route অথবা redirect. এরপর দুটি 404 — অচেনা version আর অচেনা host. শেষে quota: web-app-এর গণনা আলাদা (১/৩), আর android-app ৩/৩ পূর্ণ করার পর পেল 429. খেয়াল করুন, used একটি process-এর memory-তে; তিনটি gateway instance থাকলে প্রত্যেকে আলাদা গুনবে — তখন ভাগ করা store লাগে (পর্ব ৩০, ৪৯), নইলে সীমা কার্যত তিন গুণ।
৫ · কোনটি gateway-তে, কোনটি সার্ভিসে — “smart gateway” ফাঁদ
সব অনুরোধ gateway দিয়ে যায় — তাই নতুন যেকোনো নিয়ম “gateway-তেই বসিয়ে দিই” বলা সবচেয়ে সহজ। নিয়মটি মনে রাখুন: gateway-তে যাবে অনুরোধ-স্তরের সাধারণ নীতি, যার উত্তর অনুরোধ দেখেই দেওয়া যায়; যে সিদ্ধান্তে ব্যবসার ডেটা বা নিয়ম লাগে, তা থাকবে সেই ডেটার মালিক সার্ভিসে।
| দায়িত্ব | কোথায় | কেন |
|---|---|---|
| TLS শেষ করা, HTTP থেকে HTTPS-এ redirect | Gateway | সবার জন্য একই; certificate এক জায়গায় |
| Token-এর স্বাক্ষর আর মেয়াদ যাচাই | Gateway (সার্ভিসও আবার যাচাই করতে পারে) | ভুয়া অনুরোধ ভেতরে ঢোকার আগেই থামে |
| “অর্ডার ৪২ কি u-1029-এর?” | সার্ভিস | উত্তর জানতে সার্ভিসের ডেটা লাগে; বাদ পড়লে OWASP-এর API ঝুঁকির তালিকার এক নম্বর — BOLA |
| API key প্রতি মিনিটে ৬০০ অনুরোধ | Gateway | শুধু অনুরোধের হার; ব্যবসার অর্থ নেই |
| “দিনে ৫ বারের বেশি টাকা তোলা যাবে না” | সার্ভিস | ব্যবসার নিয়ম — স্থায়ী হিসাব, audit আর ব্যতিক্রম লাগে |
| ঈদ-অফারের ছাড় বা দামের হিসাব | সার্ভিস | domain-এর যুক্তি; gateway-তে গেলে প্রতিটি অফারে gateway deploy |
| Body-র আকার আর JSON-এর মৌলিক গঠন | Gateway (মোটা দাগে) + সার্ভিস (অর্থ) | ভাঙা body আগেই থামে; “পরিমাণ ঋণাত্মক নয় তো?” সার্ভিসের কাজ |
| একটি পর্দার জন্য কয়েকটি উত্তর জোড়া | BFF | client-নির্দিষ্ট; সবার ভাগ করা edge gateway-তে নয় |
| Request ID, access log, metric | Gateway + প্রতিটি সার্ভিস ID এগিয়ে দেয় | পুরো পথ জুড়ে একটি অনুরোধ খুঁজে পাওয়া |
“Smart gateway” — একটি পরিচিত anti-pattern. শুরু হয় নিরীহভাবে: gateway-এর plugin-এ একটি ছোট script — “এই field থাকলে ১০% ছাড়” ধরনের নিয়ম — তারপর আরেকটি, তারপর কয়েকটি সার্ভিস পরপর ডেকে একটি অর্ডার-প্রবাহ চালানো। লক্ষণ: প্রতিটি নতুন feature-এ gateway দলের অনুমোদন লাগে; gateway-এর deploy হয়ে ওঠে সবচেয়ে ভয়ের deploy; একটি ভুল নিয়মে সব API একসাথে ভাঙে (blast radius সবচেয়ে বড়); আর gateway-এর config-ই এমন কোড, যার পরীক্ষা নেই, মালিক অস্পষ্ট। ইতিহাস কেন বারবার এটি ফিরিয়ে আনে, ভাবনার প্রশ্ন ০৩-এ।
৬ · BFF — প্রতিটি client-এর জন্য নিজের backend
একটি সাধারণ-উদ্দেশ্যের API সব client-কে একই উত্তর দেয়। কিন্তু client-রা আলাদা: মোবাইলের পর্দা ছোট, link ধীর, ব্যাটারি সীমিত; ওয়েবের পর্দা বড়; পার্টনার চায় স্থির চুক্তি। এক API সবার মন রাখতে গিয়ে কারও জন্যই ভালো হয় না — মোবাইল পায় অপ্রয়োজনীয় field আর অনেকগুলো call, ওয়েব দল নতুন field-এর জন্য API দলের সারিতে অপেক্ষা করে।
Backend for Frontend (BFF) pattern-এর উত্তর: প্রতিটি ব্যবহারকারী-অভিজ্ঞতার জন্য একটি পাতলা, আলাদা backend. নামটি জনপ্রিয় হয় Sam Newman-এর ২০১৫ সালের দিকের একটি লেখায়; প্রকাশিত লেখা অনুযায়ী তার আগেই কয়েকটি দল (যেমন SoundCloud-এর প্রকৌশলীরা) এভাবে কাজ করছিল। BFF-এর মূল কাজ aggregation: কয়েকটি সার্ভিস ডেকে একটি উত্তর বানানো, আর ছেঁটে client-এর মাপমতো করা।
পাইকারি আড়ত (মূল সার্ভিসগুলো) বিক্রি করে বস্তা ধরে, সবার জন্য একই মাপে। পাড়ার খুচরা দোকান (BFF) সেই মাল ছোট প্যাকেটে, পাড়ার মানুষের চাহিদামতো সাজায়; ক্রেতাকে পাঁচটি আড়তে ঘুরতে হয় না। শহরের দোকান আর গ্রামের হাটের দোকান একই আড়ত থেকে কেনে, কিন্তু সাজায় আলাদা — ঠিক যেমন Mobile BFF আর Web BFF.
একটি মোবাইল অ্যাপের হোম স্ক্রিনে লাগে তিনটি জিনিস: ব্যবহারকারীর profile, feed আর নোটিফিকেশনের সংখ্যা। ধরা যাক (শেখার জন্য গোল সংখ্যা): mobile link-এর RTT ১৫০ ms, CDN-এ ১ ms, CDN edge থেকে gateway পর্যন্ত RTT ৩০ ms, gateway-এর নিজের কাজ ১ ms, ডেটা সেন্টারের ভেতরের RTT ০.৫ ms, BFF-এর নিজের কাজ ৩ ms; সার্ভিসের সময় profile ২০, feed ৪৫, notify ১৫ ms.
| ফোনের অনুরোধ | |
| ধীর link-এ অপেক্ষা | |
| মোট সময় | |
| উত্তরের আকার |
- BFF ছাড়া — তিনটি অনুরোধ, একটির পর একটি। অ্যাপ আগে profile আনে, তারপর feed, তারপর নোটিফিকেশনের সংখ্যা। প্রতিটি অনুরোধে পুরো পথ ১৫০ + ১ + ৩০ + ১ + ০.৫ = ১৮২.৫ ms, তার ওপর সার্ভিসের সময়: ২০২.৫ + ২২৭.৫ + ১৯৭.৫ = ৬২৭.৫ ms. তিনটি সাধারণ উত্তর মিলে ৩৮ KB, যার বেশিরভাগ field এই পর্দায় লাগেই না।
- BFF সহ — একটিমাত্র অনুরোধ। অ্যাপ পাঠায়
GET /mobile/home. একই পথ পেরিয়ে অনুরোধ পৌঁছায় Mobile BFF-এ, যার নিজের কাজ ৩ ms: মোট ১৮৫.৫ ms — শুধু পথের দাম, এখনো কোনো ডেটা আনা হয়নি। - ভেতরে একসাথে তিনটি call. BFF তিন সার্ভিসকে একসাথে ডাকে; ভেতরের RTT মাত্র ০.৫ ms, তাই profile ২০.৫, feed ৪৫.৫, notify ১৫.৫ ms. অপেক্ষা সবচেয়ে ধীরটির জন্য (fan-out): ১৮৫.৫ + ৪৫.৫ = ২৩১.০ ms. ধীর link একবারই পার হতে হলো — লাভের আসল উৎস এটাই।
- একটি সার্ভিস চুপ — আংশিক উত্তর। ধরা যাক notify উত্তর দিচ্ছে না। BFF প্রতিটি call-এ ৬০ ms সময়সীমা রেখেছে; সময় ফুরোলে notify-এর জায়গায়
nullবসিয়ে বাকিটা পাঠায়: ১৮৫.৫ + ৬০ = ২৪৫.৫ ms. অ্যাপ ঘণ্টির সংখ্যা দেখায় না, কিন্তু পর্দা ঠিকই খোলে — graceful degradation. - ছাঁটাই আর আকার। আবার স্বাভাবিক অবস্থা, সময় ২৩১.০ ms. BFF শুধু ছোট পর্দার দরকারি field রাখে: ৩৮ KB থেকে ৬ KB. ধরা যাক link ১ Mbps আর ১ KB = ১,০০০ byte: ৩৮ KB পাঠাতে প্রায় ৩০৪ ms, ৬ KB-তে প্রায় ৪৮ ms — এই সময় উপরের হিসাবে ধরা হয়নি, তাই বাস্তব লাভ আরও বেশি।
- সারসংক্ষেপ। ফোনের অনুরোধ ৩ থেকে ১, ধীর link-এ অপেক্ষা ৩ RTT থেকে ১, সময় ৬২৭.৫ থেকে ২৩১.০ ms, আকার ৩৮ থেকে ৬ KB. দাম: একটি বাড়তি hop (৩ + ০.৫ = ৩.৫ ms), আরেকটি সার্ভিস চালানো, আর একজন মালিক — সাধারণত মোবাইল অ্যাপের দল।
একই হিসাব কোডে — চারটি পরিস্থিতি, দুই রকম link-এ। hop বা svc-এর মান বদলে নিজের অনুমান বসাতে পারেন:
// p50-ish latency (ms) of a home screen that needs 3 backend services.
// Every number is an ASSUMED round value for learning, not a measurement.
const hop = { cdn: 1, edgeToGw: 30, gw: 1, bff: 3, dcRtt: 0.5 }; // compute + RTTs
const svc = { profile: 20, feed: 45, notif: 15 }; // service times
const t = Object.values(svc);
const sum = a => a.reduce((x, y) => x + y, 0);
// one API call from the phone: last mile -> CDN edge -> gateway -> one service
const call = (lastMile, s) => lastMile + hop.cdn + hop.edgeToGw + hop.gw + hop.dcRtt + s;
// one trip from the phone to the BFF (same path, plus the BFF's own compute)
const front = lastMile => lastMile + hop.cdn + hop.edgeToGw + hop.gw + hop.dcRtt + hop.bff;
function scenarios(lastMile) {
return [
["app calls 3 APIs one by one", sum(t.map(s => call(lastMile, s))), "3 req, 3 waits"],
["app calls 3 APIs in parallel", Math.max(...t.map(s => call(lastMile, s))), "3 req, 1 wait"],
["BFF calls services one by one", front(lastMile) + sum(t.map(s => hop.dcRtt + s)), "1 req, 1 wait"],
["BFF calls services in parallel", front(lastMile) + Math.max(...t.map(s => hop.dcRtt + s)), "1 req, 1 wait"],
];
}
for (const [name, rtt] of [["mobile, last-mile RTT 150 ms", 150], ["broadband, RTT 20 ms", 20]]) {
console.log("== " + name + " ==");
for (const [k, ms, phone] of scenarios(rtt))
console.log(k.padEnd(31) + ms.toFixed(1).padStart(6) + " ms phone: " + phone);
}
console.log("one extra hop inside the DC: gateway " + (hop.gw + hop.dcRtt) + " ms, BFF " + (hop.bff + hop.dcRtt) + " ms");আউটপুট:
== mobile, last-mile RTT 150 ms ==
app calls 3 APIs one by one 627.5 ms phone: 3 req, 3 waits
app calls 3 APIs in parallel 227.5 ms phone: 3 req, 1 wait
BFF calls services one by one 267.0 ms phone: 1 req, 1 wait
BFF calls services in parallel 231.0 ms phone: 1 req, 1 wait
== broadband, RTT 20 ms ==
app calls 3 APIs one by one 237.5 ms phone: 3 req, 3 waits
app calls 3 APIs in parallel 97.5 ms phone: 3 req, 1 wait
BFF calls services one by one 137.0 ms phone: 1 req, 1 wait
BFF calls services in parallel 101.0 ms phone: 1 req, 1 wait
one extra hop inside the DC: gateway 1.5 ms, BFF 3.5 ms
মোবাইলে পরপর তিন অনুরোধ (627.5 ms) থেকে BFF-এর parallel call (231.0 ms): প্রায় ৩৯৬.৫ ms বাঁচল — ৩৬২ ms ধীর পথ দুবার কম পার হওয়া থেকে, বাকি ৩৪.৫ ms সার্ভিসের সময় যোগ না করে সবচেয়ে ধীরটির অপেক্ষা থেকে। এবার সৎ তুলনা: অ্যাপ নিজেই তিনটি অনুরোধ একসাথে পাঠালে (HTTP/2-এ একই সংযোগে) সময় 227.5 ms — BFF-এর চেয়ে ৩.৫ ms কম, ঠিক BFF hop-এর দাম। অর্থাৎ call স্বাধীন হলে BFF-এর latency-লাভ প্রায় শূন্য; লাভ তখন কম byte, একটি অনুরোধ (ব্যাটারি) আর আংশিক ব্যর্থতার নীতি সার্ভারে। কিন্তু call প্রায়ই পরস্পর-নির্ভর — feed আনতে আগে profile লাগে — তখন ফোন থেকে parallel করা যায় না, আর BFF আবার বড় পার্থক্য গড়ে (অনুশীলন ৩)। Broadband-এ লাভ ছোট: 237.5 → 101.0 ms. এগুলো p50-ধাঁচের মান; p99-এ প্রতিটি hop নিজের ধীর মুহূর্ত যোগ করে, আর fan-out-এ সবচেয়ে ধীর সার্ভিসের tail পুরো উত্তরের tail হয়ে যায় (পর্ব ৪)।
BFF-এর লাভ
ধীর link-এ কম RTT আর কম byte; client-এর মাপমতো উত্তর; আংশিক ব্যর্থতার নীতি এক জায়গায়; পুরোনো অ্যাপ-সংস্করণের জন্য উত্তর মানিয়ে দেওয়া; client দল নিজের গতিতে চলে।
যখন client-ভেদে চাহিদা খুব আলাদাBFF-এর দাম
একই কাজ কয়েকটি BFF-এ আলাদা করে লেখা (duplication); মালিক আর on-call কে; আরও deploy; একটি বাড়তি hop; আর BFF-এ ব্যবসার যুক্তি ঢোকার ঝুঁকি।
client একটি বা দল ছোট হলে অকারণভালো অভ্যাস: একটি ব্যবহারকারী-অভিজ্ঞতার জন্য একটি BFF, প্রতিটি ফোন-মডেলের জন্য নয়; BFF পাতলা — শুধু জোড়া, ছাঁটা আর মানিয়ে দেওয়া; মালিক সেই client-এর দল। আরেকটি পথ GraphQL, যেখানে client নিজেই বলে দেয় কোন field লাগবে; এর trade-off পর্ব ৯-এ।
৭ · Version আর canary routing — নতুন সংস্করণে ধীরে ধীরে
Version routing
API-তে ভাঙনধরা পরিবর্তন এলে নতুন সংস্করণ (API versioning, পর্ব ৮)। Gateway এটি সহজ করে: /v1/orders/ যায় orders-v1-এ, /v2/orders/ orders-v2-এ; কেউ কেউ header দিয়ে version বোঝান। পুরোনো অ্যাপ-সংস্করণ মাসের পর মাস ফোনে থাকে, তাই v1 অনেক দিন বাঁচিয়ে রাখতে হয় — কখনো gateway-এর transformation দিয়ে v1-এর অনুরোধ নতুন সার্ভিসের ভাষায় অনুবাদ করে।
Canary routing — কাদের নতুন সংস্করণে পাঠাবেন
Canary release-এ নতুন সংস্করণ আগে খুব ছোট অংশ পায়। Header দিয়ে: ভেতরের পরীক্ষকদের অ্যাপ একটি নির্দিষ্ট header পাঠায়, gateway তা দেখে v2-তে পাঠায় — তবে শুধু যাচাই করা কর্মী-অ্যাকাউন্টের জন্য। শতাংশ দিয়ে: ব্যবহারকারীর ID থেকে একটি hash বানিয়ে ০ থেকে ৯,৯৯৯-এর একটি bucket; bucket ৫০০-এর কম হলে v2 — প্রায় ৫%.
প্রতিটি অনুরোধে নতুন লটারি নয় কেন? তাহলে একই মানুষ এক মুহূর্তে নতুন পর্দা, পরের মুহূর্তে পুরোনো পর্দা দেখবেন — বিভ্রান্তি, cache-এ গোলমাল, আর অভিযোগ এলে “উনি কোন সংস্করণে ছিলেন?” বলা অসম্ভব। Hash দিলে ভাগটি sticky: একই ID, প্রতিবার একই bucket. ৫% থেকে ২০%-এ বাড়ালে শুধু সীমা ৫০০ থেকে ২,০০০ হয় — আগের সবাই v2-তেই থাকেন। আর প্রতিটি rollout-এর নাম hash-এ মেশানো (salt), যাতে প্রতিবার একই ৫% পরীক্ষার মুখে না পড়েন।
// Canary routing: about 5% of USERS (not requests) go to v2, chosen by a hash of the user ID.
function fnv1a(str) { // FNV-1a, 32-bit
let h = 0x811c9dc5;
for (let i = 0; i < str.length; i++) h = Math.imul(h ^ str.charCodeAt(i), 0x01000193) >>> 0;
return h;
}
function mulberry32(a) { // small seeded PRNG: same "random" users every run
return () => { a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
}
const bucket = id => fnv1a("home-v2:" + id) % 10000; // 0..9999, salted per rollout
const version = (id, pct) => (bucket(id) < pct * 100 ? "v2" : "v1");
const rnd = mulberry32(2026), ids = new Set();
while (ids.size < 10000) ids.add("u" + Math.floor(rnd() * 1e9));
const users = [...ids];
const at5 = users.filter(u => version(u, 5) === "v2");
console.log("5% rollout : " + at5.length + " of " + users.length + " users on v2 (" + (at5.length / 100).toFixed(2) + "%)");
for (const u of [users[0], at5[0]]) // sticky: same user, same answer
console.log(" " + u.padEnd(11) + "bucket " + String(bucket(u)).padStart(4) + " -> 5 requests: " +
[1, 2, 3, 4, 5].map(() => version(u, 5)).join(" "));
const at20 = users.filter(u => version(u, 20) === "v2");
const kept = at5.filter(u => version(u, 20) === "v2").length;
console.log("20% rollout: " + at20.length + " users on v2; of the first " + at5.length + ", still on v2: " + kept);
let mixed = 0; // contrast: a fresh coin flip on EVERY request
for (let u = 0; u < users.length; u++) {
const seen = new Set();
for (let r = 0; r < 20; r++) seen.add(rnd() < 0.05 ? "v2" : "v1");
if (seen.size === 2) mixed++;
}
console.log("per-request random, 20 requests each: " + mixed + " users saw BOTH versions");আউটপুট:
5% rollout : 497 of 10000 users on v2 (4.97%)
u455407699 bucket 9625 -> 5 requests: v1 v1 v1 v1 v1
u1731545 bucket 304 -> 5 requests: v2 v2 v2 v2 v2
20% rollout: 2027 users on v2; of the first 497, still on v2: 497
per-request random, 20 requests each: 6461 users saw BOTH versions
১০,০০০ জনের মধ্যে 497 জন v2-তে — ঠিক ৫০০ নয়, কারণ hash এলোমেলোর মতো ছড়ায়; প্রত্যাশিত বিচ্যুতি প্রায় √(১০,০০০ × ০.০৫ × ০.৯৫) ≈ ২২, তাই ৪৯৭ একদম স্বাভাবিক। পরের দুটি লাইন stickiness: u455407699-এর bucket 9625, পাঁচবারই v1; u1731545-এর bucket 304, পাঁচবারই v2 — কোনো counter বা table মনে রাখতে হয়নি, শুধু একই হিসাব। ২০%-এ 2027 জন, আর আগের ৪৯৭ জনের 497 জনই এখনো v2-তে — বাড়ানোর সময় কেউ পিছিয়ে যাননি। শেষ লাইনে তুলনা: প্রতিটি অনুরোধে নতুন লটারি হলে ২০টি অনুরোধের মধ্যেই 6461 জন দুটি সংস্করণই দেখেছেন; সূত্রে প্রত্যাশা ১ − ০.৯৫২০ − ০.০৫২০ ≈ ০.৬৪২, অর্থাৎ প্রায় ৬,৪১৫ জন — simulation তার কাছাকাছি। Canary-র বাকি অর্ধেক হলো মাপা: v1 আর v2-এর error rate আর p99 পাশাপাশি দেখে সমস্যা হলে নিজে থেকে ফিরিয়ে আনা — পর্ব ৫০ আর পর্ব ৫৩ (Deployment আর release)-এ।
Gateway শুধু অনুরোধের পথ বদলায়। v1 আর v2 একই database-এ লিখলে ডেটার গঠন দুই সংস্করণের কাছেই বোধগম্য থাকতে হবে — schema-র এই বিবর্তন পর্ব ১২-এর বিষয়।
৮ · Gateway-এর নিজের ঝুঁকি — SPOF, bottleneck, latency আর config drift
Single point of failure আর bottleneck
সব অনুরোধ gateway দিয়ে যায় — তাই gateway বন্ধ মানে সব বন্ধ (single point of failure, পর্ব ৫), আর gateway ধীর মানে সব ধীর — bottleneck. প্রতিকারগুলো:
- কয়েকটি instance, কয়েকটি zone-এ: সামনে একটি সরল L4 load balancer বা anycast — একই IP কয়েক জায়গা থেকে ঘোষণা (Computer Networks কোর্সের BGP পর্ব)। L4 স্তর HTTP পড়ে না বলে দ্রুত আর কম ভাঙে; তারও redundancy লাগে (virtual IP, পর্ব ৫)।
- Instance stateless: session নিজের কাছে নয়; quota counter ভাগ করা store-এ (পর্ব ৩০, ৪৯), বা প্রতিটি instance-এ সীমার ভাগ (তিনটি হলে মোটামুটি সীমা ÷ ৩)।
- N+1 সামর্থ্য: একটি instance হারালেও বাকিরা পুরো ভার নিতে পারে; autoscaling-এ নতুন instance আসতে সময় লাগে (পর্ব ১৩)।
- ভিড়ে নিজেকে বাঁচানো: সামর্থ্যের বেশি অনুরোধে দ্রুত
503— load shedding (পর্ব ১৮)।
একটি instance পড়ে গেলে কী ঘটে, সময় ধরে দেখুন। ধরা যাক: মোট ৩,০০০ RPS, প্রতিটি instance-এর সামর্থ্য ২,০০০ RPS.
| সময় | |
| ব্যর্থ অনুরোধ | |
| মোট ক্ষতি | |
| বাকিদের ভার |
- স্বাভাবিক অবস্থা। মোট ৩,০০০ RPS (প্রতি সেকেন্ডে অনুরোধ) তিনটি gateway-তে সমান ভাগ: প্রতিটিতে ১,০০০, অথচ প্রতিটি ২,০০০ পর্যন্ত পারে — ব্যবহার ৫০%. LB প্রতি ২ সেকেন্ডে প্রতিটিকে check পাঠায়; পরপর ৩টি ব্যর্থ হলে বাদ, পরপর ২টি সফল হলে ফেরত (HAProxy-র default
inter 2s fall 3 rise 2-এর মতো)। - t = ০: G2 crash করল। G2-তে চলমান অনুরোধগুলোর সংযোগ ভেঙে গেল। LB এখনো জানে না — তার শেষ check সফল ছিল — তাই নতুন সংযোগের এক-তৃতীয়াংশ এখনো G2-এর দিকেই পাঠাচ্ছে।
- t = ২, ৪, ৬ সেকেন্ড: পরপর তিনটি check ব্যর্থ। তৃতীয়টির পর G2 বাদ। এই প্রায় ৬ সেকেন্ডে প্রতি সেকেন্ডে ≈ ১,০০০টি নতুন অনুরোধ ব্যর্থ — মোট ≈ ৬,০০০, যদি কেউ আবার চেষ্টা না করে। কমানোর উপায়: LB সংযোগ-ব্যর্থতায় অন্য instance-এ পাঠাক, client শুধু idempotent অনুরোধ নিয়ম মেনে আবার পাঠাক (পর্ব ৪৮), আর check-এর ব্যবধান ছোট হোক — যদিও খুব ছোট হলে সাময়িক ধীরতাকেও মৃত্যু ভেবে ভুল হয়।
- t = ৬ সেকেন্ড থেকে: দুজনে সামলাচ্ছে। G1 আর G3 এখন ১,৫০০ RPS করে, ব্যবহার ৭৫% — সামর্থ্যের মধ্যে, কারণ নকশাটি N+1. মাত্র দুটি instance থাকলে বাকি একজনের ঘাড়ে পড়ত ৩,০০০ RPS, সামর্থ্যের দেড় গুণ — সেটিও ধসে পড়ত — cascading failure. Stateless বলে কোনো session হারায়নি; হারাল শুধু G2-এর local quota counter.
- t = ৩০ থেকে ৩৬ সেকেন্ড: নতুন instance. Orchestrator (যেমন Kubernetes) ধরা যাক ৩০ সেকেন্ডে নতুন G4 চালু করল, প্রায় ৩২ সেকেন্ডে সে প্রস্তুত। LB-র check ৩৪ আর ৩৬ সেকেন্ডে সফল — পরপর ২টি — তাই ৩৬ সেকেন্ড থেকে G4 traffic পায়। একই config আর একই image থেকে বানানো, তাই আচরণও G1-এর মতো।
- আবার স্বাভাবিক। তিনটি instance, প্রতিটি ১,০০০ RPS, ৫০%. শিক্ষা: instance stateless আর হুবহু একই; N+1 সামর্থ্য; টের পাওয়ার সময় (এখানে ≈ ৬ সেকেন্ড) কমানো বা retry দিয়ে ঢাকা; আর পরিকল্পিত deploy-এ connection draining — আগে LB থেকে সরিয়ে চলমান অনুরোধ শেষ হতে দেওয়া।
প্রতিটি hop-এর দাম
অংশ ৬-এ ভেতরের একটি proxy-hop ধরা হয়েছিল ১.৫ ms, BFF ৩.৫ ms — mobile-এর ১৫০ ms RTT-এর পাশে সামান্য। কিন্তু স্তর জমতে থাকে: CDN → WAF → L4 LB → gateway → BFF → প্রতিটি ভেতরের call-এ দুই পাশে দুটি sidecar. ভেতরে ২০টি call পরপর হলে, প্রতিটিতে দুটি sidecar × ০.৫ ms ধরলেই ২০ ms যোগ হয়; আর p99-এ প্রতিটি proxy নিজের ধীর মুহূর্ত (garbage collection, সারিতে অপেক্ষা) যোগ করে। নিয়ম: প্রতিটি স্তরের পক্ষে একটি নির্দিষ্ট কারণ চাই, আর latency budget-এ (পর্ব ৪) প্রতিটি hop-এর জায়গা।
Config drift — একই gateway, ভিন্ন আচরণ
ছয়টি instance, একটিতে রাত ২টায় হাতে করা “জরুরি” সংশোধন, আরেকটিতে পুরোনো plugin — এখন ফল নির্ভর করে অনুরোধটি কোন instance-এ পড়ল তার ওপর। এর নাম configuration drift; এমন বাগ খোঁজা খুব কঠিন, কারণ বেশিরভাগ চেষ্টায় সমস্যা দেখা দেয় না। প্রতিকার: config git-এ; প্রতিটি পরিবর্তন review আর স্বয়ংক্রিয় পরীক্ষা পেরিয়ে একই artifact হিসেবে সব instance-এ; হাতে বদলানো বন্ধ; চলমান config-এর hash মিলিয়ে drift ধরা; আর config-ও ধাপে ধাপে ছড়ানো। একটি ভুল config একসাথে সব instance-এ গেলে redundancy কোনো কাজে আসে না — এটিই পর্ব ৫-এর correlated failure.
Edge gateway, internal gateway আর service mesh
বাইরে থেকে ভেতরে আসা traffic-কে বলে north-south, ভেতরের সার্ভিস থেকে সার্ভিসে east-west. Proxy বসে তিন জায়গায়:
| Edge gateway | Internal gateway | Service mesh sidecar | |
|---|---|---|---|
| কোথায় বসে | ইন্টারনেট আর সেবার সীমানায় | একটি domain বা দলের সার্ভিসগুলোর সামনে, ভেতরে | প্রতিটি সার্ভিস-কপির পাশে, একই host বা pod-এ |
| কোন traffic | north-south | ভেতরের, domain-এর সীমা পেরোনো | east-west, প্রতিটি call |
| প্রধান কাজ | TLS, বাইরের auth, WAF, quota, public version | domain-এর API প্রকাশ, অনুবাদ | mTLS, retry, timeout, telemetry |
| কে চালায় | platform বা edge দল | domain দল বা platform দল | platform দল; control plane থেকে নিয়ম আসে |
| উদাহরণ | Nginx, Envoy, Kong, managed gateway | একই ধরনের software, ভেতরে | Istio-তে Envoy, Linkerd |
Sidecar-গুলো মিলে data plane, যারা আসল অনুরোধ বহন করে; control plane তাদের নিয়ম পাঠায়। Service mesh আর service discovery — পর্ব ৪৭ (Monolith বনাম microservices)-এ; Kubernetes-এর Ingress আর mesh-এর নেটওয়ার্ক-দিক Cloud আর container নেটওয়ার্কিং পর্বে।
আরও গভীরে: Google-এর Maglev প্রবন্ধ (NSDI ২০১৬) একটি software L4 load balancer-এর নকশা দেখায়, যা সাধারণ সার্ভারে চলে আর consistent hashing-এর একটি রূপ দিয়ে একই সংযোগকে একই backend-এ রাখে, এমনকি LB-যন্ত্র বদলালেও (পর্ব ১৬)। Envoy-এর xDS API দিয়ে control plane চলমান proxy-র route আর certificate বদলাতে পারে restart ছাড়াই — হাজারো proxy কেন্দ্র থেকে চালানোর ভিত্তি। আর Kubernetes-এর Gateway API (v1.0, ২০২৩) Ingress-এর উত্তরসূরি হিসেবে gateway আর route-কে আলাদা resource-এ ভাগ করে, যাতে platform দল gateway চালায় আর সার্ভিস-দল নিজের route লেখে।
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Forward আর reverse proxy-র পার্থক্য? — কার পক্ষে কাজ করে (client না server), কে বসায়, আর কে লুকানো থাকে।
- API gateway আর load balancer কি একই? — LB মূলত ভার ভাগ করে (L4 বা L7); gateway L7-এ API-নীতি প্রয়োগ করে — auth, quota, transformation, version. একই software দুটোই পারে।
- Gateway single point of failure হলে? — কয়েকটি stateless instance, কয়েকটি zone, সামনে L4 LB বা anycast, N+1 সামর্থ্য, config-এর ধাপে ধাপে rollout.
- তিনটি instance, প্রতিটিতে local counter, সীমা মিনিটে ১০০ — একজন client আসলে কত পান? — সমান ভাগ হলে প্রায় ৩০০; সমাধান ভাগ করা store, বা প্রতিটিতে সীমার এক-তৃতীয়াংশ (পর্ব ৪৯)।
- BFF কখন, কখন নয়? — client-ভেদে চাহিদা খুব আলাদা আর আলাদা দল থাকলে হ্যাঁ; একটিমাত্র client বা ছোট দলে এটি শুধু বাড়তি hop আর duplication.
৯ · AI-এর যুগে gateway-নকশা যাচাই
“Scalable architecture” চাইলে AI প্রায় নিশ্চিতভাবে একটি API gateway, একটি service mesh আর তিন-চারটি নতুন বাক্স বসিয়ে দেয়। এই বিষয়ে কিছু ভুল AI প্রায় নিয়ম করে করে:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| “API gateway, service mesh, Kafka আর Redis যোগ করুন” | ছোট সেবায় প্রতিটি নতুন স্তর latency, খরচ আর পরিচালনার বোঝা — over-engineering | প্রতিটি বাক্সের পক্ষে নির্দিষ্ট সমস্যা আর সংখ্যা চান |
| ছাড়, অনুমতি বা প্রবাহের নিয়ম gateway-এর plugin-এ | smart gateway; object-level অনুমতি gateway জানতেই পারে না | অংশ ৫-এর টেবিল ধরে প্রতিটি দায়িত্ব যাচাই |
| পণ্যের সীমা আর দাম আত্মবিশ্বাসের সাথে লেখা | managed gateway-এর সীমা আর দাম পণ্য, অঞ্চল ও সময়ভেদে বদলায়; AI প্রায়ই পুরোনো বা বানানো সংখ্যা দেয় | অফিসিয়াল ডকুমেন্টেশন |
| Nginx config-এ পুরোনো বা ভুল লাইন | listen … http2 আর http2 on মিশিয়ে ফেলা, keepalive-এ proxy_http_version 1.1 বাদ, limit_req নিজে থেকে 429 দেয় ভাবা, proxy_read_timeout-কে মোট সময় ভাবা | নিজের সংস্করণের ডকুমেন্টেশন, nginx -t, আর পরীক্ষামূলক traffic |
| Gateway একটিমাত্র যন্ত্র; local counter-কে সার্বিক সীমা ধরা | SPOF; instance বাড়লে সীমাও চুপচাপ বাড়ে | instance সংখ্যা আর counter কোথায় থাকে, জিজ্ঞেস করুন |
| Latency-র ভুল অঙ্ক | parallel call-এর সময় যোগ করে, বা mobile RTT বাদ দিয়ে BFF-এর লাভ দেখায় | অংশ ৬-এর calculator-এ নিজের সংখ্যা বসিয়ে আবার হিসাব |
| “Gateway token দেখেছে, সার্ভিসে যাচাই লাগবে না” | header জাল আর BOLA-র ঝুঁকি | ভাবনার প্রশ্ন ০২ |
AI-কে কাজে লাগান নকশার সমালোচক হিসেবে — সংখ্যা আর সীমাবদ্ধতা নিজে দিন, অনুমান আর ঝুঁকি আলাদা করে চান:
AI-এর দেওয়া gateway-নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Apache তো ছিলই — তবু তার সামনে Nginx বসানো জনপ্রিয় হলো কেন? Reverse proxy-র উত্থানের পেছনে কোন সমস্যা ছিল?
১৯৯৯ সালে Dan Kegel “C10K problem” নামের একটি লেখায় প্রশ্ন তোলেন: একটি সার্ভার কীভাবে একসাথে দশ হাজার client সামলাবে? তখনকার প্রচলিত নকশা ছিল প্রতিটি সংযোগের জন্য একটি process বা thread — Apache-এর prefork মডেল যেমন। দশ হাজার সংযোগ মানে দশ হাজার process, প্রত্যেকের নিজের memory, আর অদলবদলে CPU-র বড় অংশ নষ্ট (একসাথে অনেক client পর্ব)।
সমস্যাটি ধীর client-এ আরও প্রকট। অ্যাপের কাজ হয়তো ২০ ms-এ শেষ, কিন্তু ধীর link-এর ব্যবহারকারীর কাছে উত্তর পৌঁছাতে কয়েক সেকেন্ড — পুরো সময় একটি ভারী process, যার ভেতরে PHP বা Ruby interpreter লোড করা, শুধু byte ঠেলছে। অংশ ২-এর Little-এর হিসাব এখানেই খাটে।
Igor Sysoev ২০০২ সালে একটি ব্যস্ত রুশ ওয়েবসাইটের ভিড় সামলাতে Nginx লিখতে শুরু করেন, প্রকাশ্যে আসে ২০০৪ সালে। মূল কৌশল event-driven নকশা: অল্প কয়েকটি worker process, প্রতিটি একটি event loop-এ হাজারো সংযোগ একসাথে দেখে, আর যে সংযোগে ডেটা তৈরি শুধু তাকেই সেবা দেয় (Event-driven সার্ভার পর্ব)। একটি অলস সংযোগের দাম তখন কয়েক KB memory, একটি পুরো process নয়।
তাই জনপ্রিয় হলো জোড়া নকশা: সামনে Nginx — হাজারো ধীর সংযোগ, TLS আর static file সস্তায় সামলায়, অনুরোধ জমিয়ে দ্রুত ভেতরে পাঠায়; পেছনে অল্প কয়েকটি ভারী app process, যারা শুধু আসল কাজ করে। কিছু app server এটিকে শর্তই করে দিয়েছিল — যেমন Ruby-র Unicorn-এর ডকুমেন্টেশন বলে, এটি শুধু দ্রুত, কম-latency-র client-এর জন্য নকশা করা; সামনে Nginx-এর মতো buffering proxy রাখতে হবে।
মূল উপলব্ধি: reverse proxy শুধু “সুবিধার” স্তর নয় — এটি দুই রকম কাজকে আলাদা করে: অসংখ্য অলস, ধীর সংযোগ ধরে রাখা (event loop-এ সস্তা) আর ভারী গণনা (process বা thread-এ দামি)। প্রতিটি কাজ তার উপযুক্ত যন্ত্রে।
প্র ০২Gateway token যাচাই করে দিয়েছে — তাহলে ভেতরের সার্ভিস কি চোখ বুজে X-User-Id header বিশ্বাস করবে?
সরাসরি “হ্যাঁ” বলা বিপজ্জনক, কারণ বিশ্বাসটি তিনটি অনুমানের ওপর দাঁড়িয়ে: (১) সার্ভিসে পৌঁছানোর একমাত্র পথ gateway; (২) gateway বাইরে থেকে আসা X-User-Id মুছে নিজের মান বসায়; (৩) ভেতরের নেটওয়ার্কে কেউ নিজে অনুরোধ বানাতে পারে না। কোনো একটি ভাঙলেই যে কেউ header-এ অন্যের ID লিখে তার হয়ে কাজ করাতে পারে।
পুরোনো নকশা ছিল “দুর্গ আর পরিখা” — বাইরের দেয়াল শক্ত, ভেতরে সবাই বিশ্বস্ত। Google ২০১৪ সালে ;login: পত্রিকায় BeyondCorp নামের নকশা প্রকাশ করে, যেখানে কোম্পানির নেটওয়ার্কের ভেতরে থাকা মানেই বিশ্বাস নয়; প্রতিটি অনুরোধে যন্ত্র আর ব্যবহারকারীর পরিচয় যাচাই হয়। এই ধারণার সাধারণ নাম zero trust.
দ্বিতীয় সমস্যা আরও সূক্ষ্ম: gateway জানে “কে” (u-1029), কিন্তু জানে না “এই অর্ডার কার”. OWASP-এর API Security Top 10-এর ২০১৯ আর ২০২৩ — দুই তালিকাতেই এক নম্বরে BOLA: সার্ভিস যাচাই করে না যে অনুরোধকারী সেই নির্দিষ্ট বস্তুর অধিকারী কি না। Gateway যত ভালোই হোক, এটি ঠেকাতে পারে শুধু ডেটার মালিক সার্ভিস।
প্রতিরক্ষার স্তরগুলো তাই: সার্ভিসকে এমন নেটওয়ার্কে রাখা, যেখানে শুধু gateway পৌঁছাতে পারে; gateway আর সার্ভিসের মাঝে mTLS, যাতে সার্ভিস নিশ্চিত হয় অনুরোধটি সত্যিই gateway থেকে; plain header-এর বদলে স্বাক্ষর করা পরিচয় — মূল token, বা gateway-এর দেওয়া স্বল্পমেয়াদি ভেতরের token — যা সার্ভিস নিজে যাচাই করতে পারে; আর প্রতিটি সার্ভিসে object-level অনুমতির যাচাই। একই যুক্তি X-Forwarded-For-এ: IP-ভিত্তিক নিয়ম মানবে শুধু চেনা proxy-র লেখা মান। বিস্তারিত পর্ব ৫২ (নকশায় নিরাপত্তা)-এ।
মূল উপলব্ধি: gateway প্রথম পাহারাদার, শেষ নয়। Defense in depth মানে প্রতিটি স্তর ধরে নেয়, আগের স্তর কোনো দিন ভুল করতে পারে।
প্র ০৩ESB থেকে “smart gateway” — কেন্দ্রীয় বাক্সে বুদ্ধি জমানোর ভুল বারবার ফিরে আসে কেন?
২০০০-এর দশকে service-oriented architecture (SOA)-এর যুগে বড় প্রতিষ্ঠানগুলো সব সিস্টেম জুড়তে একটি কেন্দ্রীয় ESB বসাত। শুরুতে কাজ ছিল routing আর বার্তার ফরম্যাট অনুবাদ। ধীরে ধীরে সেখানে ঢুকল নিয়ম — “এই গ্রাহকের অর্ডার হলে আগে ক্রেডিট যাচাই, তারপর গুদামে পাঠানো” — অর্থাৎ ব্যবসার প্রবাহ। ফল: প্রতিটি পরিবর্তনে ESB দলের অনুমোদন, আর তার deploy সবচেয়ে ভয়ের।
২০১৪ সালে James Lewis ও Martin Fowler-এর microservices-বিষয়ক বহুল-পঠিত প্রবন্ধ এর বিপরীত নীতি সামনে আনে: “smart endpoints and dumb pipes” — বুদ্ধি থাকবে সার্ভিসে, মাঝের পাইপ থাকবে সরল। অথচ কয়েক বছরের মধ্যেই API gateway-তে একই ঘটনা দেখা দিল; ThoughtWorks-এর Technology Radar ২০১৫ সালের দিকে “overambitious API gateways” নিয়ে সতর্ক করে — gateway-তে transformation আর orchestration জমিয়ে তাকে নতুন ESB বানানো।
কেন ফিরে আসে? কারণ gateway-তে যোগ করাই আজ সবচেয়ে সহজ: সব অনুরোধ সেখান দিয়েই যায়, একটি plugin লিখলেই সব client পেয়ে যায়, কোনো সার্ভিস-দলের সাথে সমন্বয় লাগে না। খরচ আসে পরে — তখন gateway-এর config-ই এমন কোড, যার পরীক্ষা নেই, মালিক অস্পষ্ট, আর একটি ভুলে সব API একসাথে ভাঙে। Melvin Conway-এর পর্যবেক্ষণও (১৯৬৮) এখানে খাটে: সিস্টেমের গঠন প্রতিষ্ঠানের যোগাযোগ-কাঠামোর মতো হয়ে যায় — একটি কেন্দ্রীয় দল থাকলে একটি কেন্দ্রীয় বুদ্ধিমান বাক্স গড়ে ওঠে।
প্রতিকারও তাই কাঠামোগত: platform দলের মালিকানায় শুধু cross-cutting plugin (auth, quota, log); route আর সার্ভিস-নির্দিষ্ট config সার্ভিস-দলগুলো নিজেরাই git-এ বদলায়, সীমিত নিয়মের ভেতরে; client-নির্দিষ্ট জোড়া লাগানো BFF-এ, যার মালিক client দল; আর ব্যবসার প্রবাহ সার্ভিসে বা workflow engine-এ (পর্ব ৩৭)।
মূল উপলব্ধি: কেন্দ্রীয় বাক্সে বুদ্ধি জমানো আজ সময় বাঁচায়, কাল সবাইকে সেই বাক্সের সারিতে দাঁড় করায়। প্রশ্ন করুন: “এই নিয়ম বদলাতে কোন দলকে deploy করতে হবে?”
প্র ০৪সব client-এর জন্য একটি সাধারণ API, নাকি প্রতিটির জন্য আলাদা BFF — সিদ্ধান্ত কীভাবে নেবেন?
প্রশ্নটি নতুন নয়। ২০১২ সালের দিকে Netflix-এর engineering blog-এ প্রকাশিত লেখা অনুযায়ী, তারা দেখছিল বহু ধরনের যন্ত্র (টিভি, গেম কনসোল, ফোন) একই সাধারণ REST API ডাকছে, আর প্রতিটি পর্দা আঁকতে অনেকগুলো call লাগছে। তাদের সমাধান ছিল সার্ভারের ভেতরে যন্ত্র-নির্দিষ্ট “adapter” কোড, যা যন্ত্রের দলগুলোই লিখত — একটি অনুরোধে পুরো পর্দার ডেটা। ২০১৫ সালের দিকে Sam Newman এই ধরনের নকশাকে “Backends For Frontends” নামে সুসংহত করেন।
BFF-এর পক্ষে লক্ষণ: (১) client-গুলোর চাহিদা সত্যিই আলাদা — পর্দার আকার, link-এর গতি, offline থাকা; (২) প্রতিটি client-এর আলাদা দল আছে, যারা নিজের BFF নিজে বদলাতে পারে; (৩) একটি পর্দার জন্য অনেক বা পরস্পর-নির্ভর call লাগে; (৪) পুরোনো অ্যাপ-সংস্করণ মাসের পর মাস টিকে থাকে, তাদের জন্য উত্তর মানিয়ে দিতে হয়।
বিপক্ষে লক্ষণ: client একটিই, বা সবাই প্রায় একই ডেটা চায়; দল ছোট — তিনটি BFF মানে তিনটি deploy, তিনটি on-call; আর duplication — login-এর পর profile আনা বা ত্রুটির বার্তা সাজানোর মতো কাজ প্রতিটি BFF-এ আলাদা লিখলে সময়ের সাথে সেগুলো আলাদা হয়ে যায়। Newman-এর লেখায় পরামর্শটি মোটামুটি এমন: একটি ব্যবহারকারী-অভিজ্ঞতার জন্য একটি BFF, প্রতিটি যন্ত্র-মডেলের জন্য নয়; ভাগ করা কাজ যাবে ভাগ করা library বা নিচের সার্ভিসে।
আরেকটি পথ GraphQL — client নিজে বলে দেয় কোন field লাগবে, একটি endpoint থেকে জোড়া ডেটা আসে; অনেক দল BFF-এর ভেতরেই GraphQL চালায়। এর নিজস্ব trade-off, যেমন cache করা আর জটিল query-র খরচ সীমিত রাখা, পর্ব ৯-এ।
মূল উপলব্ধি: BFF মূলত একটি প্রতিষ্ঠানগত সিদ্ধান্ত — client-ভিত্তিক দলগুলোকে নিজের গতিতে চলতে দেওয়া। দল আর client-এর বৈচিত্র্য না থাকলে এটি শুধু বাড়তি hop.
প্র ০৫Edge proxy থেকে service mesh — proxy কি সব জায়গায় ছড়িয়ে পড়ছে, আর তার দাম কত?
Microservice বাড়ার সাথে সাথে ভেতরের, সার্ভিস থেকে সার্ভিসে যাওয়া traffic বাইরের traffic-কে ছাড়িয়ে যায়। প্রতিটি সার্ভিসকে retry, timeout, mTLS আর metric নিজের ভাষার library-তে লিখতে হলে Java, Go, Python-এর জন্য আলাদা বাস্তবায়ন, আলাদা বাগ।
Lyft-এর প্রকৌশলীরা এর উত্তরে Envoy বানান আর ২০১৬ সালে open source করেন: একটি proxy, যা প্রতিটি সার্ভিসের পাশে বসে, আর সার্ভিস ভাবে সে সাধারণ localhost-এ কথা বলছে। প্রায় একই সময়ে Buoyant-এর Linkerd এই ধরনের নকশাকে “service mesh” নামে জনপ্রিয় করে। ২০১৭ সালে Google, IBM আর Lyft মিলে Istio ঘোষণা করে — Envoy-কে data plane রেখে ওপরে একটি control plane, যা হাজারো sidecar-কে নিয়ম পাঠায়।
দাম কোথায়? প্রতিটি call এখন দুটি বাড়তি proxy পার হয় — পাঠানোর দিকের আর গ্রহণের দিকের sidecar. p50-এ হয়তো এক ms-এর ভগ্নাংশ, কিন্তু একটি ব্যবহারকারী-অনুরোধ ভেতরে ২০টি call পরপর করলে যোগফল চোখে পড়ার মতো, আর p99-এ প্রতিটি proxy-র নিজের ধীর মুহূর্ত যোগ হয়। প্রতিটি pod-এ বাড়তি memory আর CPU, আর হাজারো sidecar-এর সংস্করণ হালনাগাদের ঝামেলা। এই খরচ কমাতে সাম্প্রতিক কিছু mesh-নকশা প্রতিটি pod-এ sidecar না রেখে node-স্তরের ভাগ করা proxy ব্যবহার করে — খুঁটিনাটি সংস্করণভেদে দ্রুত বদলাচ্ছে।
তাহলে কোনটা কোথায়? Edge gateway থাকে ইন্টারনেটের সীমানায় — বাইরের client-এর পরিচয়, quota, public API-এর সংস্করণ। Mesh থাকে ভেতরে — সার্ভিস থেকে সার্ভিসের পরিচয় (mTLS), retry, timeout, telemetry. দুটি একই software (যেমন Envoy) দিয়েও হতে পারে, কিন্তু দায়িত্ব আলাদা। পর্ব ৪৭-এ service mesh-এর আরও বিস্তার।
মূল উপলব্ধি: প্রতিটি proxy-স্তর কিছু কাজ সার্ভিস থেকে সরিয়ে নেয়, বিনিময়ে নেয় latency, সম্পদ আর পরিচালনার খরচ। “আরেকটা proxy বসাই” বলার আগে জিজ্ঞেস করুন — কোন সমস্যা, কত দাম, আর কে চালাবে।
অনুশীলন
আগে নিজে খাতায় সমাধান করুন, তারপর উত্তর মিলিয়ে নিন।
-
Routing-এর নিয়ম লিখুন: একটি কাল্পনিক অনলাইন বই-দোকানের দুটি host:
api.boi.exampleআরadmin.boi.example. শর্ত: (ক) বইয়ের তালিকা/v1/books/সবার জন্য, token ছাড়া, শুধু GET; (খ)/v1/cart/আর/v1/orders/-এ token লাগবে, GET ও POST; DELETE শুধু cart-এ; (গ)/v2/orders/যাবে নতুন orders-v2-তে; (ঘ) admin শুধু অফিসের নেটওয়ার্ক থেকে, token-সহ; (ঙ)/v1/orders/export/একটি ভারী রিপোর্ট, আলাদা report-svc-তে। বাকি সব404. Route table লিখুন, তারপর বলুনGET /v1/orders/export/2026,DELETE /v1/orders/7আরGET /v1/ordersকোথায় যাবে।host prefix service methods token অন্য নিয়ম api.boi.example /v1/books/ books-v1 GET না api.boi.example /v1/cart/ cart-v1 GET POST DELETE হ্যাঁ api.boi.example /v1/orders/ orders-v1 GET POST হ্যাঁ api.boi.example /v1/orders/export/ report-svc GET হ্যাঁ কম quota, দীর্ঘ timeout api.boi.example /v2/orders/ orders-v2 GET POST হ্যাঁ admin.boi.example / admin-ui GET POST হ্যাঁ অফিসের IP ছাড়া 403 (অন্য সব) 404GET /v1/orders/export/2026: দুটি prefix মেলে —/v1/orders/আর/v1/orders/export/; লম্বাটি জেতে, তাই report-svc.DELETE /v1/orders/7: route orders-v1, কিন্তু DELETE অনুমোদিত নয় —405.GET /v1/orders: শেষে/নেই, কোনো prefix মেলে না —404(/v1/-এর মতো সাধারণ route থাকলে ভুল সার্ভিসে চলে যেত)। ভালো সমাধান: exact route বা/v1/orders/-এ redirect, অথবা segment ধরে মেলানো। Admin-এ token না থাকলে401, token ঠিক কিন্তু অফিসের বাইরের ঠিকানা হলে403. ভারী রিপোর্টে দীর্ঘ synchronous timeout-এর চেয়ে background job (পর্ব ৩৭) ভালো; আর gateway-এর timeout সার্ভিসের নিজের সময়সীমার চেয়ে একটু বেশি রাখতে হয়, নইলে সার্ভিস কাজ শেষের আগেই gateway504দেবে। -
Gateway না সার্ভিস — নকশার সমালোচনা: এই আটটি দায়িত্ব কোথায় — gateway, BFF না সার্ভিসে? (১) JWT-এর স্বাক্ষর ও মেয়াদ যাচাই; (২) “অর্ডার ৪২ কি এই ব্যবহারকারীর?”; (৩) প্রতি API key-তে মিনিটে ৬০০ অনুরোধ; (৪) “দিনে ৫ বারের বেশি টাকা তোলা যাবে না”; (৫) ঈদ-অফারে ১০% ছাড়; (৬) ১ MB-এর বড় body বাতিল; (৭) মোবাইল হোম স্ক্রিনের জন্য তিনটি উত্তর জোড়া; (৮) request ID আর access log. তারপর এই প্রস্তাবের সমালোচনা করুন: “ছাড়ের হিসাব gateway-এর একটি script-এ রাখি, তাহলে orders সার্ভিস বদলাতে হবে না।”
Gateway: (১) ভুয়া token আগেই থামে; (৩) শুধু হারের সীমা; (৬) মোটা দাগের সুরক্ষা; (৮) gateway ID বানায় ও log লেখে, সার্ভিসগুলো ID এগিয়ে দেয়। BFF: (৭) client-নির্দিষ্ট জোড়া, মালিক মোবাইল দল। সার্ভিস: (২) মালিকানা জানে শুধু orders সার্ভিস, বাদ পড়লে BOLA; (৪) ব্যবসার নিয়ম — স্থায়ী গণনা, audit আর ব্যতিক্রম; (৫) দামের যুক্তি।
প্রস্তাবের সমালোচনা: (ক) ছাড় এক জায়গায় (gateway), অর্ডারের মোট দাম আরেক জায়গায় — দুটি মিলবে কে নিশ্চিত করবে, বিশেষ করে partner অন্য পথে এলে? (খ) প্রতিটি অফারে gateway deploy — সব API-র ঝুঁকি একটি ব্যবসায়িক পরিবর্তনের ঘাড়ে; (গ) gateway-script-এর test, version আর মালিক সাধারণত দুর্বল; (ঘ) ছাড়ের হিসাব audit-যোগ্য হতে হয়, যার রেকর্ড রাখে orders সার্ভিস। সিদ্ধান্ত: ছাড় orders বা আলাদা pricing সার্ভিসে; gateway শুধু অনুরোধ পৌঁছে দেবে।
-
Hop-এর হিসাব: অংশ ৬-এর অনুমানগুলো ধরুন (mobile RTT ১৫০, CDN ১, edge থেকে gateway ৩০, gateway ১, ভেতরের RTT ০.৫, BFF ৩ ms; profile ২০, feed ৪৫, notify ১৫ ms ধরে)। এবার feed আনতে আগে profile লাগে, notify স্বাধীন। (ক) BFF ছাড়া অ্যাপ: আগে profile, তারপর feed আর notify একসাথে — মোট সময়? (খ) BFF একই কাজ ভেতরে করলে? (গ) নিরাপত্তা দল gateway-এর আগে একটি WAF বসাল (কাজ ২ ms + ভেতরের RTT ০.৫ ms), আর ভেতরের প্রতিটি call-এ (gateway থেকে BFF, BFF থেকে প্রতিটি সার্ভিস) দুই পাশে দুটি sidecar, প্রতিটি ০.৫ ms — (খ)-এর সময় কত হয়?
ফোন থেকে একটি call-এর পথ: ১৫০ + ১ + ৩০ + ১ + ০.৫ = ১৮২.৫ ms, তার ওপর সার্ভিসের সময়।
(ক) প্রথম trip: ১৮২.৫ + ২০ = ২০২.৫ ms. দ্বিতীয় trip-এ feed আর notify একসাথে: সবচেয়ে ধীরটি ১৮২.৫ + ৪৫ = ২২৭.৫ ms. মোট ৪৩০.০ ms — ধীর link দুবার।
(খ) BFF পর্যন্ত পথ ১৮২.৫ + ৩ = ১৮৫.৫ ms; ভেতরে profile ০.৫ + ২০ = ২০.৫; তারপর feed (৪৫.৫) আর notify (১৫.৫) একসাথে — ৪৫.৫. মোট ১৮৫.৫ + ২০.৫ + ৪৫.৫ = ২৫১.৫ ms. পরস্পর-নির্ভর call-এ BFF বাঁচাল ১৭৮.৫ ms.
(গ) WAF পথে একবার: + ২.৫ ms. সবচেয়ে ধীর পথে ভেতরের call তিনটি — gateway থেকে BFF, BFF থেকে profile, তারপর BFF থেকে feed (notify সমান্তরালে, ধীর পথে নেই); প্রতিটিতে ২ × ০.৫ = ১ ms, মোট + ৩ ms. নতুন সময় ২৫১.৫ + ২.৫ + ৩ = ২৫৭.০ ms — মাত্র ২% বেশি। শিক্ষা: p50-এ mobile RTT-এর পাশে hop সস্তা; কিন্তু ভেতরে পরপর ২০টি call হলে sidecar-ই যোগ করত ২০ ms.
-
আউটপুট অনুমান করুন: অংশ ৭-এর
canary_routing.js-এ তিনটি বদল আলাদা করে করলে কী আসবে — আগে অনুমান, তারপর চালিয়ে মিলিয়ে নিন। (ক)version(u, 20)-এর দুটি জায়গায়20-এর বদলে10: কতজন v2-তে, আর প্রথম ৪৯৭ জনের কতজন রয়ে যাবেন? (খ) শুধু শেষ loop-এr < 20-এর বদলেr < 5: কতজন দুটি সংস্করণই দেখবেন? (গ) salt"home-v2:"-এর বদলে"checkout-v3:"করলে ৫%-এর নতুন দলে আগের ৪৯৭ জনের কতজন থাকবেন?(ক) অনুমান: প্রায় ১,০০০ (প্রত্যাশিত বিচ্যুতি √(১০,০০০ × ০.১ × ০.৯) = ৩০), আর আগের ৪৯৭ জনের সবাই — bucket ৫০০-এর কম হলে ১,০০০-এরও কম। চালানোর ফল (লেখাটিও বদলে নিলে):
10% rollout: 993 users on v2; of the first 497, still on v2: 497.(খ) অনুমান: ১ − ০.৯৫৫ − ০.০৫৫ ≈ ০.২২৬, অর্থাৎ প্রায় ২,২৬২ জন — তবু প্রতি চারজনে প্রায় একজন। চালানোর ফল:
per-request random, 5 requests each: 2287 users saw BOTH versions(শেষ লেখাটিও বদলে নিলে)।(গ) অনুমান: নতুন salt-এ hash নতুন করে ছড়ায়, তাই আগের দলের মোটামুটি ৫% — ২৫-এর কাছাকাছি। একটি বাড়তি লাইন দিয়ে গুনে দেখা ফল: নতুন salt-এ v2-তে ৪৮৬ জন, তাদের মধ্যে আগের দলের ২৫ জন। তাই প্রতিটি rollout-এ আলাদা salt; কিন্তু একই rollout-এর মাঝপথে salt বদলালে stickiness ভেঙে যায়।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি API gateway-এর ভেতরে একটি অনুরোধের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🔒 TLS শেষ করা আর অনুরোধকে একটি request ID দেওয়া
- 🔑 Token যাচাই — অনুরোধকারী কে
- ⏱️ সেই ব্যবহারকারীর quota যাচাই
- 🧭 Route মিলিয়ে সার্ভিসের একটি সুস্থ instance বেছে নেওয়া
- 🔧 Header বদলে অনুরোধ পাঠানো, উত্তর compress করে ফেরত
একটি অফিসের সব কর্মীর ওয়েব ব্রাউজিং একটি proxy-র মধ্য দিয়ে যায়, যা কিছু সাইট আটকায় আর কে কোথায় গেল তার লগ রাখে। এটি কোন ধরনের proxy?
- Forward proxy
- Reverse proxy
- API gateway
- BFF
একটি gateway-এর route table: /v1/ → catalog, /v1/orders/ → orders, /v1/orders/export/ → reports. নিয়ম: path সাধারণ string-prefix হিসেবে মেলানো হয়, আর সবচেয়ে লম্বা মিল জেতে। GET /v1/orders (শেষে / নেই) কোথায় যাবে?
- orders
- catalog
- reports
404— কোনো route মেলেনি
একটি উত্তর ২ MB. ব্যবহারকারীর link ১ Mbps, আর reverse proxy থেকে অ্যাপ সার্ভারের link ১ Gbps. Proxy পুরো উত্তর buffer করলে অ্যাপ সার্ভারের worker মোটামুটি কতক্ষণ এই উত্তরে আটকে থাকে?
- প্রায় ১৬ সেকেন্ড
- প্রায় ১৬ ms
- প্রায় ১.৬ সেকেন্ড
- শূন্য — proxy নিজেই উত্তর বানায়
নিচের কোন দায়িত্বটি API gateway-তে না রেখে সার্ভিসেই রাখা উচিত?
- TLS শেষ করা
- JWT-এর স্বাক্ষর আর মেয়াদ যাচাই
- প্রতি API key-তে মিনিটের অনুরোধ-সীমা
- “অর্ডার ৪২ কি এই ব্যবহারকারীর?” যাচাই
Canary-তে ব্যবহারকারীর ID-র hash দিয়ে ৫% ব্যবহারকারীকে নতুন সংস্করণে পাঠানো হয়, প্রতিটি অনুরোধে নতুন করে এলোমেলো বাছাই না করে। এর প্রধান কারণ কী?
- Hash হিসাব এলোমেলো সংখ্যার চেয়ে দ্রুত
- Hash দিলে ঠিক ৫০০ জনই নতুন সংস্করণে যান
- একই ব্যবহারকারী প্রতিবার একই সংস্করণ পান
- ব্যবহারকারী বুঝতে পারেন না তিনি কোন সংস্করণে
ফোন থেকে প্রতিটি API call-এ RTT ১২০ ms আর সার্ভারে ৩০ ms লাগে। অ্যাপ ৪টি call পরপর করে। BFF থাকলে ফোন ১টি call করে; BFF ৪টি সার্ভিস একসাথে ডাকে (প্রতিটি ৩০ ms), আর BFF-এর নিজের কাজ ও ভেতরের RTT মিলে ৫ ms. দুই ক্ষেত্রে মোট সময় মোটামুটি কত?
- ৬০০ ms থেকে ১৫৫ ms
- ৬০০ ms থেকে ২৪৫ ms
- ৪৮০ ms থেকে ১৫৫ ms
- দুই ক্ষেত্রেই ৬০০ ms
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Proxy মানে প্রতিনিধি, দুই দিকে আলাদা সংযোগ: forward proxy client-এর পক্ষে, reverse proxy সার্ভারের পক্ষে।
- Reverse proxy: TLS শেষ করা, routing, compression, cache, ধীর client buffer করা, connection pool, সীমা, গঠন লুকানো; buffering-এ ৮০০ worker থেকে ০.৮ (Little-এর সূত্র)।
- API gateway = API-র জন্য বিশেষ reverse proxy: auth, API key, quota (
429), transformation, version ও canary routing, request ID ও log. ব্যবসার নিয়ম আর object-level অনুমতি থাকে সার্ভিসে. - BFF: প্রতি client-অভিজ্ঞতায় একটি পাতলা backend; বড় লাভ ধীর link-এ কম RTT, কম byte আর আংশিক উত্তর; দাম বাড়তি hop, duplication আর মালিকানার প্রশ্ন। Canary-তে user ID-র hash দিলে ভাগ sticky.
- Gateway নিজেই ঝুঁকি: কয়েকটি stateless instance, N+1, L4 LB বা anycast; প্রতিটি hop-এর latency হিসাব; smart gateway এড়ানো; config git-এ, ধাপে ধাপে rollout — config drift নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১২ · Data format আর serialization — JSON, Protobuf, Avro আর schema evolutionপরবর্তী পর্বgateway আর সার্ভিসের মাঝে চলা ডেটার ফরম্যাট আর তার বিবর্তন।
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Data center নেটওয়ার্ক — leaf-spine আর load balancerগভীরে যানL4 আর L7 load balancer, health check আর draining-এর নেটওয়ার্ক-দিক।
- Firewall আর VPN — nftables, IPsec আর WireGuardপাশের ট্র্যাকProxy firewall, WAF, DMZ আর বাইরে যাওয়ার নিয়ম।
- Event-driven সার্ভার — select, poll আর epollপাশের ট্র্যাকNginx-এর মতো proxy কীভাবে একসাথে হাজারো সংযোগ সামলায়।