CodeGym /Các khóa học /ChatGPT Apps /Độ tin cậy của luồng: rate‑limits, backpressure và giám s...

Độ tin cậy của luồng: rate‑limits, backpressure và giám sát sự kiện

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

1. Vì sao luồng đặc biệt nhạy cảm với tải

Ở các phần trước, chúng ta đã xem cách tổ chức sự kiện MCP, các trạng thái job.progress/job.completed, các async job và kênh stream (SSE/HTTP-stream) cho GiftGenius. Giờ quan trọng là nhìn xem kiến trúc này sẽ ra sao dưới tải thực tế.

Khi bạn chỉ có một người dùng thỉnh thoảng chạy việc chọn quà, mọi thứ trông rất ổn. Nhưng ngay khi GiftGenius vào production và đồng thời nhận hàng trăm yêu cầu “chọn quà cho tất cả nhân viên cho tiệc công ty”, bạn sẽ đột ngột phát hiện rằng:

  • trên server có hàng trăm kết nối SSE sống lâu;
  • các worker hăm hở gửi job.progress cho mọi thay đổi nhỏ;
  • log tăng lên hàng gigabyte mỗi ngày;
  • UI của người dùng bắt đầu giật lag, dù “server có vẻ không sập”.

Một yêu cầu HTTP cổ điển sống trong mili giây hoặc vài giây. Luồng SSE hay HTTP‑stream có thể sống hàng phút, thậm chí hàng giờ. Nó giữ kết nối, bộ nhớ, các file descriptor. Mỗi event gửi đi đều là tuần tự hóa JSON, sao chép qua mạng, làm việc cho GC. Nếu coi đó như “chỉ là thêm một console.log ở backend”, hệ thống của bạn sẽ rất nhanh biến thành máy sưởi.

Sự kiện MCP còn có một đặc điểm nữa: chúng thường được tạo lặp lại nhiều lần cho cùng một tác vụ. Worker cập nhật tiến độ mỗi 0.1% sẽ tạo ra số lượng sự kiện đáng kể cho một job. Cuối cùng bạn nhận được “nhiễu”: vô số thông điệp nhỏ, những thứ:

  • làm nặng mạng và CPU;
  • làm nghẽn hàng đợi và bộ đệm;
  • khiến việc gỡ lỗi và phân tích log trở nên đau đớn.

Vì vậy, cả với luồng và sự kiện MCP, hãy coi chúng nghiêm túc như truy vấn cơ sở dữ liệu hay gọi model: đó là tài nguyên đắt giá, cần được chuẩn hóa, kiểm soát và giám sát.

Để xử lý được, hãy luôn ghi nhớ ba chủ đề lớn:

  1. Rate‑limits — giới hạn số lượng và tần suất bạn có thể cho phép tạo và gửi sự kiện/luồng.
  2. Backpressure — phản ứng khi bên tiêu thụ không theo kịp bên sản xuất.
  3. Giám sát và metric — đo lường những gì đang xảy ra và kịp thời nhận ra khi mọi thứ bắt đầu “sôi”.

2. Rate‑limiting cho luồng và sự kiện

Bắt đầu từ thứ dễ hiểu nhất — giới hạn.

Điều quan trọng là trong kịch bản stream, “kẻ gây rối nguy hiểm” thường không phải client mà là server. Trong REST API thông thường, bạn giới hạn số yêu cầu đến server để người dùng không biến thành DDoS. Trong thế giới MCP và stream, rất dễ tạo DDoS ngược: worker hoặc server MCP bắn vào client hàng nghìn sự kiện mỗi giây.

Cần những loại giới hạn nào

Thường nghĩ theo ba chiều.

Thứ nhất, giới hạn theo người dùng hoặc phiên. Không thể cho phép một người dùng mở hai chục wizard GiftGenius song song, mỗi cái với một luồng SSE riêng. Một giới hạn hợp lý — vài luồng đang hoạt động cho mỗi phiên và giới hạn số job ở trạng thái running cho mỗi người dùng hoặc tenant.

Thứ hai, giới hạn cho một job. Ở đây ta quan tâm đến tần suất sự kiện. Đủ để gửi job.progress không thường hơn một lần mỗi N mili giây hoặc chỉ khi có thay đổi đáng kể, ví dụ mỗi 5%. Không cần gửi thông điệp cho mỗi sản phẩm được xử lý trong danh mục. Cũng nên giới hạn kích thước payload: sự kiện tiến độ không nên mang theo hàng megabyte văn bản.

Thứ ba, giới hạn theo IP hoặc tổ chức. Đây là biện pháp chống lạm dụng khi ai đó chạy script spam task, hoặc khi App của bạn bỗng nhiên nổi tiếng. Lúc này các cơ chế quen thuộc của API gateway và proxy vào cuộc.

Triển khai đơn giản cho giới hạn tần suất sự kiện

Xem xét worker GiftGenius chạy nền để chọn quà theo danh sách người nhận dài và định kỳ gửi tiến độ qua MCP‑notification event/progress. Chúng ta muốn các sự kiện chỉ được gửi không thường hơn một lần mỗi 500 mili giây và chỉ khi phần trăm thay đổi ít nhất 5 điểm.

Pseudo code TS cho worker:

// giả sử có mcpClient.sendNotification(...)
let lastSentPercent = 0;
let lastSentAt = 0;

function reportProgress(jobId: string, percent: number, message: string) {
  const now = Date.now();
  const percentDelta = percent - lastSentPercent;
  const timeDelta = now - lastSentAt;

  // chỉ gửi nếu đã trôi qua >= 500 ms HOẶC tăng >= 5%
  if (percentDelta >= 5 || timeDelta >= 500) {
    mcpClient.sendNotification("event/progress", {
      jobId,
      percent,
      message,
    });
    lastSentPercent = percent;
    lastSentAt = now;
  }
}

Cách tiếp cận này gọi là throttling: chúng ta “làm thưa” luồng sự kiện theo thời gian và theo mức độ thay đổi.

Nếu bạn chia theo giai đoạn (“Giai đoạn 1 trong 3”, “Giai đoạn 2 trong 3”), logic còn đơn giản hơn: chỉ gửi sự kiện khi đổi giai đoạn.

Giới hạn số luồng mở đồng thời

Ở phía server MCP, có lẽ bạn có HTTP handler cho SSE:

// app/api/events/[userId]/route.ts (Next.js 16 App Router)
export async function GET(
  req: Request,
  { params }: { params: { userId: string } },
) {
  const userId = params.userId;

  if (!canOpenMoreStreams(userId)) {
    return new Response("Too many streams", { status: 429 });
  }

  const stream = new ReadableStream({
    start(controller) {
      registerSseClient(userId, controller);
    },
    cancel() {
      unregisterSseClient(userId);
    },
  });

  return new Response(stream, {
    headers: { "Content-Type": "text/event-stream" },
  });
}

Hàm canOpenMoreStreams có thể kiểm tra số kết nối đang mở hiện tại cho người dùng và so với ngưỡng (ví dụ không quá ba luồng song song). Nếu vượt giới hạn, trả về 429 và trong hướng dẫn GPT, hãy giải thích cho model rằng trong tình huống này tốt hơn là không khởi chạy thêm một wizard dài nữa, mà gợi ý người dùng rằng “đã có phiên chọn quà đang chạy, hãy chờ hoàn tất”.

Trong hệ thống nhỏ, có thể thực hiện kiểm tra dạng này trong bộ nhớ của tiến trình. Trong hạ tầng nghiêm túc hơn, nó sẽ được chuyển sang MCP‑gateway hoặc dịch vụ rate‑limit riêng.

3. Backpressure: làm gì khi bên tiêu thụ không kịp

Rate‑limits giới hạn số sự kiện chúng ta muốn sản xuất. Nhưng ngay cả khi đã giới hạn cẩn thận, vẫn có thể xảy ra tình huống bên tiêu thụ “bị nghẹt”: người dùng có mạng di động kém, tab trình duyệt bị đơ, hoặc ChatGPT đang rất tải.

Backpressure là phản ứng của hệ thống trước việc bên tiêu thụ không kịp. Thay vì tích lũy dữ liệu vô hạn và sớm muộn gì cũng chết vì OOM, chúng ta chủ động:

  • giảm tốc;
  • tổng hợp sự kiện;
  • bỏ bớt cái ít quan trọng.

Áp lực phát sinh ở đâu

Kịch bản điển hình cho GiftGenius có thể như sau. Worker ghi sự kiện vào hàng đợi (ví dụ Redis Streams hoặc chỉ là một bảng trong CSDL), server MCP đọc ra và đẩy vào kênh SSE. Nếu client chậm (3G, laptop cũ, nhiều tab khác), buffer TCP bắt đầu đầy, tiến trình Node không kịp rút hết hàng đợi và cuối cùng tích lũy sự kiện trong bộ nhớ. Sau đó bạn thấy điều quen thuộc:

FATAL ERROR: Ineffective mark-compacts near heap limit

Backpressure ở tầng mạng (TCP) vốn đã có, nhưng nó không biết gì về thực thể miền của bạn. Nó chỉ nói: “Ê, chậm lại, buffer đầy rồi”. Nhiệm vụ của chúng ta là diễn giải điều này ở cấp sự kiện MCP.

Buffer có giới hạn và loại bỏ sự kiện

Đối với tiến độ và trạng thái, ta có một đặc điểm dễ chịu: không phải sự kiện nào cũng có giá trị như nhau. Người dùng cần biết phần trăm mới nhất, chứ không cần lịch sử mọi mốc “51%, 52%, 53%, 54%”. Điều này có nghĩa là ta có thể thoải mái drop một phần sự kiện và chỉ gửi cái cuối cùng.

Giả sử có một lớp nhận sự kiện tiến độ từ worker và đặt chúng vào một buffer cho mỗi jobId:

type ProgressEvent = { jobId: string; percent: number; message: string };

const progressBuffers = new Map<string, ProgressEvent[]>();
const MAX_BUFFER = 10;

function bufferProgress(event: ProgressEvent) {
  const buffer = progressBuffers.get(event.jobId) ?? [];
  buffer.push(event);

  // giới hạn kích thước bộ đệm
  if (buffer.length > MAX_BUFFER) {
    // chỉ giữ lại vài sự kiện cuối
    progressBuffers.set(event.jobId, buffer.slice(-MAX_BUFFER));
  } else {
    progressBuffers.set(event.jobId, buffer);
  }
}

Một timer riêng, ví dụ mỗi 500 ms, sẽ xem buffer và chỉ gửi sự kiện cuối cùng, bỏ qua phần còn lại:

setInterval(() => {
  for (const [jobId, buffer] of progressBuffers.entries()) {
    if (!buffer.length) continue;

    const last = buffer[buffer.length - 1];
    sendProgressToClient(last); // SSE/MCP notification

    progressBuffers.set(jobId, []); // dọn sạch
  }
}, 500);

Đây là ví dụ về chiến thuật conflation: gộp nhiều cập nhật thành một trạng thái hiện tại. Đối với tiến độ — đây là mẫu vàng.

Với các sự kiện kiểu “log” hoặc partial_result, chiến lược có thể khác. Ở đó mất sự kiện thường không chấp nhận được: văn bản log quan trọng, và một JSON chunk bị mất có thể phá cấu trúc dữ liệu. Trong các trường hợp này, bạn có thể:

  • tổng hợp thông điệp (nối vài dòng log thành một gói);
  • hoặc gửi tín hiệu điều khiển cho worker “hãm tốc độ tạo log”.

Trong hệ thống bất đồng bộ, phương án thứ hai khó hơn, nhưng cũng đáng để cân nhắc.

Giới hạn độ sâu hàng đợi

Backpressure không chỉ dừng ở buffer sự kiện ngay trước khi gửi. Cần xem tất cả hàng đợi trong hệ thống:

  • hàng đợi task chờ worker;
  • hàng đợi sự kiện giữa worker và server MCP;
  • các buffer bên trong thư viện streaming phía server.

Với mỗi hàng đợi, quan trọng là đặt một giới hạn độ sâu hợp lý. Nếu hàng đợi tràn, bạn hoặc bắt đầu trả cho client “hệ thống quá tải, hãy thử lại sau”, hoặc bỏ những job ít quan trọng hơn, hoặc chuyển một phần kịch bản sang “chế độ offline” (ví dụ tạo báo cáo và gửi liên kết sau).

Một thủ thuật thú vị khác — ưu tiên theo loại sự kiện. Khi quá tải, bạn có thể chỉ gửi job.completedjob.failed, còn job.progress thì hạ ưu tiên hoặc tắt hẳn.

4. Giám sát luồng và sự kiện

Không có đo lường, tất cả vẻ đẹp của rate‑limits và backpressure chỉ là “múa may”. Cần thấy được rằng số luồng tăng bất thường, sự kiện đi kèm độ trễ, và client rơi rụng theo nhóm.

Luồng hành xử khác yêu cầu HTTP thông thường: thời lượng có thể tính bằng phút hoặc giờ, nên các metric cổ điển như “request/second” và “độ trễ trung bình” không cho bức tranh đầy đủ.

Các metric then chốt

Với luồng SSE hoặc HTTP/stream, nên theo dõi một vài nhóm chỉ số.

  1. Metric kết nối. Hiện có bao nhiêu luồng SSE đang hoạt động? Trung bình một kết nối sống bao lâu? Phần trăm luồng kết thúc bằng lỗi hoặc timeout là bao nhiêu? Một cú tăng đột biến số kết nối hoạt động báo hiệu cơn bão lưu lượng hoặc rò rỉ tài nguyên (client không đóng kết nối). Một cú tụt mạnh — báo hiệu đứt hàng loạt (ví dụ sự cố mạng hoặc bug nghiêm trọng phía server).
  2. Metric sự kiện. Bạn gửi bao nhiêu sự kiện mỗi giây trên tất cả luồng (EPS — events per second)? Kích thước trung bình của một sự kiện là bao nhiêu? Bạn thấy bao nhiêu lỗi deserialize hoặc validate payload? Nếu đột nhiên thấy kích thước sự kiện tăng — có thể ai đó bắt đầu gửi vào job.progress cả văn bản báo cáo thay vì một chuỗi ngắn.
  3. Metric job. Phân bố theo trạng thái (pending, running, completed, failed, canceled), thời gian hoàn thành trung bình theo loại tác vụ, tỷ lệ job đi vào retry hoặc dead‑letter. Điều này giúp hiểu vấn đề không chỉ ở tầng mạng mà còn ở worker: API bên ngoài chậm lại, xuất hiện lỗi hàng loạt.
  4. Metric backpressure và chỉ số hệ thống. Trong hệ thống stream, thường theo dõi độ sâu buffer và hàng đợi giữa các thành phần, cũng như tỷ lệ thời gian luồng bị block do chờ bên tiêu thụ giải phóng chỗ. Nếu các hàng đợi gần như luôn đầy kịch trần, đó là tín hiệu rõ ràng hệ thống đang ở giới hạn. Cũng cần theo dõi chỉ số hệ thống: CPU và bộ nhớ trên các server lo việc streaming, và lỗi/timeout ở tầng mạng. Đôi khi chính băng thông mạng giữa server MCP và ChatGPT là nút thắt cổ chai.

Tổng hợp lại, bốn nhóm này cho bạn câu trả lời cho ba câu hỏi: hiện có bao nhiêu luồng đang sống, bạn đang đẩy bao nhiêu dữ liệu, các job hành xử ra sao và chính xác ở đâu hệ thống bắt đầu “nghẹt”.

Ghi log những gì

Log — trụ cột thứ hai của quan sát. Quan trọng là ghi log sự kiện và kết nối sao cho sau này có thể tập hợp lịch sử cho một job cụ thể.

Thường trong log cho mỗi sự kiện và luồng sẽ thêm:

  • jobId và/hoặc eventId;
  • userIdsessionId (nếu có đa tenant);
  • loại sự kiện (progress, completed, failed, resource.updated);
  • loại kênh (SSE hoặc HTTP/stream);
  • timestamp gửi và, nếu có thể, timestamp sinh sự kiện ở worker.

Như vậy có thể tính được lag: chênh lệch giữa thời điểm worker tạo sự kiện và thời điểm nó được đẩy vào socket. Sự tăng của lag này là chỉ báo tốt cho vấn đề backpressure.

Cần cẩn thận để log không trở thành nguồn quá tải. Với các sự kiện tần suất cao như job.progress, không phải lúc nào cũng hợp lý để log mọi sự kiện; có thể bật sampling — log mỗi sự kiện thứ N thay vì tất cả — hoặc tổng hợp thống kê.

Trong code, trông có thể như một helper đơn giản:

function logEvent(event: {
  type: string;
  jobId: string;
  userId?: string;
  channel: "sse" | "http-stream";
  payload: unknown;
}) {
  console.info({
    ...event,
    timestamp: new Date().toISOString(),
  });
}

Trong dự án thực tế, bạn sẽ bọc nó bằng thư viện structured logging, nhưng ý tưởng vẫn vậy: tối đa ngữ cảnh hữu ích trong mỗi bản ghi.

5. Alert và chính sách degrade

Khi đã có metric và log, bước tiếp theo là cấu hình alert và suy nghĩ hệ thống nên “degrade” thế nào khi gặp khó. Ý tưởng là thà thành thật hoạt động kém hơn còn hơn đột ngột sập.

Ví dụ alert

Với GiftGenius, hợp lý để theo dõi vài tình huống điển hình.

Thứ nhất, số lượng luồng hoạt động bất thường. Nếu bình thường bạn chỉ có vài chục kết nối SSE đang hoạt động, mà bỗng thành hàng nghìn, cần tìm hiểu điều gì đang xảy ra. Có thể bạn trở nên phổ biến, hoặc có bug khiến kết nối không được đóng.

Thứ hai, độ trễ giữa thời điểm job thực sự hoàn tất và thời điểm client nhận job.completed. Nếu độ trễ này vượt ngưỡng (ví dụ 510 giây), nghĩa là đâu đó giữa worker và client đang tích lũy sự kiện hoặc kết nối trục trặc.

Thứ ba, tỷ lệ job.failed hoặc job.canceled cao so với thành công. Nguyên nhân có thể ở worker (API ngoài bị lỗi, bug mới) hoặc do người dùng nhạy cảm hơn với độ trễ (họ hủy task thường xuyên hơn).

Cuối cùng, mức lỗi kết nối và đứt stream tăng: nếu số disconnect bất thường tăng, có thể mạng hoặc phía client có vấn đề, và nên nghĩ tới các kịch bản fallback.

Mẫu hình degrade

Khi hệ thống quá tải, có thể bật “chế độ tiết kiệm tài nguyên”. Điều này tốt hơn là trả 500 cho tất cả mọi thứ.

Mẫu phổ biến nhất — tần suất sự kiện thích ứng. Nếu thấy event‑rate (số sự kiện mỗi giây) tăng gấp mười lần so với bình thường và độ trễ trong hàng đợi bắt đầu tăng, hãy giảm tần suất sự kiện tiến độ. Từ mỗi 1% — chuyển thành mỗi 10%. Từ mỗi 500 ms — thành một lần mỗi 23 giây. Người dùng hoàn toàn sống ổn mà không cần tiến độ siêu chi tiết; còn UI treo cứng thì không.

Với những sự kiện ít quan trọng hơn — ví dụ resource.updated khi cập nhật nền feed sản phẩm — có thể tạm thời tắt gửi cho đến khi hệ thống hết tải.

Một cách khác — chuyển một phần kịch bản từ stream sang polling định kỳ. Nếu các kênh SSE sụp đổ, server MCP có thể gửi tới widget một sự kiện hệ thống như system.overloaded, và widget sẽ chuyển sang chiến lược “mỗi N giây gọi REST endpoint để hỏi trạng thái job”.

6. Mảnh ghép thực hành nhỏ cho GiftGenius

Để kết nối mọi thứ, giả sử ta đã có:

  • MCP‑tool startGiftSearch, tạo job và trả về jobId;
  • worker thực thi tìm kiếm và gửi event/progressevent/completed;
  • SSE endpoint /api/events/[userId] mà widget trong Next.js sẽ kết nối.

Hãy thêm một lớp bảo vệ đơn giản chống “bão sự kiện” và giám sát tối thiểu.

Giới hạn tiến độ theo bước và thời gian

Trong worker, ta thêm throttling và conflation như đã bàn ở trên. Giờ sự kiện chỉ gửi không thường hơn mỗi nửa giây và khi thay đổi ít nhất 5%.

Theo dõi số luồng đang mở

Ở SSE endpoint, lưu bộ đếm theo người dùng:

const activeStreams = new Map<string, number>();
const STREAM_LIMIT = 3;

function canOpenMoreStreams(userId: string) {
  const current = activeStreams.get(userId) ?? 0;
  return current < STREAM_LIMIT;
}

function registerSseClient(userId: string, controller: ReadableStreamDefaultController) {
  const current = activeStreams.get(userId) ?? 0;
  activeStreams.set(userId, current + 1);

  // lưu controller này vào một cấu trúc nào đó,
  // để sau này ghi sự kiện vào luồng này
}

function unregisterSseClient(userId: string) {
  const current = activeStreams.get(userId) ?? 1;
  activeStreams.set(userId, Math.max(0, current - 1));
}

Server có thể gửi metric về activeStreams.size tới Prometheus/Grafana hoặc bất kỳ hệ thống giám sát nào khác.

Metric event‑rate đơn giản nhất

Ban đầu, có thể đếm sơ sơ số sự kiện ta gửi như sau:

let eventsSentLastMinute = 0;

function sendProgressToClient(ev: ProgressEvent) {
  // ... tuần tự hóa và ghi vào luồng SSE
  eventsSentLastMinute++;
}

setInterval(() => {
  console.info({
    metric: "events_per_minute",
    value: eventsSentLastMinute,
    timestamp: new Date().toISOString(),
  });
  eventsSentLastMinute = 0;
}, 60_000);

Theo thời gian, có thể thay thế bằng các bộ đếm và alert “đúng chuẩn”, nhưng làm điểm khởi đầu như vậy đã là tốt.

Nếu gom tất cả các phần trên: giới hạn, backpressure, metric/alert và UX fallback hợp lý sẽ giúp GiftGenius của bạn không còn là “demo để trình diễn” mà chịu đựng được các cơn bão lưu lượng thực sự. Ở các module tiếp theo, nơi chúng ta nói về gateway, kiến trúc production và khả năng quan sát đầy đủ, các pattern này còn hữu ích nữa.

7. Lỗi điển hình khi làm việc với luồng, rate‑limits và giám sát

Lỗi #1: không có giới hạn cho số luồng và tần suất sự kiện.
Lập trình viên thêm SSE “cho đẹp”, worker thì chăm chỉ gửi tiến độ cho mỗi đối tượng được xử lý, và mọi thứ có vẻ chạy ổn trên demo. Nhưng khi người dùng thực sự tăng đột biến, server bắt đầu tiêu tốn phần lớn tài nguyên cho việc tuần tự hóa và truyền đi hàng nghìn sự kiện nhỏ xíu, còn UI trong ChatGPT biến thành trình chiếu.

Lỗi #2: cố buffer “tất cả mọi thứ” mà không có giới hạn.
Trong code xuất hiện một mảng vô hạn “sự kiện chưa gửi đi”, nó sẽ tăng cho đến khi client phục hồi. Spoiler: client không phục hồi, server chết trước. Bất kỳ buffer nào cũng phải có giới hạn cứng, và logic xử lý khi tràn phải rõ ràng.

Lỗi #3: đối xử như nhau với mọi loại sự kiện.
Tiến độ có thể tổng hợp và drop (phần trăm mới nhất quan trọng hơn lịch sử thay đổi). Với log và partial result thì không thể — mất một chunk có thể làm hỏng dữ liệu. Khi thiết kế hệ thống, hãy nhóm sự kiện theo mức quan trọng từ trước và nghĩ chiến lược cho từng nhóm khi quá tải.

Lỗi #4: thiếu khả năng quan sát.
Không có metric cho luồng đang hoạt động, không đếm event‑rate, trong log chỉ có “đã có lỗi”. Trong tình huống này, bạn chỉ biết vấn đề từ phản hồi người dùng và biểu đồ tải CPU. Thiết lập ít nhất metric cơ bản và log theo jobIdeventId — không phải xa xỉ, mà là cần thiết.

Lỗi #5: UX cứng nhắc, không tính đến degrade.
Widget và hướng dẫn GPT mặc định rằng luồng luôn sẵn có, tiến độ cập nhật “thời gian thực”, partial result đến đúng kịch bản. Ngay khi có vấn đề mạng, người dùng thấy thanh tiến độ “đóng băng” mà không có lời giải thích nào. Tốt hơn nhiều là đặt sẵn fallback trung thực: “Hiện có vấn đề với cập nhật trực tiếp, tôi vẫn sẽ tiếp tục chọn quà và sẽ báo khi xong” — và chuyển sang cập nhật thưa hơn hoặc polling.

Lỗi #6: tin rằng “người dùng của chúng tôi sẽ không tạo nhiều task đồng thời”.
Thực tế cho thấy nếu bạn không giới hạn số job và luồng song song, thế nào cũng có người mở năm tab, chạy mỗi tab một phiên chọn quà “tối đa” rồi đi uống cà phê. Tư duy “chắc là ổn” trong production gần như luôn kết thúc bằng việc làm quen với hệ thống giám sát trong tiếng động ầm ầm của alert.

1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Thông báo
Thông báo và các kịch bản streaming (sự kiện MCP)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION