Nghiên cứu điển hình thực tế về việc tối ưu hoá hiệu suất React SPA.
Hiệu suất trang web không chỉ là thời gian tải. Điều quan trọng là phải mang đến cho người dùng trải nghiệm nhanh và nhạy, đặc biệt là đối với các ứng dụng máy tính giúp nâng cao năng suất mà mọi người sử dụng hằng ngày. Nhóm kỹ sư tại Recruit Technologies đã thực hiện một dự án tái cấu trúc để cải thiện một trong các ứng dụng web của họ, AirSHIFT, nhằm cải thiện hiệu suất đầu vào của người dùng. Sau đây là cách họ thực hiện.
Phản hồi chậm, hiệu suất thấp
AirSHIFT là một ứng dụng web dành cho máy tính giúp chủ cửa hàng (như nhà hàng và quán cà phê) quản lý ca làm việc của nhân viên. Được xây dựng bằng React, ứng dụng một trang này cung cấp các tính năng phong phú cho ứng dụng khách, bao gồm nhiều bảng lưới về lịch làm việc theo ngày, tuần, tháng và nhiều thông tin khác.
Khi nhóm kỹ sư của Recruit Technologies thêm các tính năng mới vào ứng dụng AirSHIFT, họ bắt đầu nhận được nhiều ý kiến phản hồi hơn về hiệu suất chậm. Yosuke Furukawa, người quản lý kỹ thuật của AirSHIFT, cho biết:
Trong một nghiên cứu về người dùng, chúng tôi đã rất ngạc nhiên khi một trong những chủ cửa hàng nói rằng cô sẽ rời khỏi chỗ ngồi để pha cà phê sau khi nhấp vào một nút, chỉ để giết thời gian chờ bảng ca làm việc tải.
Sau khi nghiên cứu, nhóm kỹ sư nhận thấy nhiều người dùng đang cố gắng tải các bảng thay đổi lớn trên máy tính có thông số kỹ thuật thấp, chẳng hạn như máy tính xách tay Celeron M 1 GHz từ 10 năm trước.
Ứng dụng AirSHIFT đang chặn luồng chính bằng các tập lệnh tốn kém, nhưng nhóm kỹ sư không nhận ra các tập lệnh đó tốn kém như thế nào vì họ đang phát triển và thử nghiệm trên các máy tính có thông số kỹ thuật phong phú với kết nối Wi-Fi nhanh.

Sau khi phân tích hiệu suất trong Công cụ của Chrome cho nhà phát triển với CPU và tính năng điều tiết mạng được bật, họ nhận thấy rõ ràng rằng cần phải tối ưu hoá hiệu suất. AirSHIFT đã thành lập một nhóm đặc nhiệm để giải quyết vấn đề này. Sau đây là 5 điều mà họ tập trung vào để giúp ứng dụng phản hồi tốt hơn với hoạt động đầu vào của người dùng.
1. Ảo hoá các bảng lớn
Việc hiển thị bảng ca làm việc đòi hỏi nhiều bước tốn kém: tạo DOM ảo và hiển thị trên màn hình theo tỷ lệ với số lượng nhân viên và khung giờ. Ví dụ: nếu một nhà hàng có 50 nhân viên làm việc và muốn kiểm tra lịch làm việc theo ca hằng tháng, thì đó sẽ là một bảng gồm 50 (nhân viên) nhân với 30 (ngày), dẫn đến 1.500 thành phần ô cần hiển thị. Đây là một thao tác rất tốn kém, đặc biệt là đối với các thiết bị có thông số kỹ thuật thấp. Thực tế thì mọi thứ còn tệ hơn. Qua nghiên cứu, họ nhận thấy có những cửa hàng quản lý 200 nhân viên,cần khoảng 6.000 thành phần ô trong một bảng hằng tháng.
Để giảm chi phí của thao tác này, AirSHIFT đã ảo hoá bảng ca làm việc. Ứng dụng hiện chỉ gắn các thành phần trong khung nhìn và tháo các thành phần ngoài màn hình.


Trong trường hợp này, AirSHIFT đã sử dụng react-virtualized vì có các yêu cầu về việc bật bảng lưới hai chiều phức tạp. Họ cũng đang khám phá các cách chuyển đổi cách triển khai để sử dụng react-window gọn nhẹ trong tương lai.
Kết quả
Chỉ việc ảo hoá bảng đã giúp giảm thời gian tập lệnh xuống 6 giây (trên môi trường Macbook Pro bị giảm tốc CPU gấp 4 lần + bị giới hạn tốc độ 3G nhanh). Đây là điểm cải thiện hiệu suất có tác động lớn nhất trong dự án tái cấu trúc.


2. Kiểm tra bằng API Thời gian của người dùng
Tiếp theo, nhóm AirSHIFT đã tái cấu trúc các tập lệnh chạy trên dữ liệu đầu vào của người dùng. Biểu đồ hình ngọn lửa của Công cụ của Chrome cho nhà phát triển cho phép bạn phân tích những gì đang thực sự diễn ra trong luồng chính. Tuy nhiên, nhóm AirSHIFT nhận thấy việc phân tích hoạt động của ứng dụng dựa trên vòng đời của React sẽ dễ dàng hơn.
React 16 cung cấp dấu vết hiệu suất thông qua User Timing API (API Thời gian của người dùng). Bạn có thể trực quan hoá dấu vết này trong mục Thời gian của Công cụ của Chrome cho nhà phát triển. AirSHIFT đã sử dụng phần Timings (Thời gian) để tìm logic không cần thiết đang chạy trong các sự kiện trong vòng đời của React.

Kết quả
Nhóm AirSHIFT phát hiện thấy một Quá trình điều chỉnh cây React không cần thiết đang diễn ra ngay trước mỗi thao tác điều hướng tuyến. Điều này có nghĩa là React đang cập nhật bảng ca làm việc không cần thiết trước khi điều hướng. Một bản cập nhật trạng thái Redux không cần thiết đã gây ra vấn đề này. Việc khắc phục lỗi này đã tiết kiệm khoảng 750 mili giây thời gian tập lệnh. AirSHIFT cũng đã thực hiện các hoạt động tối ưu hoá vi mô khác, cuối cùng giúp giảm tổng thời gian tập lệnh xuống 1 giây.
3. Tải từng phần các thành phần và di chuyển logic tốn kém sang trình chạy web
AirSHIFT có một ứng dụng trò chuyện tích hợp. Nhiều chủ cửa hàng giao tiếp với nhân viên của họ thông qua tính năng trò chuyện trong khi xem bảng ca làm việc. Điều này có nghĩa là người dùng có thể đang nhập tin nhắn trong khi bảng đang tải. Nếu luồng chính bị chiếm bởi các tập lệnh đang kết xuất bảng, thì dữ liệu đầu vào của người dùng có thể bị giật.
Để cải thiện trải nghiệm này, AirSHIFT hiện sử dụng React.lazy và Suspense để hiển thị phần giữ chỗ cho nội dung bảng trong khi tải từng phần các thành phần thực tế.
Nhóm AirSHIFT cũng đã di chuyển một số logic nghiệp vụ tốn kém trong các thành phần tải lười sang worker web. Điều này đã giải quyết vấn đề giật khi người dùng nhập liệu bằng cách giải phóng luồng chính để luồng này có thể tập trung vào việc phản hồi hoạt động đầu vào của người dùng.
Thông thường, nhà phát triển gặp phải sự phức tạp khi sử dụng worker, nhưng lần này Comlink đã giúp họ giải quyết vấn đề này. Dưới đây là mã giả về cách AirSHIFT tạo worker cho một trong những hoạt động tốn kém nhất mà họ từng thực hiện: tính tổng chi phí nhân công.
Trong App.js, hãy sử dụng React.lazy và Suspense để hiển thị nội dung dự phòng trong khi tải
/** App.js */
import React, { lazy, Suspense } from 'react'
// Lazily loading the Cost component with React.lazy
const Hello = lazy(() => import('./Cost'))
const Loading = () => (
<div>Some fallback content to show while loading</div>
)
// Showing the fallback content while loading the Cost component by Suspense
export default function App({ userInfo }) {
return (
<div>
<Suspense fallback={<Loading />}>
<Cost />
</Suspense>
</div>
)
}
Trong thành phần Chi phí, hãy sử dụng comlink để thực thi logic tính toán
/** Cost.js */
import React from 'react';
import { proxy } from 'comlink';
// import the workerlized calc function with comlink
const WorkerlizedCostCalc = proxy(new Worker('./WorkerlizedCostCalc.js'));
export default async function Cost({ userInfo }) {
// execute the calculation in the worker
const instance = await new WorkerlizedCostCalc();
const cost = await instance.calc(userInfo);
return <p>{cost}</p>;
}
Triển khai logic tính toán chạy trong worker và hiển thị logic đó bằng comlink
// WorkerlizedCostCalc.js
import { expose } from 'comlink'
import { someExpensiveCalculation } from './CostCalc.js'
// Expose the new workerlized calc function with comlink
expose({
calc(userInfo) {
// run existing (expensive) function in the worker
return someExpensiveCalculation(userInfo);
}
}, self);
Kết quả
Mặc dù lượng logic mà họ đã thử nghiệm có giới hạn, nhưng AirSHIFT đã chuyển khoảng 100 mili giây JavaScript từ luồng chính sang luồng worker (mô phỏng bằng cách điều tiết CPU gấp 4 lần).
AirSHIFT hiện đang tìm hiểu xem liệu có thể tải lười các thành phần khác và giảm tải thêm logic cho trình chạy web để giảm thiểu hiện tượng giật hay không.
4. Đặt ngân sách hiệu suất
Sau khi triển khai tất cả các biện pháp tối ưu hoá này, điều quan trọng là phải đảm bảo rằng ứng dụng vẫn hoạt động hiệu quả theo thời gian. AirSHIFT hiện sử dụng bundlesize để không vượt quá kích thước tệp JavaScript và CSS hiện tại. Ngoài việc thiết lập các ngân sách cơ bản này, họ còn tạo một trang tổng quan để hiển thị nhiều phần trăm thời gian tải bảng ca làm việc nhằm kiểm tra xem ứng dụng có hoạt động hiệu quả ngay cả trong điều kiện không lý tưởng hay không.
- Thời gian hoàn tất tập lệnh cho mọi sự kiện Redux hiện được đo lường
- Dữ liệu hiệu suất được thu thập trong Elasticsearch
- Hiệu suất phân vị thứ 10, 25, 50 và 75 của mỗi sự kiện được hiển thị bằng Kibana
AirSHIFT hiện đang theo dõi sự kiện tải bảng ca để đảm bảo sự kiện này hoàn tất trong 3 giây đối với người dùng ở phân vị thứ 75. Hiện tại, đây là ngân sách chưa được thực thi, nhưng họ đang cân nhắc việc gửi thông báo tự động qua Elasticsearch khi vượt quá ngân sách.

Kết quả
Từ biểu đồ trên, bạn có thể thấy rằng AirSHIFT hiện chủ yếu đạt ngân sách 3 giây cho người dùng phân vị thứ 75 và cũng tải bảng ca trong vòng một giây cho người dùng phân vị thứ 25. Bằng cách thu thập dữ liệu hiệu suất RUM từ nhiều điều kiện và thiết bị, giờ đây, AirSHIFT có thể kiểm tra xem bản phát hành tính năng mới có thực sự ảnh hưởng đến hiệu suất của ứng dụng hay không.
5. Hackathon về hiệu suất
Mặc dù tất cả những nỗ lực tối ưu hoá hiệu suất này đều quan trọng và có tác động, nhưng không phải lúc nào các nhóm kỹ thuật và kinh doanh cũng dễ dàng ưu tiên phát triển không chức năng. Một phần thách thức là bạn không thể lập kế hoạch cho một số hoạt động tối ưu hoá hiệu suất này. Bạn cần thử nghiệm và có tư duy thử-và-sai.
AirSHIFT hiện đang tổ chức các cuộc thi lập trình kéo dài 1 ngày về hiệu suất nội bộ để các kỹ sư chỉ tập trung vào công việc liên quan đến hiệu suất. Trong các cuộc thi lập trình này, họ loại bỏ mọi quy tắc ràng buộc và tôn trọng sự sáng tạo của các kỹ sư, tức là mọi phương pháp triển khai góp phần tăng tốc độ đều đáng được cân nhắc. Để đẩy nhanh quá trình tổ chức hackathon, AirSHIFT chia nhóm thành các nhóm nhỏ và mỗi nhóm sẽ cạnh tranh để xem nhóm nào có thể cải thiện điểm hiệu suất Lighthouse nhiều nhất. Các đội sẽ cạnh tranh rất quyết liệt! 🔥
Kết quả
Phương pháp hackathon đang phát huy hiệu quả đối với họ.
- Bạn có thể dễ dàng phát hiện nút thắt cổ chai về hiệu suất bằng cách thực sự thử nhiều phương pháp trong cuộc thi lập trình và đo lường từng phương pháp bằng Lighthouse.
- Sau cuộc thi lập trình, tôi dễ dàng thuyết phục được nhóm về việc họ nên ưu tiên tối ưu hoá nào cho bản phát hành chính thức.
- Đây cũng là một cách hiệu quả để ủng hộ tầm quan trọng của tốc độ. Mọi người tham gia đều có thể hiểu được mối tương quan giữa cách bạn lập trình và hiệu suất của mã.
Một hiệu ứng phụ tích cực là nhiều nhóm kỹ sư khác trong Recruit đã quan tâm đến phương pháp thực hành này và nhóm AirSHIFT hiện đang hỗ trợ nhiều cuộc thi lập trình tốc độ trong công ty.
Tóm tắt
Đây chắc chắn không phải là hành trình dễ dàng nhất để AirSHIFT làm việc trên các hoạt động tối ưu hoá này, nhưng chắc chắn là đã được đền đáp. Giờ đây, AirSHIFT đang tải bảng ca làm việc trong vòng 1,5 giây trung bình, tức là cải thiện hiệu suất gấp 6 lần so với trước khi có dự án.
Sau khi các tính năng tối ưu hoá hiệu suất ra mắt, một người dùng đã nói:
Cảm ơn bạn rất nhiều vì đã giúp bảng ca làm việc tải nhanh. Giờ đây, việc sắp xếp công việc theo ca giờ trở nên hiệu quả hơn rất nhiều.