CodeGym /Các khóa học /ChatGPT Apps /Gateway và bảo vệ perimeter: proxy, rate limiting, hàng đ...

Gateway và bảo vệ perimeter: proxy, rate limiting, hàng đợi và backpressure

ChatGPT Apps
Mức độ , Bài học
Có sẵn

1. Tại sao phải bảo vệ perimeter của ChatGPT App

Trong ứng dụng web cổ điển, người dùng — là trình duyệt, thường gửi request tới các endpoint của bạn khá dự đoán được. Trong thế giới ChatGPT Apps, bạn có thêm một kiểu client mới: LLM, tự quyết định khi nào và gọi công cụ nào.

Mô hình có thể:

  • trong một cuộc hội thoại gọi cùng một tool nhiều lần liên tiếp;
  • thử nghiệm: “nếu gọi lại suggest_gifts với tham số hơi khác thì sao?”;
  • làm việc song song cho hàng trăm người dùng.

Thêm vào đó là bot, script thử nghiệm, lỗi trong chính code của bạn (ví dụ vòng lặp vô hạn liên tục kích hoạt tool‑call), và bạn sẽ có công thức gần như hoàn hảo cho một vụ DoS “thiện chí”.

Điểm đau nữa — chi phí. Mỗi tool‑call có thể:

  • gọi các API trả phí bên ngoài (giao vận, thanh toán, catalog),
  • gọi các LLM khác (ví dụ RAG search),
  • khởi chạy tác vụ nền nặng.

Không có giới hạn và bảo vệ perimeter, một client “không may mắn” có thể:

  • làm ngã tất cả dịch vụ backend phía sau gateway (Gift API, Commerce API v.v.),
  • vượt hạn mức các API bên ngoài,
  • và “đốt” đáng kể ngân sách cho mô hình.

Mục tiêu bài giảng — chỉ ra cách gateway/proxy + rate limiting + hàng đợi + backpressure biến một thảm họa tiềm tàng thành hệ thống có thể kiểm soát.

Insight

Nền tảng ChatGPT không cung cấp bất kỳ cơ chế bảo vệ nào cho máy chủ MCP của bạn khỏi lưu lượng bên ngoài. Bất kỳ client Internet nào cũng có thể gửi request tới đó, kể cả tiện ích như MCP Jam.

Tất cả những gì ChatGPT có thể đề xuất là giới hạn lưu lượng vào theo địa chỉ IP bằng cách cấu hình reverse proxy (ví dụ NGINX) hoạt động với allowlist. Nếu không cấu hình lọc IP, máy chủ MCP của bạn sẽ hoàn toàn mở, điều này không an toàn — cả cho bạn lẫn người dùng của bạn.

2. Proxy/Gateway như “lá chắn” trước các dịch vụ backend và agents

Trước hết nhắc lại sơ đồ, nhưng nhìn qua lăng kính bảo vệ.

Hãy hình dung sơ đồ điển hình:

flowchart LR
  ChatGPT["ChatGPT / Widget"]
    --> GW["MCP Gateway (Auth, Rate Limit, Logs)"]

  GW --> GiftAPI["Gift REST API (gợi ý quà tặng)"]
  GW --> CommerceAPI["Commerce REST API (checkout, ACP)"]
  GW --> Analytics["Analytics Service / REST API"]

  GW --> Queue["Hàng đợi tác vụ"]
  Queue --> Worker["Background workers"]

Gateway đứng giữa thế giới bên ngoài (ChatGPT, webhooks, client thử nghiệm) và mọi thứ còn lại. Nó:

  • nhìn thấy toàn bộ request đi vào;
  • là nơi đầu tiên kiểm tra token và định dạng request;
  • có thể loại bỏ những thứ chắc chắn không hợp lệ (host lạ, path kỳ quặc, body quá lớn);
  • quyết định có nên gửi request tới dịch vụ REST/HTTP nội bộ nào hay không.

Ở lớp này cũng có:

  • rate limiting — giới hạn số request được thực hiện trong một khoảng thời gian;
  • backpressure cơ bản — từ chối nếu các dịch vụ bên dưới đã quá tải;
  • chuyển sang bất đồng bộ — việc nặng đưa ngay vào hàng đợi, trả lời client: “đã nhận, vui lòng chờ”.

Tức là gateway không chỉ là “router” mà còn là “áo giáp chống đạn”. Điều quan trọng — đừng biến nó thành “monolith của toàn bộ nghiệp vụ”, điều này chúng ta đã nói ở bài trước.

3. Những luồng traffic nào cần kiểm soát

Trong hệ sinh thái ChatGPT App thường có ba loại traffic chính mà chúng ta quan tâm về mặt giới hạn và bảo vệ.

Thứ nhất, MCP tool‑calls từ ChatGPT. Đây là mọi thứ đến qua giao thức MCP: các lần gọi suggest_gifts, get_product_details, create_checkout_session và các công cụ khác. Mô hình có thể tạo ra chúng khá nhanh, đặc biệt khi dưới nắp còn có Agents.

Thứ hai, các request đi ra từ backend của chúng ta đến API bên ngoài. Bên trong dịch vụ có thể có rate limit riêng cho hệ thống bên thứ ba: catalog, logistics, thanh toán. Vi phạm chúng — bị chặn, bị phạt hoặc giảm chất lượng.

Thứ ba, webhooks đi vào — thông báo từ ACP, các nhà cung cấp thanh toán (Stripe, v.v.), giao vận. Chúng đến độc lập với hoạt động của người dùng. Nếu endpoint của chúng ta chậm hoặc trả lỗi, hệ thống bên ngoài sẽ bắt đầu retry và có thể tạo ra “cơn bão” thông báo lặp lại.

Với GiftGenius, điều này trông như sau:

  • người dùng và mô hình gọi suggest_giftsfind_similar_gifts khá nhiều;
  • checkout‑tool gọi ACP/backend thương mại;
  • sau khi thanh toán, hệ thống thanh toán gửi webhooks payment.succeeded / payment.failed.

Tất cả các luồng này hội tụ về một điểm — Gateway, vì vậy hợp lý là đặt “bộ đếm, bộ lọc và điểm chặn” ngay tại đây.

4. Rate limiting: lớp bảo vệ cơ bản và tiết kiệm chi phí

Rate limiting là gì trong ngữ cảnh của chúng ta

Rate limiting là cơ chế giới hạn số lượng request từ một client cụ thể trong một đơn vị thời gian. Ý tưởng cũ như Internet, nhưng trong ngữ cảnh ChatGPT Apps, nó giải quyết ngay ba việc:

  • không để một client (hoặc một bug) đánh sập dịch vụ của bạn;
  • giúp tuân thủ hạn mức API bên ngoài;
  • bảo vệ ví tiền của bạn khỏi các lần gọi mô hình mất kiểm soát.

Các thuật toán kinh điển:

  • cửa sổ cố định (Fixed Window),
  • cửa sổ trượt (Sliding Window),
  • xô token (Token Bucket),

chúng ta chỉ cần nắm khái niệm: “trong một phút không quá N request”, “mỗi request tiêu tốn một token, token được bổ sung với tốc độ X mỗi giây”, v.v. Việc hiện thực thường do thư viện hoặc API Gateway đảm nhiệm.

Đặt giới hạn ở đâu

Giới hạn có thể đặt ở nhiều mức.

Ở mức reverse proxy (Nginx, Cloudflare, AWS API Gateway) tiện để:

  • chặn lưu lượng hỗn loạn theo IP;
  • giới hạn kích thước body request;
  • bảo vệ khỏi các pattern DDoS đơn giản.

Ở mức MCP Gateway (ứng dụng) có ích để làm rate limiting “có ý nghĩa” hơn:

  • theo người dùng (userId lấy từ token),
  • theo tổ chức (tenantId),
  • theo loại thao tác (ví dụ create_checkout_session giới hạn chặt, search — nới hơn),
  • theo nguồn (webhook vs tool‑call).

Và có thể bổ sung giới hạn ngay trong các microservice cho các thao tác đặc biệt đắt đỏ, nhưng đó là mức chi tiết tiếp theo.

Chọn khóa giới hạn như thế nào

Lỗi phổ biến nhất — giới hạn theo địa chỉ IP. Trong trường hợp ChatGPT điều này khá vô dụng:

  • tất cả request có thể đi từ cùng một dải IP của OpenAI,
  • những người dùng khác nhau sẽ “ngồi” sau cùng một IP.

Điều chúng ta quan tâm hơn:

  • userId — người dùng cụ thể trong ứng dụng của bạn;
  • tenantId — tổ chức (nếu bạn làm B2B và một chat được nhiều nhân viên dùng);
  • API token hoặc clientId, nếu bạn có nhiều tích hợp.

Trong GiftGenius, thường userId + tenantId lấy từ token mà ChatGPT truyền trong các cuộc gọi MCP là đủ.

Triển khai rate limiting đơn giản bằng TypeScript

Giả sử chúng ta có một MCP Gateway nhỏ dùng Express. Thêm rate limiting đơn giản: không quá 30 tool‑calls mỗi phút cho một người dùng.

// Rate limiting sơ khai: N request/phút theo userId
const WINDOW_MS = 60_000;
const MAX = 30;
const hits = new Map<string, { ts: number; count: number }>();

function rateLimit(req: Request, res: Response, next: NextFunction) {
  const userId = (req.headers["x-user-id"] as string) ?? "anonymous";
  const now = Date.now();
  const rec = hits.get(userId) ?? { ts: now, count: 0 };

  if (now - rec.ts > WINDOW_MS) {      // Cửa sổ "hết hạn" — bắt đầu lại
    rec.ts = now;
    rec.count = 0;
  }
  rec.count += 1;
  hits.set(userId, rec);

  if (rec.count > MAX) {
    return res.status(429).json({
      error: "rate_limit_exceeded",
      retryAfterSec: 60,
      message: "Too many tool calls, please retry later."
    });
  }
  next();
}

Và giờ dùng nó trong route MCP:

// Áp dụng middleware cho tất cả MCP tool-calls
app.post("/mcp/tools/call", rateLimit, async (req, res) => {
  const result = await callBackendForTool(req.body); // Gọi REST tới Gift/Commerce/Analytics API
  res.json(result);
});

Các điểm chính:

  • chúng ta trả về lỗi có ý nghĩa (error: "rate_limit_exceeded") chứ không chỉ 500;
  • mô hình có thể đọc lỗi này, hiểu chuyện gì xảy ra và giải thích đúng cho người dùng, thay vì bịa ra.

Trong production thực tế, bộ đếm dĩ nhiên không sống trong bộ nhớ của một process, mà ở Redis hoặc kho dùng chung khác để hoạt động trong cluster. Nhưng để hiểu nguyên lý thì vậy là đủ.

Rate limiting và giới hạn ở lớp gateway bảo vệ chúng ta khỏi “tuyết lở” request, nhưng chúng không giải quyết vấn đề khác — một số thao tác vẫn có thể rất nặng và chạy lâu. Khi đó HTTP đồng bộ không còn đủ, và hàng đợi cùng tác vụ bất đồng bộ xuất hiện.

5. Hàng đợi và tác vụ bất đồng bộ: khi không thể đồng bộ nữa

Vấn đề timeout của ChatGPT

Ngay cả khi bạn đã cấu hình rate limiting cẩn thận, ChatGPT (và nói chung các HTTP client) không thích đáp ứng quá lâu. Nền tảng giới hạn thời gian thực thi tool‑call, và nếu bạn chờ đến khi “siêu thuật toán gợi ý” nào đó xong thì:

  • người dùng sẽ thấy spinner mãi mãi;
  • nền tảng cắt request do timeout;
  • mô hình cho rằng “đã có trục trặc” và bắt đầu bịa lời giải thích.

Giải pháp: chuyển các thao tác nặng sang chế độ bất đồng bộ. Mẫu kinh điển:

  1. Gateway nhận request.
  2. Đưa tác vụ vào hàng đợi.
  3. Trả về ngay đáp ứng 202 Accepted với jobId.
  4. Worker riêng lấy tác vụ từ hàng đợi và xử lý.
  5. Client (widget của chúng ta hoặc thậm chí ChatGPT qua một tool khác) định kỳ hỏi trạng thái theo jobId hoặc nhận thông báo qua sự kiện MCP.

Trong ngữ cảnh ChatGPT App, điều này thường là hai công cụ: tool đầu nhận request, đưa tác vụ vào hàng đợi và trả về jobId, tool thứ hai cho mô hình hoặc widget kiểm tra trạng thái theo jobId và lấy kết quả. Ngoài ra có thể nhân bản sự kiện tiến độ qua thông báo MCP.

Mini‑queue cho GiftGenius (ví dụ code)

Giả sử chúng ta có công cụ nặng generate_large_gift_report, có thể chạy hàng chục giây. Trong App thực, nó chỉ trả jobId, còn tool riêng get_report_status cho phép mô hình hoặc widget biết trạng thái và lấy kết quả theo jobId. Ở Gateway, ta làm endpoint riêng với hàng đợi.

type Job = { id: string; payload: any };
const queue: Job[] = [];
const MAX_QUEUE = 100;

app.post("/mcp/tools/generate_report", (req, res) => {
  if (queue.length >= MAX_QUEUE) {
    return res.status(503).json({
      error: "system_busy",
      message: "System is busy, please retry later."
    });
  }

  const job: Job = { id: crypto.randomUUID(), payload: req.body };
  queue.push(job);
  res.status(202).json({ jobId: job.id, status: "accepted" });
});

Và một worker tối giản, cứ 200 ms lấy một tác vụ:

async function processJob(job: Job) {
  // Ở đây gọi dịch vụ backend thực hoặc workflow agent qua REST
  await handleHeavyGiftReport(job.payload);
}

setInterval(async () => {
  const job = queue.shift();
  if (!job) return;
  await processJob(job);
}, 200);

Rõ ràng đây là ví dụ đơn giản hóa mạnh:

  • trong thực tế hàng đợi sống trong Redis, SQS, Kafka, v.v.;
  • trạng thái tác vụ lưu ở nơi nào đó để có thể truy vấn;
  • thường có nhiều worker.

Nhưng khái niệm đã rõ: Gateway không giữ request mở cho đến khi xong. Nó nhận, đưa vào xử lý và trả lời nhanh.

6. Backpressure: làm sao không “chìm” trong chính hàng đợi của mình

Backpressure khác gì với rate limiting

Rate limiting trả lời câu hỏi: “một client có thể thực hiện bao nhiêu request trong một khoảng thời gian?”. Đó là bảo vệ khỏi “một người dùng quá tích cực” hoặc bug ở phía một client cụ thể.

Còn backpressure nói: “hệ thống của chúng ta có thể tiêu hóa tổng cộng bao nhiêu tác vụ/request đồng thời mà không vỡ?”. Đây là về tổng tải, bất kể đến từ ai.

Ví dụ:

  • rate limiting: “một người dùng không thể gọi suggest_gifts quá 30 lần/phút”;
  • backpressure: “không thể có quá 100 tác vụ chưa xử lý trong hàng đợi, nếu không chúng ta bắt đầu từ chối request mới”.

Lý tưởng là hai cơ chế này bổ sung cho nhau: rate limit giữ client trong khuôn khổ, backpressure cứu hệ thống nếu vẫn có rất nhiều người dùng đổ vào.

Triển khai đơn giản giới hạn số tác vụ đang hoạt động

Một cách backpressure đơn giản — giới hạn số cuộc gọi đang hoạt động ở bên dưới. Ví dụ: không giữ đồng thời quá 50 tool‑calls đang hoạt động tới một dịch vụ backend/REST cụ thể (Gift API, Commerce API v.v.).

let activeCalls = 0;
const MAX_ACTIVE = 50;

app.post("/mcp/tools/call", async (req, res) => {
    if (activeCalls >= MAX_ACTIVE) {
        return res.status(429).json({
            error: "gateway_overloaded",
            message: "Gateway is temporarily overloaded, please retry later."
        });
    }

    activeCalls += 1;
    try {
        const result = await callBackendForTool(req.body); // Gọi REST tới Gift/Commerce/Analytics API
        res.json(result);
    } catch (err) {
        console.error("Tool call error", err);
        res.status(500).json({ error: "internal_error" });
    } finally {
        activeCalls -= 1;
    }
});

Điều gì đang xảy ra ở đây:

  • khi số request đang thực thi đồng thời nhỏ hơn MAX_ACTIVE, ta cho phép call mới đi qua;
  • nếu đạt trần, trả về ngay lỗi có ý nghĩa;
  • cần giảm bộ đếm trong finally để không “mất slot” khi có lỗi.

Đây chính là backpressure tối giản: chúng ta nói thẳng với client: “lúc này tôi không thể, hãy thử lại sau”, thay vì nhận vô tội vạ và rồi “chết”.

Về sau có thể:

  • đặt MAX_ACTIVE khác nhau cho các loại thao tác khác nhau (ví dụ luôn ưu tiên cho checkout, còn tạo báo cáo thì giới hạn chặt hơn),
  • chuyển đổi giới hạn theo thời gian thực tùy metric tải.

7. Webhooks và “bão”: bảo vệ các sự kiện đi vào

Trước đó chúng ta chủ yếu nhìn vào những request do chúng ta hoặc ChatGPT khởi phát (tool‑calls, request đi ra, async‑job). Nhưng trong thực tế còn một nguồn tải quan trọng khác lên Gateway — webhooks đi vào từ hệ thống bên ngoài.

Webhooks là mặt còn lại của vấn đề: nếu tool‑calls do ta (thông qua mô hình) khởi phát, thì webhooks do dịch vụ bên ngoài khởi phát. Đó là loại traffic thứ ba trong mục 4, mà ta không kiểm soát được về thời điểm và tần suất, nhưng buộc phải tiêu hóa mà không sập. Cổng thanh toán, ACP, logistics — tất cả gửi thông báo (webhooks) đến endpoint của ta khi có mỗi thay đổi quan trọng: “thanh toán thành công”, “đơn hàng được tạo”, “trạng thái giao hàng cập nhật”.

Vấn đề bắt đầu khi:

  • endpoint của ta trả lời chậm;
  • trả lỗi;
  • đôi khi không sẵn sàng.

Khi đó, dịch vụ bên ngoài theo best practices sẽ retry. Và nếu xui, bạn sẽ nhận “cơn bão” webhooks — hàng chục, hàng trăm sự kiện lặp lại cố “gõ cửa” bằng mọi giá.

Để không “chết” vì sự quan tâm ấy, ở Gateway nên:

  1. Giới hạn webhooks đi vào theo nguồn: ví dụ “không quá 10 sự kiện/phút cho một event_type từ từng nhà cung cấp”.
  2. Kiểm tra chữ ký trước khi parse JSON: chữ ký HMAC hoặc cơ chế tương tự giúp loại bỏ request giả.
  3. Xử lý sự kiện theo cách idempotent: theo event_id hoặc trường tương tự để sự kiện lặp không tạo trùng đơn hàng hay thanh toán.
  4. Khi bão mạnh, bật backpressure bổ sung: tạm trả “503: hãy thử lại sau” nếu các dịch vụ downstream không kịp.

Ví dụ tối giản (ý tưởng, không phải code production):

app.post("/webhooks/stripe", rateLimitWebhook, (req, res) => {
    const sig = req.headers["stripe-signature"] as string;
    if (!isValidSignature(req.rawBody, sig)) {
        return res.status(400).send("Invalid signature");
    }

    const event = JSON.parse(req.body.toString());
    if (isAlreadyProcessed(event.id)) {
        return res.json({ received: true }); // idempotent
    }

    handleStripeEvent(event);
    res.json({ received: true });
});

Tại Gateway, chúng ta:

  • áp dụng chính sách rate limiting riêng cho webhooks;
  • xác thực chữ ký trước khi tin vào nội dung;
  • bảo vệ khỏi trùng lặp qua isAlreadyProcessed.

8. Áp dụng cho GiftGenius: ví dụ chính sách giới hạn và hàng đợi

Giờ hãy rời khỏi lý thuyết và xem điều này trông ra sao với GiftGenius của chúng ta.

Giả sử có ba kịch bản chính:

  1. Tìm kiếm quà tặng (suggest_gifts, find_similar_gifts).
  2. Tạo đơn hàng / checkout (create_checkout_session, confirm_order).
  3. Nhận webhooks từ nhà cung cấp thanh toán và ACP.

Với mỗi kịch bản, hợp lý là xác định:

  • giới hạn tính theo khóa nào;
  • cho phép bao nhiêu request mỗi phút;
  • làm gì khi vượt quá.

Ví dụ:

Kịch bản Khóa giới hạn Giới hạn mỗi phút Hành vi khi vượt quá
Tìm kiếm quà tặng userId 30 429 + gợi ý “thu hẹp tham số tìm kiếm”
Tạo đơn hàng userId + tenantId 5 429 + thông điệp “quá nhiều lần thử, hãy kiểm tra đơn hàng”
Webhooks đi vào provider + eventType 10 429/503, ghi log, có thể degrade

Với webhooks, thường hợp lý hơn là giới hạn theo tổ hợp “nhà cung cấp + loại sự kiện”, còn loại trừ trùng lặp bằng cơ chế idempotent riêng dựa trên event_id.

Trong code, điều này thành các middleware khác nhau: rateLimitSearch, rateLimitCheckout, rateLimitWebhook.

Với các thao tác nặng, như “tạo báo cáo PDF lớn về quà tặng theo năm”, chúng ta dùng hàng đợi và mẫu bất đồng bộ như đã nêu. Khi đó Gateway:

  • nhận request từ ChatGPT;
  • đưa tác vụ vào hàng đợi;
  • trả về jobId và gợi ý cho mô hình cách lấy trạng thái;
  • giới hạn kích thước hàng đợi (backpressure) để không làm tràn hệ thống.

Cần nhớ: cả rate limiting lẫn backpressure — không chỉ về bảo mật và độ tin cậy, mà còn về UX. Dễ chịu hơn nhiều khi trợ lý nói: “Dịch vụ đang quá tải, thử lại sau một phút nhé”, thay vì ngồi chờ đến timeout hoặc thấy “Internal Server Error”.

9. Mini‑thực hành: thêm bảo vệ vào MCP Gateway của chúng ta

Để tài liệu không chỉ là lý thuyết, hãy làm một mini‑thực hành bạn có thể áp dụng trong dự án học tập của mình.

Rate limiting cho mọi MCP tool‑calls

Thêm middleware rateLimit (như ở trên) và gắn vào /mcp/tools/call. Ban đầu có thể đặt giới hạn rất đơn giản: 30 request/phút theo userId. Sau đó hãy thử:

  • giảm giới hạn và xem App và mô hình phản ứng ra sao;
  • đặt giới hạn khác nhau cho các loại tool khác nhau, ví dụ truyền toolName vào middleware.

Backpressure tối giản theo số cuộc gọi đang hoạt động

Thêm bộ đếm activeCalls và giới hạn MAX_ACTIVE. Thử mô phỏng tải (ví dụ script gửi một lô request) và xem khi nào Gateway bắt đầu trả gateway_overloaded.

Điều quan trọng là hành vi: bạn không đợi mọi thứ sụp đổ, mà từ chối nhận tác vụ mới, nói thẳng với client rằng hiện quá nóng.

Hàng đợi cho công cụ nặng

Chọn một thao tác nặng (hoặc cố ý làm “nặng” — chèn setTimeout/một fetch lâu) và chuyển nó sang mẫu “hàng đợi + jobId”. Tối thiểu:

  • endpoint POST /mcp/tools/generate_report — đưa tác vụ vào hàng đợi và trả về jobId;
  • endpoint GET /jobs/:id — trả trạng thái (pending, done, error, và có thể cả kết quả);
  • worker, cứ mỗi X mili‑giây gọi processJob.

Vậy là đủ để bạn hình dung tích hợp thực với BullMQ hay một engine hàng đợi khác sẽ như thế nào.

10. Các lỗi điển hình khi bảo vệ perimeter

Lỗi số 1: Chỉ giới hạn theo IP.
Trong thế giới ChatGPT Apps điều này gần như vô dụng: phần lớn request đến từ địa chỉ của OpenAI, và mọi người dùng của bạn sẽ ở cùng một IP. Kết quả là một ai đó “đốt” hết giới hạn cho tất cả, còn thủ phạm thật sự lại không biết. Đúng hơn là giới hạn theo userId, tenantId hoặc token, còn IP chỉ dùng như bộ lọc rất thô ở reverse proxy.

Lỗi số 2: Trả về 500 trần thay vì lỗi có ý nghĩa.
Nếu khi vượt giới hạn hoặc quá tải bạn chỉ gửi 500 Internal Server Error, mô hình không hiểu gì và bắt đầu bịa. Trong khi lỗi có cấu trúc với mã (rate_limit_exceeded, gateway_overloaded) và mô tả dễ hiểu cho phép LLM giải thích đúng cho người dùng và, nếu cần, thử lại sau.

Lỗi số 3: Làm hàng đợi vô hạn không có backpressure.
Đôi khi nghĩ: “cứ đưa hết vào hàng đợi, rồi tính”. Thực tế hàng đợi phình tới hàng nghìn tác vụ, độ trễ tăng, bộ nhớ cạn, người dùng vẫn không thấy kết quả. Luôn giới hạn kích thước hàng đợi và số thao tác đang hoạt động. Tốt hơn là từ chối thẳng request mới với 503 hoặc 429, hơn là biến hàng đợi thành “lỗ đen”.

Lỗi số 4: Chỉ dựa vào rate limiting và bỏ qua webhooks.
Nhiều người chỉ bảo vệ traffic vào từ ChatGPT, còn webhooks để “tự nhiên”. Khi nhà cung cấp thanh toán bắt đầu retry, chính webhooks có thể tạo ra cơn bão thật sự. Endpoint webhooks cần giới hạn riêng, kiểm tra chữ ký và xử lý idempotent. Nếu không, rất dễ có cả chục bản sao của cùng một đơn hàng.

Lỗi số 5: Lưu mọi bộ đếm và hàng đợi chỉ trong bộ nhớ của một instance.
Với dự án học tập thì ổn, nhưng trong production khi scale Gateway lên nhiều instance, bộ đếm trên mỗi nút sẽ “sống cuộc đời riêng”, giới hạn không còn là toàn cục, còn việc khởi động lại nút sẽ xóa sạch hàng đợi. Trong hệ thống thực, người ta dùng kho chung (Redis, hàng đợi đám mây, v.v.) để lưu trạng thái giới hạn và hàng đợi. Chúng ta sẽ nói thêm trong bài về scale và production.

Lỗi số 6: Nhồi logic nghiệp vụ vào Gateway “vì nó đã ở giữa mọi nơi”.
Đôi khi có cám dỗ: “hay chúng ta quyết định luôn gợi ý quà nào ngay trong Gateway, vì request cũng vào đó”. Kết quả gateway biến thành monolith đầy logic, vừa là router, vừa là “bộ não” nghiệp vụ, vừa là logger. Điều đó làm phức tạp scale và vận hành. Gateway nên giữ vai trò lớp mạng/hạ tầng: xác thực, phân quyền, giới hạn, cache, định tuyến — được; gợi ý quà — không.

Lỗi số 7: Nghĩ rằng “chúng tôi nhỏ, chuyện đó không liên quan”.
Nhiều người nghĩ: “Chúng tôi không có triệu người dùng, khỏi cần gateway/giới hạn”. Thực ra chỉ một bug trong code client (hoặc trong prompt khiến mô hình gọi tool vòng vòng) cũng đủ tạo một “tận thế” cục bộ nhỏ. Rate limiting cơ bản và backpressure tối thiểu — không phải xa xỉ, mà là “bàn chải đánh răng” của production: dùng ngay từ đầu, trước khi đau.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION