Bộ nhớ đệm vẫn hoạt động, nhưng vẫn gây ra các cuộc gọi API trùng lặp

Bộ nhớ đệm không hề bị hỏng.

Tuy nhiên, ba yêu cầu đồng thời cho cùng một tên người dùng đã truy cập GitHub tới ba lần.

Điều này đã xảy ra trong CommitPulse, một API Next.js giúp chuyển đổi dữ liệu GitHub thành các huy hiệu SVG. Khi một tệp README trở nên nổi tiếng, hàng ngàn người sẽ xem huy hiệu đó cùng một lúc. Điều này tạo ra lưu lượng truy cập khổng lồ.

Bộ nhớ đệm hoạt động tốt với các yêu cầu tuần tự. Nó thất bại với các yêu cầu đồng thời.

Vấn đề nằm ở đây:

  • Yêu cầu A kiểm tra bộ nhớ đệm. Kết quả là không tìm thấy (cache miss). Yêu cầu A bắt đầu lấy dữ liệu từ GitHub.
  • Yêu cầu B đến sau đó 5ms. Nó kiểm tra bộ nhớ đệm. Kết quả vẫn là không tìm thấy vì yêu cầu A chưa hoàn tất. Yêu cầu B bắt đầu một lần lấy dữ liệu thứ hai.
  • Yêu cầu C đến sau đó 10ms. Nó cũng thấy bộ nhớ đệm trống và bắt đầu lần lấy dữ liệu thứ ba.

Đây chính là vấn đề thundering herd. Một lỗi cache miss dưới tải trọng cao sẽ kích hoạt một loạt các cuộc gọi giống hệt nhau đến nhà cung cấp thượng nguồn (upstream provider) của bạn. Nếu bạn sử dụng một API bị giới hạn tốc độ (rate-limited) như GitHub, điều này có thể làm cạn kiệt hạn mức của bạn ngay lập tức.

Giải pháp là gộp các yêu cầu (request coalescing).

Bạn phải theo dõi các yêu cầu đang chờ xử lý (pending requests) tách biệt với các mục trong bộ nhớ đệm đã hoàn tất. Tôi đã triển khai một Map cho các yêu cầu đang thực thi (in-flight) để quản lý việc này:

  • Khi một yêu cầu bắt đầu, hãy lưu Promise của nó vào một Map.
  • Nếu yêu cầu thứ hai đến với cùng một khóa (key), đừng bắt đầu một lần lấy dữ liệu mới.
  • Thay vào đó, hãy trả về Promise hiện có từ Map.
  • Sau khi yêu cầu hoàn tất, hãy xóa nó khỏi Map và lưu kết quả vào bộ nhớ đệm.

Điều này đảm bảo rằng bất kể có bao nhiêu người yêu cầu cùng một dữ liệu cùng một lúc, chỉ có duy nhất một cuộc gọi được gửi đến API.

Trong khi sửa lỗi này, tôi cũng đã giải quyết được ba lỗi trường hợp biên (edge-case) khác trong cùng một tệp:

  • Lỗi thiếu Token: Hệ thống đã gửi "undefined" dưới dạng thông tin xác thực. Tôi đã cập nhật để xác thực các token trước khi thực hiện yêu cầu.
  • Rò rỉ bộ nhớ (Memory Leaks): Logic thử lại (retry logic) đã để lại các trình lắng nghe sự kiện (event listeners) cũ trên AbortSignals. Tôi đã thêm logic dọn dẹp để ngăn chặn rò rỉ.
  • Tấn công chèn URL (URL Injection): Tên người dùng có các ký tự đặc biệt đã làm hỏng các đường dẫn API. Tôi đã thêm mã hóa (encoding) để bảo vệ cấu trúc URL.

Chỉ có bộ nhớ đệm là chưa đủ. Bạn còn cần phải loại bỏ các yêu cầu trùng lặp đang trong quá trình thực thi (in flight).

Nguồn: https://dev.to/eshaanagrawal/the-cache-was-working-and-still-causing-duplicate-api-calls-3n51