API của tôi phản hồi trong 4 ms, nhưng điều hướng vẫn cảm thấy chậm

Tôi đang gỡ lỗi một ứng dụng quản lý dự án được xây dựng bằng SvelteKit và một Rust API.

Trên máy cục bộ (local machine), mọi thứ đều diễn ra tức thì. Trên VPS, việc điều hướng cảm thấy rất nặng nề. Việc mở các trang như Tickets hay Timeline mất quá nhiều thời gian. Nhấp vào một ticket cũng gây ra sự chậm trễ rõ rệt trước khi bản xem trước xuất hiện.

Tôi đã nghĩ vấn đề nằm ở máy chủ. Tôi nghi ngờ các truy vấn cơ sở dữ liệu bị chậm hoặc do VPS yếu.

Các phép đo lường đã chứng minh tôi sai.

Tôi đã kiểm tra endpoint danh sách tính năng (feature list). Chỉ với 52 ticket, API phản hồi trong 4 ms. Nghe có vẻ nhanh. Nhưng kích thước phản hồi lên tới 354 KB.

Payload của route SvelteKit cũng cho thấy vấn đề tương tự. Kích thước dữ liệu quá lớn đối với một danh sách đơn giản.

Vấn đề không phải là tốc độ. Vấn đề là trọng lượng (kích thước dữ liệu).

Chỉ riêng phần mô tả đã chiếm tới 80% tổng phản hồi. Endpoint danh sách đang trả về toàn bộ mô tả Markdown cho từng mục một.

Đây là một cái bẫy phổ biến. Endpoint danh sách đã trở thành một endpoint chi tiết. Mỗi khi một trang cần một danh sách, nó phải trả giá bằng lượng dữ liệu mà nó không hề sử dụng.

Cách UI sử dụng không quyết định payload. Chính cấu trúc trả về của loader mới quyết định điều đó.

Nếu loader của bạn trả về một đối tượng đầy đủ, SvelteKit sẽ tuần tự hóa (serialize) toàn bộ đối tượng đó vào dữ liệu của route. Ngay cả khi bạn không bao giờ render một trường nào đó, nó vẫn được truyền qua mạng.

Tôi đã khắc phục điều này bằng cách tách biệt dữ liệu tóm tắt (summary) khỏi dữ liệu chi tiết (detail).

Tôi đã tạo ra hai contract khác nhau:

• Một contract danh sách cho các bản tóm tắt (ID, tiêu đề, trạng thái, ngày tháng). • Một contract chi tiết cho thông tin đầy đủ (mô tả, lệnh).

Tôi đã chia API thành hai endpoint:

  • GET /features (trả về các bản tóm tắt)
  • GET /features/:id (trả về một chi tiết đầy đủ)

Tôi cũng thay đổi cách thức hoạt động của frontend. Giờ đây, ứng dụng sẽ render danh sách ngay lập tức bằng dữ liệu tóm tắt. Khi bạn nhấp vào một ticket, ứng dụng sẽ hiển thị một khung xương (shell) và tải dữ liệu chi tiết nặng nề ở chế độ nền (background).

Kết quả đạt được vô cùng ấn tượng:

• API danh sách tính năng: giảm 89,6% • Dữ liệu route Tickets: giảm 91,4% • API tài liệu OpenSpec: giảm 98,9%

Cơ sở dữ liệu vốn dĩ luôn nhanh. Nút thắt cổ chai thực sự nằm ở contract dữ liệu giữa API và trang web.

Bài học cho các ứng dụng chứa nhiều danh sách:

  • Hãy đo lường kích thước phản hồi, thay vì chỉ đo thời gian phản hồi.
  • Hãy coi payload của danh sách và chi tiết là hai thực thể riêng biệt.
  • Đừng bao giờ trả về các trường văn bản lớn trong chế độ xem danh sách.
  • Kiểm tra xem SSR loader của bạn thực sự đang tuần tự hóa (serializing) những gì.
  • Sử dụng lazy-load cho dữ liệu chi tiết nhưng hãy hiển thị khung giao diện (UI shell) ngay lập tức.
  • Đừng dùng loading spinner để che đậy một payload nặng nề.

Một truy vấn nhanh không đảm bảo một trang web sẽ nhanh.

Nguồn: https://dev.to/ahikmah/my-api-responded-in-4-ms-but-navigation-still-felt-slow-1hk8