Deploy version mới cho API gateway. Một trong ba backend server restart chậm hơn dự kiến — 45 giây thay vì 10 giây. Trong 45 giây đó, load balancer vẫn gửi request đến server đang restart, 1/3 request trả 502. Error rate tăng 33%, hai server kia hoàn toàn khoẻ — nhưng load balancer không biết server thứ ba đang chết vì health check interval 30 giây và lần check cuối server vẫn healthy.

Vấn đề không phải do code hay infra sập — do cấu hình load balancer: health check quá thưa, không có connection draining khi deploy, thuật toán round-robin vô tư chia đều traffic cho server đang chết. Bài này đi qua thuật toán load balancing, cấu hình health check, và cách hệ thống hành xử khi backend thay đổi — đủ để chọn đúng algorithm, cấu hình health check hợp lý, và tránh cạm bẫy phổ biến khi vận hành.


Tại sao cần load balancing

Một server đơn lẻ có giới hạn vật lý — CPU, RAM, network bandwidth, số connection đồng thời. Khi traffic vượt khả năng xử lý của một máy, bạn có hai lựa chọn: scale vertical (mua máy mạnh hơn) hoặc scale horizontal (thêm nhiều máy). Scale vertical có trần — máy mạnh nhất vẫn có giới hạn, và giá tăng phi tuyến (máy gấp đôi CPU không phải gấp đôi giá mà thường gấp 3-4 lần). Scale horizontal linh hoạt hơn — thêm bao nhiêu máy cũng được, nhưng cần một thứ đứng trước để chia traffic: load balancer.

Load balancer giải quyết ba bài toán cùng lúc. Thứ nhất, phân phối tải — chia request đều (hoặc theo trọng số) giữa các backend, không server nào bị quá tải trong khi server khác rảnh. Thứ hai, high availability — khi một backend chết, load balancer phát hiện và ngừng gửi traffic đến đó, user không thấy lỗi (nếu health check đúng). Thứ ba, triển khai linh hoạt — thêm server mới vào pool, rút server cũ ra để deploy, mà không cần downtime.

Nhưng load balancer cũng là single point of failure nếu không được thiết kế HA. Một load balancer chết thì toàn bộ traffic chết theo — nên production luôn cần ít nhất hai load balancer với failover (active-passive hoặc active-active). Cloud load balancer (AWS ALB/NLB, GCP Load Balancer) giải quyết vấn đề này vì chúng là managed service với HA built-in.


L4 vs L7 — hai tầng load balancing

Trước khi nói về thuật toán, cần phân biệt hai loại load balancing vì chúng quyết định những gì load balancer có thể làm.

L4 — tầng transport (TCP/UDP)

Load balancer L4 hoạt động ở tầng TCP — nó nhìn thấy IP nguồn, IP đích, port nguồn, port đích, và chọn backend dựa trên thông tin đó. Nó không hiểu nội dung HTTP — không biết URL, header, cookie, hay body.

Ưu điểm: rất nhanh vì chỉ cần xem header TCP, không cần parse HTTP. Throughput cao, latency thêm vào rất thấp (microsecond). Hoạt động với mọi protocol chạy trên TCP — HTTP, gRPC, WebSocket, database connection, SMTP. AWS NLB, HAProxy mode TCP, Kubernetes Service (ClusterIP/NodePort) là L4.

Nhược điểm: không thể route theo URL path, không thể sửa HTTP header, không thể terminate SSL (trừ khi có TLS passthrough), không thể làm sticky session theo cookie. Mọi quyết định routing chỉ dựa trên thông tin TCP.

L7 — tầng application (HTTP)

Load balancer L7 hiểu HTTP — parse request, đọc URL, header, cookie, thậm chí body. Dựa trên thông tin này, nó có thể route request đến backend khác nhau, thêm/sửa header, terminate SSL, implement sticky session bằng cookie.

Ưu điểm: routing linh hoạt — /api/* đến backend API, /static/* đến CDN origin, request có header X-Version: canary đến canary backend. Có thể terminate SSL, gắn cookie, nén response, cache response. AWS ALB, Nginx, HAProxy mode HTTP, Kubernetes Ingress là L7.

Nhược điểm: chậm hơn L4 vì phải parse HTTP. Không hoạt động với protocol non-HTTP (trừ khi có plugin đặc biệt). Tốn resource hơn vì phải duy trì HTTP state.

Quy tắc đơn giản mà mình dùng: nếu cần route theo URL, header, hoặc cookie → L7. Nếu chỉ cần chia traffic TCP thuần (database connection, non-HTTP service) → L4. Nếu cần throughput cực cao và latency cực thấp → L4. Trong thực tế, nhiều hệ thống dùng cả hai: L4 ở tầng ngoài (NLB) forward đến L7 ở tầng trong (Nginx/ALB) để được cả throughput lẫn routing linh hoạt.


Thuật toán phân phối

Round-robin — đơn giản nhất

Request được chia đều lần lượt: server A, server B, server C, rồi quay lại A. Không quan tâm server nào đang bận hay rảnh — cứ xoay vòng.

Round-robin hoạt động tốt khi tất cả backend có cùng capacity và mỗi request tốn tương đương nhau. Nhưng thực tế ít khi như vậy — có request mất 5ms, có request mất 5 giây. Server đang xử lý request nặng vẫn nhận thêm request mới đều đặn. Kết quả: server “xui” nhận toàn request nặng bị quá tải, trong khi server khác rảnh.

Dù vậy, round-robin vẫn là lựa chọn mặc định hợp lý cho hầu hết service stateless có request pattern tương đối đồng đều. Đơn giản, dễ hiểu, dễ debug. Nginx mặc định dùng round-robin, và rất nhiều production system chạy tốt với nó.

Weighted round-robin — khi server không đều

Khi backend có capacity khác nhau — server A có 8 CPU, server B có 4 CPU — round-robin thuần chia đều là lãng phí. Weighted round-robin cho phép gán trọng số: server A weight 2, server B weight 1 → A nhận gấp đôi traffic so với B.

upstream backend {
    server 10.0.0.1 weight=3;
    server 10.0.0.2 weight=2;
    server 10.0.0.3 weight=1;
}

Mình dùng weighted round-robin khi có mix hardware (canary deployment trên server nhỏ hơn, hoặc server cũ yếu hơn server mới) và khi cần drain traffic dần — giảm weight về 0 trước khi remove server, thay vì cắt đột ngột.

Least connections — thông minh hơn

Request mới được gửi đến server đang có ít connection nhất. Server đang xử lý 50 request không nhận thêm nếu server khác chỉ có 10 request. Thuật toán này tự cân bằng — server nhanh xử lý xong request sớm, connection count giảm, nhận thêm request mới.

Least connections phù hợp khi thời gian xử lý request khác nhau đáng kể — ví dụ API endpoint vừa có query nhanh (5ms) vừa có query chậm (2 giây). Round-robin có thể dồn request chậm vào một server, least connections tự điều chỉnh.

upstream backend {
    least_conn;
    server 10.0.0.1;
    server 10.0.0.2;
    server 10.0.0.3;
}

Nhược điểm: load balancer phải track connection count cho mỗi backend — overhead nhỏ nhưng có. Và “ít connection nhất” không nhất thiết nghĩa là “ít bận nhất” — một server có 5 connection nhưng mỗi connection đang xử lý query nặng có thể bận hơn server có 10 connection xử lý query nhẹ.

IP hash — gắn client vào server

Hash IP nguồn của client để chọn backend — cùng IP luôn đến cùng server. Đây là dạng sticky session đơn giản không cần cookie.

upstream backend {
    ip_hash;
    server 10.0.0.1;
    server 10.0.0.2;
    server 10.0.0.3;
}

Vấn đề: nhiều user đứng sau cùng NAT (văn phòng, mobile carrier) chia sẻ IP → dồn hết vào một server. IPv6 thì mỗi device có IP riêng nhưng client có thể dùng privacy extension đổi IP liên tục → sticky bị phá. IP hash nghe đơn giản nhưng thực tế gặp nhiều edge case — mình thường prefer cookie-based sticky nếu cần sticky session.

Consistent hashing — cho cache và stateful service

Đây là thuật toán quan trọng nhất cần hiểu khi làm việc với stateful backend — cache layer, session store, hoặc bất kỳ service nào mà “request nên đến đúng server đã xử lý trước đó”.

Vấn đề với hash thông thường: server = hash(key) % N. Khi thêm hoặc bớt server (N thay đổi), hầu hết key bị remap sang server khác — cache miss toàn bộ, thundering herd đến database. Với 3 server lên 4 server, khoảng 75% key bị remap.

Consistent hashing giải quyết điều này: mỗi server và mỗi key được hash lên một vòng tròn (hash ring). Key được gán cho server gần nhất theo chiều kim đồng hồ. Khi thêm server mới, chỉ key nằm giữa server mới và server trước đó bị remap — khoảng 1/N key thay đổi thay vì (N-1)/N. Bớt server tương tự — chỉ key của server bị remove chuyển sang server kế tiếp.

upstream backend {
    hash $request_uri consistent;
    server 10.0.0.1;
    server 10.0.0.2;
    server 10.0.0.3;
}

Mình dùng consistent hashing cho mọi cache layer phía trước database — Memcached cluster, Redis cluster (khi không dùng Redis Cluster mode), và CDN origin routing. Khi scale cache lên/xuống, chỉ một phần nhỏ cache bị miss thay vì toàn bộ.

Lưu ý thực tế: consistent hashing có thể phân phối không đều nếu số server ít — giải quyết bằng virtual nodes (mỗi server physical có nhiều điểm trên hash ring). Hầu hết implementation đã handle điều này sẵn.


Chọn thuật toán nào

Mình quyết định thuật toán dựa trên đặc tính workload, không phải sở thích cá nhân.

Service stateless, request pattern đồng đều — round-robin hoặc weighted round-robin. Đây là default cho hầu hết web API, microservice thông thường. Đừng over-engineer khi round-robin đã đủ tốt.

Service stateless, request time khác nhau nhiều (mix fast/slow endpoint) — least connections. Load balancer tự điều chỉnh, server nhanh nhận nhiều request hơn server đang bận.

Cache layer, stateful service cần affinity — consistent hashing. Đảm bảo cùng key đến cùng server, giảm cache miss khi topology thay đổi.

Service cần session affinity đơn giản — IP hash nếu client có IP ổn định, cookie-based sticky (L7) nếu cần reliable hơn. Nhưng hãy tự hỏi “có thực sự cần sticky session không?” trước khi chọn — phần lớn thời gian, thiết kế stateless tốt hơn.


Health check — phát hiện backend chết

Health check là cơ chế load balancer dùng để biết backend nào đang khoẻ, backend nào đang chết hoặc đang có vấn đề. Không có health check, load balancer vẫn gửi traffic đến server đã sập — user thấy lỗi.

Active health check

Load balancer chủ động gửi request đến backend theo interval định kỳ — thường là HTTP GET đến endpoint /health hoặc TCP connect đến port. Nếu backend không phản hồi hoặc trả status code lỗi trong N lần liên tiếp, load balancer đánh dấu backend unhealthy và ngừng gửi traffic.

upstream backend {
    server 10.0.0.1;
    server 10.0.0.2;
    server 10.0.0.3;

    # Nginx Plus / OpenResty / config tương tự
    # Check mỗi 5 giây, 3 lần fail → unhealthy, 2 lần pass → healthy lại
}

HAProxy cấu hình rõ ràng hơn:

backend api_servers
    option httpchk GET /health
    http-check expect status 200

    server s1 10.0.0.1:8080 check inter 5s fall 3 rise 2
    server s2 10.0.0.2:8080 check inter 5s fall 3 rise 2
    server s3 10.0.0.3:8080 check inter 5s fall 3 rise 2

inter 5s — check mỗi 5 giây. fall 3 — 3 lần fail liên tiếp mới đánh dấu unhealthy. rise 2 — 2 lần pass liên tiếp mới đánh dấu healthy lại. Ba tham số này cần tune cẩn thận — quá nhanh thì nhạy giả (một lần timeout vì GC pause → đánh dấu unhealthy), quá chậm thì phát hiện muộn (server chết 30 giây mới biết).

Passive health check

Load balancer không gửi request riêng — thay vào đó theo dõi response của traffic thật. Nếu backend trả nhiều 5xx hoặc timeout liên tục, load balancer đánh dấu unhealthy. Nginx gọi đây là max_fails:

upstream backend {
    server 10.0.0.1 max_fails=3 fail_timeout=30s;
    server 10.0.0.2 max_fails=3 fail_timeout=30s;
}

Ưu điểm: không tốn thêm request cho health check. Phát hiện vấn đề ở tầng application — endpoint /health có thể trả 200 nhưng endpoint /api/orders đang trả 500 vì bug; passive check phát hiện được, active check thì không (vì active chỉ check /health).

Nhược điểm: cần có traffic thực để phát hiện — nếu backend mới thêm vào pool chưa nhận traffic, passive check không biết nó healthy hay không. Và khi phát hiện, đã có request thật bị lỗi rồi.

Mình khuyên dùng cả hai: active health check để phát hiện server sập hoàn toàn (TCP refuse, process crash), passive health check để phát hiện lỗi application (bug, dependency failure). Kết hợp cả hai cho coverage tốt nhất.

Health check endpoint — /health vs /ready

Đây là phân biệt mà Kubernetes formalise nhưng áp dụng được cho bất kỳ hệ thống nào.

Liveness (/health hoặc /healthz) trả lời câu “process có đang sống không?”. Check đơn giản: process respond được, không bị deadlock. Nếu liveness fail → restart process (Kubernetes) hoặc remove khỏi pool (load balancer). Endpoint này nên rẻ và ít phụ thuộc — không query database, không gọi external service. Nếu liveness check gọi database mà database chậm, tất cả pod bị coi là dead → restart storm → cascade failure. Mình đã thấy incident này xảy ra — database latency tăng, liveness check timeout, Kubernetes restart tất cả pod, traffic dồn vào pod mới khởi động chưa warm cache, database còn nặng hơn.

Readiness (/ready hoặc /readyz) trả lời câu “process có sẵn sàng nhận traffic không?”. Check kỹ hơn: database connection OK, cache warm xong, dependency service reachable. Nếu readiness fail → ngừng gửi traffic nhưng không restart process. Khi dependency recover, readiness pass lại, traffic trở lại.

@app.get("/health")
async def health():
    return {"status": "alive"}

@app.get("/ready")
async def ready():
    try:
        await db.execute("SELECT 1")
        return {"status": "ready"}
    except Exception:
        raise HTTPException(status_code=503, detail="not ready")

Trong load balancer config, dùng readiness endpoint cho health check routing (quyết định gửi traffic hay không). Dùng liveness cho process supervisor (quyết định restart hay không). Nhầm lẫn hai cái này là nguồn gốc của nhiều incident nghiêm trọng.


Connection draining — rút server an toàn

Khi bạn cần remove backend khỏi pool — deploy version mới, maintenance, scale down — không thể cắt đột ngột. Server đang xử lý 50 request dở dang, cắt ngay → 50 request fail.

Connection draining (hay graceful shutdown) là quá trình: load balancer ngừng gửi request mới đến server, nhưng cho request đang xử lý hoàn thành trước khi remove server. Thường có timeout — nếu request không hoàn thành trong 30 giây, force close.

backend api_servers
    server s1 10.0.0.1:8080 check drain

Trong Kubernetes, khi pod bị terminate, nó nhận SIGTERM trước, có terminationGracePeriodSeconds (mặc định 30 giây) để hoàn thành request đang xử lý. Đồng thời, Kubernetes remove pod khỏi Service endpoints — load balancer (kube-proxy) ngừng gửi traffic mới. Nhưng có race condition: pod có thể nhận SIGTERM trước khi endpoints update propagate xong — trong khoảng đó, traffic mới vẫn đến pod đang shutdown.

Giải pháp mà mình dùng: thêm preStop hook với sleep ngắn (5-10 giây) để đợi endpoints update propagate trước khi app bắt đầu shutdown:

lifecycle:
  preStop:
    exec:
      command: ["sleep", "5"]

Kết hợp với app xử lý SIGTERM đúng cách — ngừng accept connection mới, đợi request đang xử lý hoàn thành, rồi mới exit. Pattern graceful shutdown đã nói kỹ ở bài trước.


Sticky session — khi nào cần, khi nào tránh

Sticky session (session affinity) đảm bảo request từ cùng user luôn đến cùng backend server. Có hai cách implement phổ biến.

Cookie-based sticky (L7): load balancer gắn cookie vào response — ví dụ SERVERID=s1. Request tiếp theo từ browser gửi kèm cookie, load balancer đọc cookie và route đến server s1.

upstream backend {
    server 10.0.0.1;
    server 10.0.0.2;
    sticky cookie srv_id expires=1h;
}

IP-based sticky (L4/L7): dùng IP hash như đã nói ở trên. Đơn giản hơn cookie nhưng kém tin cậy vì nhiều user share IP qua NAT.

Khi nào cần sticky session

Khi backend giữ state local — session data trong memory, shopping cart trong local cache, WebSocket connection (connection phải duy trì với cùng server). Nếu request đến server khác, server đó không có session data, user bị logout hoặc mất cart.

Mình còn thấy sticky session dùng cho server-side rendering có cache local — một user nhất định luôn đến cùng server, tận dụng cache đã warm cho user đó.

Tại sao sticky session gây khó scale

Sticky session phá vỡ giả định “mọi backend đều thay thế được” — khi server A chết, tất cả user sticky với A bị ảnh hưởng, session mất, phải login lại hoặc mất cart. Không thể chỉ thêm server mới vào pool — user cũ vẫn sticky với server cũ, server mới rảnh rỗi.

Auto-scaling cũng bị ảnh hưởng. Scale down remove server → user sticky với server đó mất session. Scale up thêm server → không có user mới nào sticky với server mới (trừ khi user mới đến). Phân phối load trở nên bất đều theo thời gian.

Deploy cũng phức tạp hơn — rolling deploy phải drain session trước khi remove server, hoặc chấp nhận user bị mất session. Blue-green deploy cần migrate session giữa hai pool.

Giải pháp tốt hơn: externalize session — lưu session trong Redis, Memcached, hoặc database. Mọi backend đều đọc/ghi session từ store chung. Request đến server nào cũng có session, không cần sticky. Đây là pattern mà mình luôn khuyến khích — chi phí setup session store rất nhỏ so với rắc rối của sticky session.

Nếu vẫn cần sticky session (legacy app không thể sửa), ít nhất hãy dùng cookie-based với TTL ngắn, và đảm bảo khi server chết thì user bị redirect sang server khác thay vì nhận lỗi mãi.


SSL/TLS termination tại load balancer

Terminate SSL/TLS tại load balancer là pattern phổ biến nhất — load balancer giữ certificate, decrypt HTTPS từ client thành HTTP, forward HTTP đến backend. Backend không cần xử lý SSL, giảm CPU overhead.

server {
    listen 443 ssl;
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    location / {
        proxy_pass http://backend;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

Header X-Forwarded-Proto nói cho backend biết request gốc là HTTPS — quan trọng để backend generate URL đúng scheme và set cookie Secure. X-Forwarded-For truyền IP gốc của client vì backend chỉ thấy IP của load balancer.

Nếu traffic giữa load balancer và backend đi qua mạng không tin cậy (cross-region, qua internet), cần encrypt lại — dùng mutual TLS (mTLS) giữa LB và backend, hoặc TLS passthrough (L4 LB forward encrypted traffic thẳng, backend tự decrypt). Trong cùng VPC hoặc cùng datacenter, HTTP giữa LB và backend thường chấp nhận được vì network đã được bảo vệ ở tầng infrastructure.


Các lựa chọn load balancer

Nginx

Load balancer phổ biến nhất cho web. Hỗ trợ L7 (HTTP/HTTPS), reverse proxy, caching, SSL termination. Cấu hình bằng file text, reload không downtime (nginx -s reload). Bản open-source thiếu active health check — cần Nginx Plus hoặc module bên thứ ba. Performance rất tốt — hàng chục nghìn concurrent connection trên hardware phổ thông.

Mình dùng Nginx cho hầu hết project vừa và nhỏ vì cấu hình quen thuộc, tài liệu dồi dào, và ecosystem lớn.

HAProxy

Chuyên load balancing, hỗ trợ cả L4 lẫn L7. Active health check built-in, connection draining built-in, dashboard stats real-time. Cấu hình chi tiết hơn Nginx cho use case load balancing phức tạp — ACL routing, stick-table cho rate limiting, detailed logging. Performance ngang Nginx hoặc tốt hơn cho pure load balancing.

Mình dùng HAProxy khi cần L4 load balancing (database connection pooling) hoặc khi cần health check phức tạp mà Nginx OSS không hỗ trợ.

Cloud load balancer

AWS ALB (L7), NLB (L4), GCP Load Balancer, Azure Load Balancer — managed service, không cần vận hành. Auto-scale, HA built-in, tích hợp với cloud ecosystem (target group, auto-scaling group, WAF). Chi phí theo traffic — rẻ khi traffic thấp, có thể đắt khi traffic rất cao.

NLB đặc biệt phù hợp cho L4 use case — latency cực thấp, throughput cực cao, hỗ trợ static IP (Elastic IP). ALB phù hợp cho HTTP routing phức tạp — path-based routing, host-based routing, authentication integration.

Kubernetes Service và Ingress

Kubernetes Service (ClusterIP, NodePort, LoadBalancer) là L4 load balancing nội bộ cluster — kube-proxy dùng iptables hoặc IPVS để route traffic đến pod. Ingress (Nginx Ingress Controller, Traefik, Istio Gateway) là L7 load balancing — route theo host, path, header.

Trong Kubernetes, mình thường dùng Service cho communication giữa các service nội bộ, và Ingress cho traffic từ ngoài vào. Ingress controller cần tune cẩn thận — default config thường không tối ưu cho production.


Vấn đề hay gặp

Thundering herd sau khi backend recover

Backend server restart xong, health check pass, load balancer đánh dấu healthy và gửi traffic trở lại. Nhưng server vừa khởi động — cache trống, connection pool chưa warm, JIT chưa compile hot path. Đột ngột nhận full traffic → overload → health check fail lại → vòng lặp.

Giải pháp: slow start — sau khi backend trở lại healthy, load balancer tăng traffic dần (10% → 30% → 60% → 100%) thay vì gửi 100% ngay. HAProxy hỗ trợ slowstart:

server s1 10.0.0.1:8080 check slowstart 30s

Nginx Plus cũng hỗ trợ slow start. Nếu load balancer không hỗ trợ, dùng readiness check — endpoint /ready chỉ trả 200 sau khi cache warm xong, connection pool sẵn sàng.

Phân phối tải không đều

Dù dùng round-robin, một số server vẫn nhận nhiều load hơn. Nguyên nhân thường gặp: keep-alive connection dài — client mở connection đến server A, gửi 100 request qua connection đó. Round-robin chỉ balance connection mới, không balance request trên connection đã mở. Server A nhận 100 request từ connection cũ, server B chỉ nhận request từ connection mới.

Với HTTP/2 tệ hơn — một TCP connection multiplex hàng trăm request. Nếu client mở connection đến server A trước, toàn bộ request đi qua connection đó đến server A.

Giải pháp: dùng least connections thay round-robin, hoặc giảm keep-alive timeout để connection được recycle thường xuyên hơn, tạo cơ hội re-balance.

Health check quá nhạy hoặc quá chậm

Health check interval 1 giây, fail threshold 1 → GC pause 2 giây khiến server bị đánh dấu unhealthy, traffic bị remove → GC xong, server healthy lại, traffic trở lại → thundering herd. Đây là health check quá nhạy — tạo flapping.

Health check interval 60 giây, fail threshold 3 → server chết 3 phút mới bị phát hiện. 3 phút toàn bộ request đến server đó fail. Đây là health check quá chậm.

Mình thường dùng: interval 5-10 giây, fall 3 (3 lần fail liên tiếp mới unhealthy), rise 2 (2 lần pass mới healthy lại). Tổng thời gian phát hiện server chết: 15-30 giây — đủ nhanh cho hầu hết use case mà không nhạy giả.


Anti-pattern

Không có health check. Load balancer gửi traffic đến server đã chết, user thấy 502 liên tục cho đến khi ai đó phát hiện bằng tay. Mình từng thấy setup Nginx upstream không có max_fails — server crash mà Nginx vẫn gửi request đều đặn, 1/3 request fail.

Sticky session cho mọi thứ. “Thêm sticky session cho chắc” — không. Sticky session chỉ cần khi backend thực sự giữ state local. Nếu service stateless thì sticky session chỉ gây phân phối load không đều và khó scale. Externalise session ra Redis rồi bỏ sticky session.

Single load balancer không HA. Load balancer là single point of failure — nếu nó chết, toàn bộ traffic chết theo. Production cần ít nhất hai load balancer với failover. Dùng cloud managed load balancer nếu không muốn tự quản lý HA.

Health check endpoint gọi mọi dependency. Endpoint /health query database, gọi Redis, gọi external API — bất kỳ dependency nào chậm thì health check fail, server bị remove khỏi pool, trong khi server hoàn toàn có thể phục vụ request không cần dependency đó. Liveness check phải nhẹ, dependency check thuộc readiness.

Không có connection draining khi deploy. Remove server đột ngột khỏi pool → request đang xử lý bị cắt → user thấy lỗi. Luôn drain connection trước khi remove — đợi request hoàn thành hoặc timeout, rồi mới tắt server.

Ignore backend capacity khi scale load balancer. Thêm load balancer capacity (ALB auto-scale) mà backend không scale theo — load balancer chấp nhận nhiều request hơn nhưng backend vẫn y nguyên → overload backend. Load balancer không tạo ra capacity, nó chỉ phân phối. Capacity nằm ở backend.


Tóm tắt

Load balancer phân phối traffic, cung cấp HA, và cho phép triển khai linh hoạt — nhưng cần cấu hình đúng. L4 cho throughput cao và protocol-agnostic, L7 cho routing linh hoạt theo HTTP context. Chọn thuật toán theo workload: round-robin cho stateless đồng đều, least connections khi request time biến động lớn, consistent hashing cho cache và stateful service.

Health check là phần quan trọng nhất — kết hợp active check (LB chủ động ping) và passive check (theo dõi response thật). Phân biệt liveness (process sống không) và readiness (sẵn sàng nhận traffic không) — nhầm lẫn hai cái gây cascade failure. Interval 5-10 giây, fall 3, rise 2 là điểm khởi đầu hợp lý.

Connection draining bắt buộc khi deploy — ngừng gửi request mới, đợi request cũ hoàn thành. Sticky session chỉ dùng khi thật sự cần — externalise session ra Redis là giải pháp tốt hơn trong hầu hết trường hợp. SSL termination tại load balancer giảm overhead cho backend, nhưng cần encrypt lại nếu traffic nội bộ qua mạng không tin cậy.

Load balancer không phải “cắm vào quên đi” — nó cần monitoring (backend health, connection count, latency qua LB), tuning (health check interval, algorithm, timeout), và HA (ít nhất hai instance hoặc managed service). Hiểu đúng cách nó hoạt động giúp bạn tránh incident như mở đầu bài — 45 giây downtime vì health check quá thưa và không có connection draining.


Tham khảo