0

Cuộc đời của một HTTP Request

Đối tượng: Junior → Senior Developer
Mục tiêu: Hiểu đúng, đủ, và có thể tra cứu lại
Cách đọc: Đọc thẳng từ đầu nếu bạn mới. Dùng mục lục để jump nếu bạn cần ôn một phần cụ thể.


Mục lục

  1. Browser tạo HTTP Request
  2. DNS Lookup
  3. TCP Handshake
  4. TLS Handshake
  5. Load Balancer
  6. Reverse Proxy
  7. API Gateway
  8. Web Server & Thread Model
  9. Middleware / Filter / Interceptor
  10. Controller → Validation
  11. Service Layer
  12. Cache
  13. Database Query & ORM
  14. Transaction
  15. Microservice: Internal Calls
  16. Sync vs Async Communication
  17. Distributed Tracing
  18. Response: Serialize & Compress
  19. Response quay về client
  20. Browser render
  21. Timeout
  22. Retry
  23. Rate Limiting
  24. Circuit Breaker
  25. Observability

Tổng quan luồng

Trước khi đi vào từng bước, đây là toàn bộ hành trình của một HTTP Request theo thứ tự:

[Client]
  │
  ├─ 1. Tạo HTTP Request (method, URL, headers, body)
  ├─ 2. DNS Lookup (domain → IP)
  ├─ 3. TCP Handshake (3-way)
  ├─ 4. TLS Handshake (nếu HTTPS)
  │
[Network / Internet]
  │
  ├─ 5. Load Balancer (phân phối traffic)
  ├─ 6. Reverse Proxy (SSL termination, cache, compression)
  ├─ 7. API Gateway (auth, rate limit, routing)
  │
[Backend Server]
  │
  ├─ 8. Web Server nhận, cấp thread/event
  ├─ 9. Middleware chain (auth, logging, tracing)
  ├─ 10. Controller (parse, validate request)
  ├─ 11. Service Layer (business logic)
  ├─ 12. Cache check (Redis, Memcached)
  ├─ 13. Database query (SQL/NoSQL)
  ├─ 14. Transaction (nếu cần)
  ├─ 15–16. Internal service calls (sync/async)
  │
[Response]
  │
  ├─ 17. Serialize (object → JSON)
  ├─ 18. Compress (gzip/brotli)
  ├─ 19. Đi ngược lại toàn bộ hành trình về client
  └─ 20. Browser parse và render

Tổng thời gian: 50ms đến vài giây, tùy network, số lượng service call, và database performance.


Phần 1 — Từ client đến datacenter

1. Browser tạo HTTP Request

Khi người dùng bấm một button hay gõ URL, browser (hoặc mobile app) tạo ra một HTTP Request với cấu trúc:

POST /api/v1/login HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGci...
Cookie: session_id=abc123
User-Agent: Mozilla/5.0...
Accept-Encoding: gzip, deflate, br
Content-Length: 47

{"email":"user@example.com","password":"..."}

Các thành phần cần hiểu đúng:

Method — Không chỉ là convention:

  • GET: Idempotent, không có body, có thể cached. Server không được thay đổi state khi nhận GET.
  • POST: Không idempotent theo spec. Tạo resource mới hoặc trigger action.
  • PUT: Idempotent. Replace toàn bộ resource tại URI đó.
  • PATCH: Partial update. Không nhất thiết idempotent.
  • DELETE: Idempotent. Gọi nhiều lần kết quả giống nhau (resource đã xóa rồi vẫn là đã xóa).

Tại sao idempotency quan trọng? Vì retry. Nếu network timeout sau khi server đã xử lý, client retry. Nếu operation idempotent, retry an toàn. Nếu không, bạn có thể tạo duplicate.

Headers — không phải tất cả đều tùy ý:

  • Content-Type: application/json — server dùng cái này để biết parse body theo kiểu gì. Thiếu header này, nhiều framework từ chối hoặc parse sai.
  • Authorization — thường là Bearer <JWT> hoặc Basic base64(user:pass).
  • Accept-Encoding — báo server client hỗ trợ compression nào.
  • If-None-Match / If-Modified-Since — cho conditional request, phục vụ caching.

Content-Type phổ biến và khi nào dùng:

Content-Type Khi nào dùng Ghi chú
application/json REST API Phổ biến nhất
application/x-www-form-urlencoded HTML form submit key=value&key2=value2
multipart/form-data Upload file Bắt buộc khi có file
text/plain Debug, webhook đơn giản Ít dùng trong API

2. DNS Lookup

Vấn đề: Browser có domain api.example.com, nhưng TCP cần IP address. DNS là hệ thống dịch tên → địa chỉ.

Thứ tự tra cứu (từng bước, dừng lại khi tìm được):

1. Browser DNS cache
   └─ Mỗi browser có cache riêng với TTL
2. OS DNS cache
   └─ /etc/hosts, systemd-resolved, nscd
3. Router cache
   └─ Home/office router thường cache DNS
4. ISP Recursive Resolver
   └─ Server của Viettel, VNPT, FPT...
5. Root DNS Servers (13 cụm trên toàn cầu)
   └─ Trả về địa chỉ TLD nameserver
6. TLD Nameserver (.com, .vn, .org...)
   └─ Trả về địa chỉ authoritative nameserver
7. Authoritative Nameserver (của domain đó)
   └─ Trả về IP record

TTL (Time To Live): Mỗi DNS record có TTL tính bằng giây. Sau TTL, cache expire và phải query lại. Khi bạn thay đổi DNS record (ví dụ migrate server), TTL quyết định bao lâu để change propagate.

api.example.com.  300  IN  A  203.0.113.42
                  ^^^
                  TTL = 300 giây = 5 phút

DNS record types hay gặp:

Record Mục đích Ví dụ
A Domain → IPv4 example.com → 203.0.113.1
AAAA Domain → IPv6 example.com → 2001:db8::1
CNAME Domain → Domain khác www.example.com → example.com
MX Mail server example.com → mail.example.com
TXT Metadata, SPF, verification Nhiều dạng
NS Nameserver cho domain example.com → ns1.cloudflare.com

Latency thực tế:

  • Cache hit (browser/OS): < 1ms
  • ISP recursive resolver: 5–20ms
  • Full chain (cache miss): 50–200ms

Vì sao DNS chậm làm app chậm: Nếu mỗi API call cần DNS lookup (~50ms) mà không có caching, app thêm 50ms latency mỗi request. Browser cache DNS, nhưng mỗi new tab hay sau khi TTL expire phải lookup lại. Mobile app cũng có DNS cache nhưng TTL thường ngắn hơn.

DNS Prefetching: Browser hiện đại hỗ trợ <link rel="dns-prefetch" href="//api.example.com"> để resolve DNS trước khi user thực sự cần.


3. TCP Handshake

HTTP/1.1 và HTTP/2 chạy trên TCP. Trước khi gửi bất kỳ byte application data nào, TCP phải thiết lập connection qua 3-way handshake:

Client                          Server
  │                               │
  │──── SYN (seq=x) ─────────────▶│
  │                               │
  │◀─── SYN-ACK (seq=y, ack=x+1) ─│
  │                               │
  │──── ACK (ack=y+1) ───────────▶│
  │                               │
  │════ Data transfer begins ══════│

Ý nghĩa từng bước:

  • SYN: Client báo "tôi muốn kết nối" + gửi Initial Sequence Number (ISN) ngẫu nhiên.
  • SYN-ACK: Server đồng ý + gửi ISN của server + ACK sequence number của client.
  • ACK: Client confirm đã nhận SYN-ACK. Connection established.

Chi phí: Một RTT (Round Trip Time) trước khi gửi được data. Với server ở Việt Nam, RTT khoảng 5-20ms. Với server ở US, RTT 150-250ms.

Connection Reuse: Đây là lý do HTTP/1.1 có Connection: keep-alive và HTTP/2 multiplexing. Thay vì tạo TCP connection mới cho mỗi request, dùng lại connection đã có.

HTTP/1.0: Mỗi request = 1 TCP connection (tạo mới, xong đóng)
HTTP/1.1: Keep-alive, dùng lại connection, nhưng mỗi lần chỉ 1 request
HTTP/2:   1 TCP connection, nhiều stream song song (multiplexing)
HTTP/3:   Chạy trên QUIC (UDP-based), không cần TCP handshake truyền thống

TCP vs UDP: TCP đảm bảo delivery (có ACK, retransmit nếu mất packet), ordered (packet đến đúng thứ tự). UDP không đảm bảo — nhưng nhanh hơn. HTTP/3 dùng QUIC trên UDP với reliability mechanism riêng, giảm được latency của handshake.


4. TLS Handshake

HTTPS = HTTP + TLS. TLS (Transport Layer Security) mã hóa toàn bộ communication.

TLS 1.2 Handshake (2 RTT):

Client                              Server
  │                                   │
  │── ClientHello ───────────────────▶│
  │   (supported cipher suites,       │
  │    TLS version, random bytes)     │
  │                                   │
  │◀── ServerHello ───────────────────│
  │    (chosen cipher suite,          │
  │     server random, session ID)    │
  │◀── Certificate ───────────────────│
  │◀── ServerHelloDone ───────────────│
  │                                   │
  │── ClientKeyExchange ─────────────▶│
  │   (pre-master secret,             │
  │    encrypted with server pubkey)  │
  │── ChangeCipherSpec ──────────────▶│
  │── Finished ─────────────────────▶│
  │                                   │
  │◀── ChangeCipherSpec ──────────────│
  │◀── Finished ──────────────────────│
  │                                   │
  │════════ Encrypted data ═══════════│

TLS 1.3 (1 RTT, phổ biến hiện nay): Rút gọn handshake, loại bỏ các cipher suite yếu, hỗ trợ 0-RTT resumption cho connection đã biết trước.

Certificate Validation: Khi nhận certificate, client phải:

  1. Verify chữ ký của CA (Certificate Authority) — dùng CA public key được pre-installed trong OS/browser
  2. Kiểm tra expiry date
  3. Kiểm tra domain match (SAN — Subject Alternative Name)
  4. Kiểm tra revocation (CRL hoặc OCSP)

Certificate chain: Certificate thường không được ký trực tiếp bởi Root CA mà qua Intermediate CA. Chain: Leaf Cert ← Intermediate CA ← Root CA. Browser trust Root CA, verify chain từ trên xuống.

Symmetric vs Asymmetric encryption:

  • Handshake dùng asymmetric (RSA, ECDHE) để trao đổi key an toàn.
  • Sau handshake, dùng symmetric (AES-GCM) để encrypt actual data — nhanh hơn nhiều.

Chi phí TLS: Full handshake tốn 1-2 RTT thêm. TLS Session Resumption (session ticket) giảm xuống 0 RTT cho session đã biết. Encryption/decryption overhead: ~1-5% CPU trên modern hardware (AES-NI instruction sets).


Phần 2 — Vào datacenter

5. Load Balancer

Request đến IP public. IP này trỏ đến Load Balancer, không phải application server.

Nhiệm vụ cốt lõi:

  • Distribute traffic qua nhiều backend server
  • Health check, loại bỏ server unhealthy khỏi pool
  • Failover khi một server die

Load balancing algorithms:

Algorithm Cách hoạt động Phù hợp khi
Round Robin Lần lượt từng server Request có cost tương đương
Weighted Round Robin Server mạnh hơn nhận nhiều hơn Server có specs khác nhau
Least Connections Gửi đến server ít connection nhất Request có duration khác nhau
IP Hash Hash IP → server cố định Cần sticky session theo IP
Random Ngẫu nhiên Simple, stateless service

Layer 4 vs Layer 7 Load Balancer:

  • L4 (Transport): Hoạt động ở TCP level. Không đọc HTTP content. Nhanh hơn, ít tốn CPU hơn. Không thể route theo URL hay header.
  • L7 (Application): Đọc HTTP request. Có thể route theo path, header, cookie. Hỗ trợ sticky session, SSL termination, content-based routing.
L4: TCP packet → route theo IP:port
L7: HTTP request → route theo URL, method, header, body

Ví dụ L7 routing:

upstream api_servers {
    least_conn;
    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080 weight=1;
    server 10.0.0.3:8080 backup;  # Chỉ dùng khi 2 server trên fail
}

location /api/payment/ {
    proxy_pass http://payment_servers;  # Route đến cluster riêng
}

location /api/ {
    proxy_pass http://api_servers;
}

Health Check: LB định kỳ gửi request (ví dụ GET /health) đến mỗi server. Nếu không nhận response hoặc nhận 5xx liên tiếp, LB loại server đó khỏi pool cho đến khi healthy lại.


6. Reverse Proxy

Reverse Proxy ngồi trước backend application server. Khác với Forward Proxy (proxy phía client), Reverse Proxy là proxy phía server.

Các việc Reverse Proxy thường làm:

SSL Termination: Decrypt TLS tại đây, traffic trong internal network là plain HTTP. Backend không cần xử lý TLS. Lợi: centralized certificate management, giảm CPU cho app server.

Client ──HTTPS──▶ Reverse Proxy ──HTTP──▶ Backend
         encrypted              unencrypted (internal network)

Static file serving: Nginx serve file tĩnh (HTML, CSS, JS, image) cực nhanh mà không cần đến application server. Nginx có thể handle hàng chục nghìn concurrent connection với vài MB RAM, trong khi Spring Boot hay Django tốn nhiều hơn nhiều cho cùng lượng static file.

Compression: Bật gzip/brotli tại đây thay vì để từng app server tự làm.

Request buffering: Nhận full request từ client (kể cả slow client), rồi mới forward đến backend. Tránh tình trạng backend bị block chờ slow upload từ client.

Caching: Cache response của backend theo URL, header. Request y hệt nhau không cần đến backend.

Security filtering: Block request với pattern nguy hiểm (SQL injection trong URL, header quá lớn, method không hợp lệ...).


7. API Gateway

API Gateway là layer ở trên Reverse Proxy, tập trung vào application-level concerns:

Authentication: Verify JWT token — decode, check signature, check expiry, extract user claims — trước khi request đến service. Service không cần tự làm nữa.

Request ──▶ API Gateway ──▶ [Auth check] ──▶ Service
                              │
                              └─ Token invalid → 401, không forward

Rate Limiting: Giới hạn số request theo user, IP, API key, endpoint.

/api/search:       1000 req/min/user
/api/otp/send:     5 req/hour/phone
/api/payment:      10 req/min/user
Global per IP:     10000 req/min

Routing: Map external endpoint đến internal service.

POST /api/v2/orders      → order-service:8080/orders
GET  /api/v1/products    → product-service:8081/products
POST /api/v1/auth/login  → auth-service:8082/login

Request/Response transformation: Thêm header, rename field, version translation.

Logging và Metrics: Tất cả request log tập trung tại đây.

Gắn Trace ID: Đây là chỗ Trace ID được sinh ra và gắn vào header (X-Trace-Id: uuid). Trace ID này sẽ được forward qua tất cả internal service call, phục vụ distributed tracing.

Phân biệt Reverse Proxy vs API Gateway: Reverse Proxy thường là infrastructure-level (Nginx, HAProxy), API Gateway là application-level (Kong, AWS API Gateway, custom). Trong thực tế, ranh giới mờ — Nginx với nhiều plugin có thể làm phần lớn việc của API Gateway.


Phần 3 — Trong backend server

8. Web Server & Thread Model

Request đến process của application. Web server framework quyết định cách xử lý concurrent request.

Thread-per-request model (Tomcat, truyền thống):

Connection Pool: 200 threads max

Request 1 ──▶ Thread 1 (bị giữ suốt thời gian xử lý)
Request 2 ──▶ Thread 2
...
Request 201 ──▶ Phải chờ thread available
  • Mỗi request chiếm một thread từ khi nhận đến khi trả response
  • Thread pool thường 100–500 thread
  • Thread bị blocking khi chờ DB, external API → thread waste
  • Phù hợp cho CPU-bound workload

Event Loop / Reactive model (Netty, Node.js, Vert.x):

1 event loop thread (hoặc vài thread = số CPU core)
  ├─ Nhận request A
  ├─ Gửi DB query cho A (non-blocking, register callback)
  ├─ Nhận request B
  ├─ Gửi DB query cho B (non-blocking)
  ├─ Callback: DB trả kết quả cho A → tiếp tục xử lý A
  └─ Callback: DB trả kết quả cho B → tiếp tục xử lý B
  • Không block thread khi chờ I/O
  • Một thread xử lý hàng nghìn concurrent connection
  • Phù hợp cho I/O-bound workload
  • Code phức tạp hơn (callback hell, reactive streams)

So sánh:

Thread-per-request Event Loop
Concurrent connections Bị giới hạn bởi thread count Cao hơn nhiều
Code style Imperative, dễ viết Reactive, phức tạp hơn
Phù hợp CPU-bound I/O-bound
Memory ~1MB/thread × số thread Thấp hơn
Framework Spring MVC, Django, Rails Spring WebFlux, Node.js, Vert.x

Context switching overhead: Khi nhiều thread chạy trên ít CPU core, OS phải luân phiên (context switch). Mỗi context switch tốn ~1-10µs (lưu/restore register, flush TLB cache). Với hàng nghìn thread, overhead này đáng kể.


9. Middleware / Filter / Interceptor

Trước khi Controller nhận request, một chain các middleware chạy theo thứ tự.

Trong Spring Boot:

Request
  │
  ▼
[Servlet Filter 1] ─ SecurityFilter (check auth)
  │
  ▼
[Servlet Filter 2] ─ MDCFilter (gắn request ID vào logging context)
  │
  ▼
[HandlerInterceptor] ─ RateLimitInterceptor
  │
  ▼
[AOP Advice] ─ @Timed, @Cacheable
  │
  ▼
Controller

Thứ tự Filter, Interceptor, AOP:

  • Filter: Servlet level. Chạy trước Spring. Có thể đọc/modify request/response stream. Dùng cho authentication, logging, compression.
  • Interceptor: Spring MVC level. Chạy sau Filter, trước Controller. Có access vào HandlerMethod (biết đang gọi method nào). Dùng cho authorization theo annotation.
  • AOP (Aspect-Oriented Programming): Method level. Wrap quanh method call. Dùng cho caching, transaction, metrics.

Thứ tự execution: Filter → Interceptor preHandle → Controller → Interceptor postHandle → Filter response phase

MDC (Mapped Diagnostic Context): Cơ chế gắn key-value vào logging context của thread hiện tại. Request ID, user ID gắn vào MDC sẽ tự động xuất hiện trong mọi log line trong suốt thời gian xử lý request đó — không cần pass parameter qua từng function.

// Filter gắn vào MDC
MDC.put("requestId", request.getHeader("X-Trace-Id"));
MDC.put("userId", getCurrentUserId());

// Bất kỳ log nào sau đó tự động có context
log.info("Processing order");
// Output: [requestId=abc123] [userId=1001] Processing order

10. Controller → Validation

Controller là entry point của business code.

URL Mapping:

@RestController
@RequestMapping("/api/v1")
public class OrderController {

    @PostMapping("/orders")
    public ResponseEntity<OrderResponse> createOrder(
        @RequestBody @Valid CreateOrderRequest request,
        @RequestHeader("Authorization") String authHeader
    ) {
        // ...
    }
}

Framework map POST /api/v1/orders đến method này dựa trên annotation.

Deserialization: JSON body được convert thành Java object (hoặc Python dict, Go struct...). Nếu JSON sai format → exception ngay tại đây, trước khi business logic.

// Request đúng:
{"productId": 123, "quantity": 2}

// Lỗi thường gặp:
{"productId": "123", "quantity": 2}   // Type mismatch nếu productId là int
{"product_id": 123, "quantity": 2}    // Field name mismatch (snake vs camel)
{}                                     // Missing required field

Validation:

public class CreateOrderRequest {
    @NotNull
    @Positive
    private Long productId;

    @Min(1) @Max(100)
    private Integer quantity;

    @NotBlank
    @Size(max = 500)
    private String shippingAddress;
}

Validation fail → 400 Bad Request với error detail, không vào service layer.

Input Sanitization: Ngoài type validation, cần sanitize để tránh injection:

  • Trim whitespace
  • Reject nếu chứa ký tự không hợp lệ
  • Encode trước khi dùng trong SQL (thường ORM lo), trong HTML (tránh XSS)

Phần 4 — Business logic

11. Service Layer

Service Layer chứa business logic — phần code quan trọng nhất, cũng là phần thường phức tạp nhất.

Responsibility của Service Layer:

  • Orchestrate các operation (gọi repository, gọi service khác)
  • Enforce business rules
  • Không biết gì về HTTP (không đọc Header, không set Status Code)
  • Không biết gì về database cụ thể (delegate cho Repository)

Anti-pattern thường gặp:

// ❌ Sai: Business logic trong Controller
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody OrderRequest req) {
    if (req.getQuantity() > 100) return ResponseEntity.badRequest()...;
    if (!inventoryRepo.hasStock(req.getProductId(), req.getQuantity())) {
        return ResponseEntity.status(409)...;
    }
    // ...
}

// ✅ Đúng: Controller delegate cho Service
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody @Valid OrderRequest req) {
    Order order = orderService.createOrder(req);
    return ResponseEntity.ok(order);
}

// Service xử lý business rule
public Order createOrder(OrderRequest req) {
    inventoryService.reserve(req.getProductId(), req.getQuantity()); // throws if unavailable
    Payment payment = paymentService.charge(req.getUserId(), req.getAmount());
    return orderRepo.save(new Order(req, payment));
}

Transaction boundary thường nằm ở Service Layer, không phải Controller hay Repository.


12. Cache

Cache check thường xảy ra trong Service Layer, trước khi query database.

Cache Aside Pattern (phổ biến nhất):

def get_product(product_id):
    # 1. Check cache
    cached = redis.get(f"product:{product_id}")
    if cached:
        return deserialize(cached)          # Cache hit: return ngay

    # 2. Cache miss: query DB
    product = db.query("SELECT * FROM products WHERE id = ?", product_id)

    # 3. Store vào cache
    redis.setex(f"product:{product_id}", ttl=3600, value=serialize(product))

    return product

Các caching pattern khác:

Pattern Đặc điểm Khi dùng
Cache Aside App tự manage cache Read-heavy, flexible TTL
Write Through Write cache và DB cùng lúc Cần cache luôn fresh
Write Behind Write cache trước, async flush DB Write-heavy, tolerate slight delay
Read Through Cache tự load từ DB khi miss Centralized cache logic

Vấn đề cần chú ý:

Cache Stampede (Thundering Herd): Key expire đúng lúc nhiều request cùng miss → tất cả đổ vào DB. Giải pháp: mutex lock khi populate cache, hoặc probabilistic early expiration.

Cache Invalidation: Khi data trong DB thay đổi, cache cũ có thể stale. Hai strategy:

  • TTL-based: Để cache tự expire. Đơn giản, nhưng có window stale.
  • Event-based: Khi DB update, explicit delete cache key. Consistent hơn, phức tạp hơn.

Cache key design:

user:{user_id}                    # User profile
product:{product_id}:detail       # Product detail
category:{cat_id}:products:page:{n}  # Paginated product list
rate_limit:{user_id}:{endpoint}   # Rate limiting counter
session:{session_token}           # User session

13. Database Query & ORM

Đây thường là phần tốn latency nhiều nhất trong một request.

ORM (Object-Relational Mapping): Framework như Hibernate (Java), SQLAlchemy (Python), ActiveRecord (Ruby) tự động generate SQL từ code.

// Code:
List<Order> orders = orderRepo.findByUserIdAndStatus(userId, "PENDING");

// ORM generate SQL:
SELECT * FROM orders WHERE user_id = ? AND status = ? AND deleted_at IS NULL

N+1 Problem: Lỗi phổ biến nhất với ORM.

// ❌ N+1: 1 query lấy orders, sau đó N query lấy product cho mỗi order
List<Order> orders = orderRepo.findAll();  // 1 query: SELECT * FROM orders
for (Order order : orders) {
    Product p = order.getProduct();  // N query: SELECT * FROM products WHERE id = ?
    // ...
}
// Nếu có 100 orders → 101 queries

// ✅ Đúng: JOIN hoặc batch fetch
List<Order> orders = orderRepo.findAllWithProducts();
// 1 query: SELECT o.*, p.* FROM orders o JOIN products p ON o.product_id = p.id

Query Execution Plan: Khi bạn viết SQL, database engine có thể chạy theo nhiều cách khác nhau. EXPLAIN/EXPLAIN ANALYZE cho thấy plan đó là gì:

EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 1001 AND status = 'PENDING';

-- Output:
-- Index Scan using idx_orders_user_id on orders
--   Filter: (status = 'PENDING')
--   Rows: 15, Actual time: 0.05ms

-- vs nếu không có index:
-- Seq Scan on orders
--   Filter: (user_id = 1001 AND status = 'PENDING')
--   Rows: 1000000, Actual time: 320ms

Index: Index trade-off write performance và storage để tăng read performance. Không phải column nào cũng cần index.

Column nên index: Columns thường xuất hiện trong WHERE, JOIN ON, ORDER BY, GROUP BY với high cardinality (nhiều unique value).

-- Các index thường cần:
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
CREATE UNIQUE INDEX idx_users_email ON users(email);

-- Composite index: thứ tự quan trọng
-- (user_id, status) phục vụ query WHERE user_id = ?  và WHERE user_id = ? AND status = ?
-- Không phục vụ query WHERE status = ? (không có user_id)

Connection Pool: Application không mở connection mới cho mỗi query. Connection pool giữ sẵn một số connection, tái sử dụng.

# HikariCP (Java) config
spring.datasource.hikari.maximum-pool-size: 20
spring.datasource.hikari.minimum-idle: 5
spring.datasource.hikari.connection-timeout: 3000   # ms
spring.datasource.hikari.idle-timeout: 600000       # ms

Nếu tất cả connection đang bận → request phải chờ (connection-timeout ms) → nếu vẫn không có → exception.


14. Transaction

Transaction đảm bảo nhiều operation là atomic — hoặc tất cả thành công, hoặc tất cả rollback.

ACID:

  • Atomicity: Tất cả hoặc không gì cả.
  • Consistency: Database chuyển từ valid state này sang valid state khác (không vi phạm constraints).
  • Isolation: Transaction đang chạy không ảnh hưởng lẫn nhau.
  • Durability: Transaction đã commit thì persist, kể cả crash.

Isolation Levels — đây là phần nhiều người bỏ qua:

Level Dirty Read Non-repeatable Read Phantom Read
READ UNCOMMITTED Có thể Có thể Có thể
READ COMMITTED Không Có thể Có thể
REPEATABLE READ Không Không Có thể
SERIALIZABLE Không Không Không
  • Dirty Read: Đọc data của transaction khác chưa commit (có thể bị rollback).
  • Non-repeatable Read: Cùng query, đọc 2 lần trong một transaction, kết quả khác nhau (transaction khác đã commit update giữa 2 lần đọc).
  • Phantom Read: Query trả về row khác nhau giữa 2 lần (transaction khác insert/delete row phù hợp condition).

PostgreSQL default: READ COMMITTED. MySQL InnoDB default: REPEATABLE READ.

Isolation level ảnh hưởng đến performance: SERIALIZABLE cao nhất nhưng throughput thấp nhất vì nhiều lock hơn. Chọn đúng level phù hợp với business requirement.

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    Account from = accountRepo.findById(fromId);  // Read lock
    Account to = accountRepo.findById(toId);

    if (from.getBalance().compareTo(amount) < 0) {
        throw new InsufficientBalanceException();
    }

    from.debit(amount);
    to.credit(amount);

    accountRepo.save(from);
    accountRepo.save(to);
    // Commit khi method return — cả hai update hoặc không gì cả
}

Phần 5 — Trong thế giới Microservice

15. Microservice: Internal Calls

Trong monolith, mọi thứ trong cùng process, gọi nhau qua method call (~nanoseconds). Trong microservice, gọi qua network (~milliseconds). Sự khác biệt này có hệ quả lớn.

Service mesh: Một Order request có thể trigger:

Order Service
  ├──▶ Auth Service (verify user còn active)
  ├──▶ Product Service (validate product, lấy giá)
  ├──▶ Inventory Service (check và reserve stock)
  ├──▶ Coupon Service (validate và apply coupon)
  ├──▶ Payment Service (charge)
  └──▶ Notification Service (trigger email/SMS)

Mỗi mũi tên là một network call. Latency cộng dồn.

Các vấn đề microservice gây ra mà monolith không có:

Partial failure: Service A gọi B, B timeout. A phải quyết định: retry? fail? return partial result? Monolith không có vấn đề này.

Data consistency: Không có shared database transaction qua service boundary. Cần Saga Pattern hoặc 2-phase commit (phức tạp, ít dùng).

Network latency: Method call trong process ~100ns. HTTP call qua network ~1ms nội bộ, ~10-100ms cross-region.

Service discovery: Service A gọi B ở đâu? IP của B có thể thay đổi khi scale. Cần service registry (Consul, Kubernetes Service) để lookup địa chỉ.


16. Sync vs Async Communication

Synchronous (HTTP/gRPC): Service A gọi B, đợi response rồi mới tiếp tục.

Order Service ──[HTTP POST]──▶ Payment Service
              ◀──[Response]───
              (blocked cho đến khi có response)

Dùng khi: Cần kết quả ngay để tiếp tục. Ví dụ: check balance trước khi charge.

Asynchronous (Kafka/RabbitMQ): Service A publish event, không đợi. Service B consume và xử lý độc lập.

Order Service ──[Publish event]──▶ Kafka
                                    │
                    ┌───────────────┤
                    ▼               ▼
          Email Service      Analytics Service
          (xử lý riêng)     (xử lý riêng)

Dùng khi: Không cần kết quả ngay. Ví dụ: gửi email sau khi order tạo xong.

Tradeoffs:

Synchronous Asynchronous
Latency Tổng latency của chain Response nhanh (không chờ consumer)
Coupling Tight (A phụ thuộc B available) Loose (A không biết B)
Consistency Easier (immediate) Eventual consistency
Failure handling Cascading failure Consumer retry độc lập
Debugging Easier (trace theo chain) Harder (event flow phức tạp hơn)

gRPC vs REST: gRPC dùng Protocol Buffers (binary serialization, schema-defined), nhanh hơn JSON REST. Phù hợp cho internal service-to-service. REST phù hợp cho public API vì dễ dùng hơn từ nhiều client.


17. Distributed Tracing

Khi request đi qua nhiều service, một log line trong một service không đủ để debug.

Vấn đề: User report "Tôi thanh toán bị lỗi lúc 14:32". Bạn phải tìm log trong 8 service khác nhau, correlate theo timestamp — không khả thi khi có hàng triệu request.

Giải pháp — Trace ID propagation:

API Gateway: sinh Trace ID = "abc-123"
  │
  ├──▶ Order Service (header: X-Trace-Id: abc-123)
  │     Log: [trace=abc-123] Creating order for user 1001
  │     │
  │     ├──▶ Inventory Service (header: X-Trace-Id: abc-123)
  │     │     Log: [trace=abc-123] Reserving 2 units of product 555
  │     │
  │     └──▶ Payment Service (header: X-Trace-Id: abc-123)
  │           Log: [trace=abc-123] Charging 599000 VND
  │           Log: [trace=abc-123] Payment failed: insufficient balance

Tìm theo trace ID abc-123 → thấy toàn bộ hành trình.

Span: Mỗi operation trong một service là một Span. Span có parent-child relationship theo trace.

Trace: abc-123
  ├── Span: api-gateway (0ms - 1200ms)
  │     ├── Span: order-service (5ms - 800ms)
  │     │     ├── Span: db-query-create-order (6ms - 50ms)
  │     │     ├── Span: inventory-service-call (51ms - 200ms)
  │     │     └── Span: payment-service-call (201ms - 790ms) ← slow
  │     └── Span: notification-service (801ms - 850ms)

Tools: Jaeger, Zipkin (open source), Datadog APM, AWS X-Ray. Tất cả implement OpenTelemetry spec.


Phần 6 — Response về client

18. Response: Serialize & Compress

Serialization: Java object (hoặc Python dict, Go struct) → JSON bytes.

// Object
Order order = new Order(id=123, userId=1001, total=599000, status="PAID");

// Serialized JSON
{"id":123,"userId":1001,"total":599000,"status":"PAID","createdAt":"2024-11-29T14:32:10Z"}

Serialization tốn CPU, đặc biệt với object phức tạp, nested, hoặc large list. Jackson (Java), json (Python), encoding/json (Go) là các thư viện phổ biến.

Tránh serialize data không cần thiết: Nếu client chỉ cần idstatus, đừng return cả object với 30 field. Dùng projection hoặc DTO để control output.

Compression:

HTTP/1.1 Response headers:
Content-Encoding: gzip
Content-Type: application/json

Body: [compressed bytes]

Client gửi Accept-Encoding: gzip, br để báo hỗ trợ. Server compress nếu có thể, gắn Content-Encoding header.

gzip vs brotli:

  • gzip: Phổ biến, mọi client hỗ trợ. Compression ratio tốt.
  • brotli: Mới hơn, compression ratio tốt hơn gzip ~15-25%. Hầu hết browser modern hỗ trợ. CPU intensive hơn gzip khi compress.

Nên compress không? Phụ thuộc vào content size. JSON thường compress được 60-80%. File nhỏ (< 1KB) không đáng compress vì overhead lớn hơn lợi ích.


19. Response quay về client

Response đi ngược lại đúng path đã đến:

App Server
  → Reverse Proxy (có thể cache response, add headers)
  → Load Balancer
  → Internet
  → Client (qua TLS encrypted channel đã thiết lập)

HTTP Status Codes — dùng đúng:

Range Ý nghĩa Ví dụ quan trọng
2xx Success 200 OK, 201 Created, 204 No Content
3xx Redirect 301 Permanent, 302 Temporary, 304 Not Modified
4xx Client error 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests
5xx Server error 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

Lỗi thường gặp:

  • Dùng 200 OK cho mọi thứ kể cả lỗi (trả body {"success": false}) — sai.
  • Nhầm 401 và 403: 401 = chưa authenticate (không biết bạn là ai), 403 = đã biết bạn là ai nhưng không có quyền.
  • Dùng 500 cho mọi server error thay vì phân loại rõ hơn.

Response Caching Headers:

Cache-Control: max-age=3600, public       # Cache 1 giờ, mọi cache (CDN, browser)
Cache-Control: no-cache, private          # Không cache, chỉ cho user này
ETag: "abc123"                            # Version token để conditional request
Last-Modified: Thu, 29 Nov 2024 14:00:00 GMT

20. Browser render

Sau khi nhận response, nếu là web app, browser còn phải render.

Đối với API response (JSON): JavaScript xử lý, cập nhật DOM. Người dùng thấy UI thay đổi.

Đối với HTML response (server-side rendering):

HTML parse → DOM tree
CSS parse  → CSSOM tree
             ↓
          Render tree (combine DOM + CSSOM)
             ↓
          Layout (tính vị trí, kích thước)
             ↓
          Paint (vẽ pixel)
             ↓
          Composite (xử lý layer, GPU)

Blocking resources: JavaScript mặc định block HTML parsing khi gặp <script>. CSS block rendering. Đây là lý do có <script defer>, <script async>, và đặt CSS trong <head>, JS trước </body>.

Core Web Vitals — metrics Google dùng để đánh giá UX:

  • LCP (Largest Contentful Paint): Khi nào element lớn nhất hiển thị? Target < 2.5s.
  • FID (First Input Delay): Độ trễ khi user interact lần đầu. Target < 100ms.
  • CLS (Cumulative Layout Shift): Content có bị nhảy lung tung không? Target < 0.1.

Phần 7 — Khi mọi thứ không như mong đợi

21. Timeout

Timeout là quyết định thiết kế, không phải afterthought.

Nếu không có timeout: Thread bị giữ mãi mãi chờ downstream không respond → thread pool cạn dần → server không nhận được request mới → cascade failure.

Các loại timeout:

Connection timeout: Bao lâu để thiết lập TCP connection?
Read timeout:       Bao lâu để nhận response sau khi gửi request?
Write timeout:      Bao lâu để gửi xong request?
Idle timeout:       Bao lâu connection keep-alive có thể ngồi không?
Request timeout:    Tổng thời gian tối đa cho cả request-response.

Timeout cascade — timeout phải giảm dần theo chain:

API Gateway:         30s request timeout
  └─ Order Service:  10s
       ├─ Payment:    8s
       │    └─ Bank:  5s
       └─ Inventory:  3s

Nếu Bank timeout 5s, Payment Service phải timeout trước 8s để có thời gian xử lý lỗi và trả response về Order Service trước khi Order timeout 10s.

Timeout ambiguity trong payment: Request đến Payment Service, forward đến ngân hàng. Ngân hàng xử lý nhưng response bị mất (network partition). Payment Service nhận timeout. Đã charge hay chưa? Không biết. Đây là lý do idempotency key quan trọng — retry với cùng key sẽ trả về kết quả cũ nếu đã xử lý, không charge lại.


22. Retry

Chỉ retry operation idempotent hoặc có idempotency key.

# ✅ Safe to retry: GET request
response = retry(lambda: requests.get("/api/products/123"), max_attempts=3)

# ✅ Safe to retry: POST với idempotency key
response = retry(
    lambda: requests.post("/api/payments",
        json=payload,
        headers={"Idempotency-Key": "idem-key-xyz"}
    ),
    max_attempts=3
)

# ❌ Không safe: POST không có idempotency key
# Retry có thể tạo duplicate

Exponential Backoff với Jitter:

import random, time

def retry_with_backoff(fn, max_attempts=5, base_delay=1):
    for attempt in range(max_attempts):
        try:
            return fn()
        except RetryableException as e:
            if attempt == max_attempts - 1:
                raise
            # Exponential: 1s, 2s, 4s, 8s, 16s
            delay = base_delay * (2 ** attempt)
            # Jitter: ±10% để tránh thundering herd khi nhiều client retry cùng lúc
            jitter = delay * random.uniform(-0.1, 0.1)
            time.sleep(delay + jitter)

Retry budget: Không retry vô hạn. Mỗi service nên có retry budget — tổng số retry trong một khoảng thời gian. Nếu vượt budget, stop retry và alert.

Phân loại lỗi cần retry vs không:

  • Retry: 503 Service Unavailable, 429 Too Many Requests, network timeout, connection refused.
  • Không retry: 400 Bad Request (request sai, retry cũng sai), 401/403 (auth issue), 404 (không tồn tại).

23. Rate Limiting

Rate limiting bảo vệ hệ thống khỏi bị quá tải, dù từ bot hay legitimate user.

Sliding Window Counter với Redis:

def is_rate_limited(user_id: str, endpoint: str,
                    limit: int, window_seconds: int) -> bool:
    now = time.time()
    key = f"rate:{endpoint}:{user_id}"

    pipe = redis.pipeline()
    # Xóa entries cũ hơn window
    pipe.zremrangebyscore(key, 0, now - window_seconds)
    # Thêm request hiện tại
    pipe.zadd(key, {str(now): now})
    # Đếm
    pipe.zcard(key)
    # Reset TTL
    pipe.expire(key, window_seconds)

    results = pipe.execute()
    count = results[2]
    return count > limit

Response khi bị rate limit:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1701234567

{"error": "Rate limit exceeded. Try again in 60 seconds."}

Rate limiting theo nhiều dimension:

  • Per user ID
  • Per IP address
  • Per API key
  • Per endpoint
  • Global (protect hệ thống)

24. Circuit Breaker

Vấn đề: Service B đang chậm hoặc fail. Service A tiếp tục gọi B, mỗi call đợi đến timeout (5s). Thread pool của A bị chiếm dần. A không còn phục vụ được request khác. Cascade failure.

Circuit Breaker States:

           Failure threshold exceeded
CLOSED ─────────────────────────────▶ OPEN
  ▲                                     │
  │ Success                     Timeout │ (thử recovery)
  │                                     ▼
  └───────────────────────────── HALF-OPEN
              Success                Failure
  • CLOSED: Bình thường. Đếm failure. Nếu vượt threshold → OPEN.
  • OPEN: Fail fast — trả error ngay, không gọi downstream. Sau timeout → HALF-OPEN.
  • HALF-OPEN: Cho vài request thử. Nếu thành công → CLOSED. Nếu fail → OPEN lại.
class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=60, half_open_calls=3):
        self.state = "CLOSED"
        self.failure_count = 0
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.last_failure_time = None
        self.half_open_success = 0
        self.half_open_calls = half_open_calls

    def call(self, fn):
        if self.state == "OPEN":
            if time.time() - self.last_failure_time < self.recovery_timeout:
                raise CircuitOpenError()
            self.state = "HALF_OPEN"
            self.half_open_success = 0

        try:
            result = fn()
            if self.state == "HALF_OPEN":
                self.half_open_success += 1
                if self.half_open_success >= self.half_open_calls:
                    self.state = "CLOSED"
                    self.failure_count = 0
            return result
        except Exception as e:
            self.failure_count += 1
            self.last_failure_time = time.time()
            if self.failure_count >= self.failure_threshold:
                self.state = "OPEN"
            raise

Fallback: Khi circuit open, thay vì trả lỗi, có thể trả cached data cũ, default value, hoặc degraded response. Ví dụ: recommendation service fail → trả bestseller list cứng thay vì personalized recommendation.


25. Observability

Observability là khả năng hiểu trạng thái bên trong hệ thống từ output bên ngoài. Ba trụ cột:

Metrics: Số liệu aggregated theo thời gian.

# Prometheus format
http_requests_total{method="POST",endpoint="/api/orders",status="200"} 12453
http_request_duration_seconds{endpoint="/api/orders",quantile="0.99"} 0.842
db_query_duration_seconds{query="create_order"} 0.045
cache_hit_ratio{cache="redis",key_prefix="product"} 0.94

Metrics quan trọng cần monitor:

  • QPS (Queries Per Second): Traffic volume.
  • Latency P50, P95, P99: P99 quan trọng hơn average — average che giấu tail latency.
  • Error rate: Tỉ lệ 5xx/4xx.
  • Saturation: CPU, memory, disk, connection pool usage.

Logs: Sự kiện cụ thể tại một thời điểm.

{
  "timestamp": "2024-11-29T14:32:10.123Z",
  "level": "ERROR",
  "traceId": "abc-123",
  "spanId": "def-456",
  "userId": "1001",
  "service": "payment-service",
  "message": "Payment failed",
  "error": "InsufficientBalance",
  "amount": 599000,
  "balance": 50000
}

Structured logging (JSON) tốt hơn plain text vì có thể query theo field.

Traces: Đã mô tả ở phần 17.

Alerting: Không phải mọi metric thay đổi đều cần alert. Alert khi:

  • Error rate > threshold trong window nhất định (tránh alert flapping)
  • P99 latency vượt SLA
  • Queue consumer lag tăng liên tục

SLO (Service Level Objective): Cam kết về reliability. Ví dụ: "99.9% request hoàn thành trong 500ms". SLO cụ thể giúp đặt đúng ngưỡng alert và prioritize cải tiến.


Tổng kết

Dưới đây là tổng hợp toàn bộ hành trình và latency budget tham khảo:

Bước Latency (fast) Latency (slow) Nơi tốn thời gian
DNS Lookup < 1ms (cache hit) 50-200ms (full chain) ISP resolver, propagation
TCP Handshake 5-20ms (nội địa) 150-250ms (cross-continent) RTT
TLS Handshake 1 RTT thêm 2 RTT (TLS 1.2) Certificate chain validation
Load Balancer < 1ms 5ms Health check routing
App Middleware 1-5ms 20ms Auth token verify
Business Logic 1-10ms 100ms+ Dependent trên service calls
Cache hit < 1ms 5ms Redis network round trip
Cache miss → DB 5-50ms 500ms+ Query, index, lock contention
External API call 50-200ms Timeout Network, third-party SLA
Serialization < 1ms 20ms (large payload) Object complexity
Compression 1-5ms 20ms (large payload) CPU
Total ~50-100ms 1-5s+

Nguyên tắc cốt lõi:

  1. Mỗi lớp trong hành trình đều có thể là bottleneck.
  2. Optimize dựa trên measurement, không phải assumption (đo trước, optimize sau).
  3. Distributed system fail theo cách mà single-machine system không thể — plan for partial failure.
  4. Observability không phải afterthought, build it in từ đầu.
  5. Latency budget: biết tổng latency cho phép, phân bổ cho từng layer, enforce qua timeout.

Tài liệu này cover đường đi happy path và các failure mode phổ biến. Mỗi phần đều có thể đào sâu thêm nhiều lần — đây là nền tảng để bạn biết mình cần đọc thêm gì.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí