pnpm hay npm: cache trống có thể đảo ngược kết quả tốc độ

|Tác giả: Ban biên tập QUASA|8 phút đọc
pnpm hay npm: cache trống có thể đảo ngược kết quả tốc độ

Nếu CI thường chạy mà không khôi phục cache, tốc độ chưa phải lý do đủ mạnh để chuyển từ npm sang pnpm. Trong phép đo monorepo trên Windows của TeachMeIDEA, npm 11.19.0 mất 12,9 giây và pnpm 12.3.4 mất 39,8 giây ở lượt CI không có cache; khi cache đã ấm, thời gian tương ứng là 11,1 và 4,5 giây. Thứ hạng đổi chiều ngay trên cùng bộ phụ thuộc, nên một kết quả cài đặt đơn lẻ không đủ để chọn công cụ.

Với monorepo, pnpm đáng cân nhắc khi runner khôi phục store ổn định và nhóm duy trì nhiều bản checkout dùng chung các gói. Nếu cache thường bị mất, việc sửa cách lưu và khôi phục cache có thể đem lại lợi ích trước khi nhóm gánh thêm công việc đổi lockfile, khai báo workspace và xử lý khác biệt trong cách liên kết phụ thuộc. Cần đo hai trạng thái CI riêng rẽ trên chính pipeline sẽ sử dụng chúng.

Bốn trạng thái cài đặt cần phân biệt

Bốn trạng thái cài dependency của monorepo khác nhau ở sự hiện diện của lockfile, cache và node_modules.

Cụm từ “cài mới” che đi những phần việc khác nhau. Lockfile quyết định phiên bản cần cài; cache của package manager giữ dữ liệu gói để tái sử dụng; còn node_modules là cây phụ thuộc mà dự án dùng sau khi cài. Một runner vừa được tạo có thể nhận lại cache nhưng vẫn chưa có node_modules. Ngược lại, máy phát triển thường giữ cả hai sau lượt cài trước.

  • Cài sạch hoàn toàn: chưa có lockfile, cache hoặc node_modules. Công cụ phải giải phụ thuộc, tải gói và tạo cây cài đặt.
  • CI không khôi phục cache: đã có lockfile nhưng cache trống và node_modules chưa tồn tại. Đây là trạng thái khi pipeline không lưu cache hoặc khóa cache không khớp.
  • CI có cache ấm: đã có lockfile và dữ liệu gói được khôi phục, nhưng runner vẫn phải dựng node_modules.
  • Cài lại khi không có thay đổi: lockfile, cache và node_modules đều hiện diện. Kết quả này phản ánh thao tác lặp lại trên một môi trường đã cài, không phải lượt cài trên runner mới.

Hai trạng thái CI trả lời trực tiếp câu hỏi về thời gian chờ của pipeline. Tỉ lệ cache hit quyết định nhóm gặp trạng thái nào thường xuyên hơn; thời gian tải và khôi phục cache của dịch vụ CI cũng thuộc chi phí thực tế. Gộp lượt cache miss, cache hit và lượt cài lại trên máy phát triển thành một số trung bình sẽ che mất điều kiện tạo ra lợi thế của từng công cụ.

Vì sao hai bộ benchmark cho thứ hạng khác nhau?

Hai workload alotta-files và alotta-packages của benchmark pnpm có cấu trúc phụ thuộc khác nhau.

Benchmark do pnpm duy trì hiển thị dữ liệu lấy ngày 13/10/2026: pnpm 12.12.1 nhanh hơn npm 12.2.0 ở cả sáu trạng thái được công bố cho từng workload alotta-files và alotta-packages; ở lượt cài sạch, cặp thời gian lần lượt là 4,44 so với 45,7 giây và 13,9 so với 103 giây. alotta-files là đồ thị phụ thuộc rộng với nhiều gói nhỏ, còn alotta-packages tập hợp các bộ công cụ lớn. Các con số cho thấy lợi thế rõ rệt của pnpm trên hai workload đó.

Điều kiện đo giải thích vì sao không thể lấy thứ hạng ấy áp thẳng vào monorepo ở đầu bài. Benchmark của pnpm dùng cùng registry và đường truyền mô phỏng cho từng workload, rồi công bố lượt nhanh nhất trong ba lượt gần nhất. Phép đo monorepo chạy trên Windows, dùng một bộ phụ thuộc khác và báo trung vị của các lượt chạy; chính tác giả phép đo cũng lưu ý điều kiện mạng không được giữ cố định suốt quá trình thử. Khác biệt về môi trường, phiên bản và cách chọn kết quả đều có thể ảnh hưởng khoảng cách thời gian, đặc biệt khi phải tải lại gói.

Tên trạng thái cũng cần được đọc chính xác. Trong benchmark của pnpm, trường hợp “CI, first install” là một trạng thái riêng, còn “cache + lockfile” được mô tả là cài lại một dự án đã có. Trạng thái thứ hai không tự động tương đương với runner mới chỉ được khôi phục cache nhưng chưa có node_modules. Khi đánh giá CI của nhóm, điều kiện cần đối chiếu là sự hiện diện thực tế của lockfile, cache và cây phụ thuộc, không chỉ nhãn của cột kết quả.

Dung lượng đĩa phải tính cả store

Store theo nội dung của pnpm chia sẻ cùng tệp gói cho nhiều cây node_modules, giúp giảm dữ liệu lưu trùng.

Theo mô tả cơ chế lưu trữ của pnpm, các tệp gói nằm trong một store theo nội dung và được liên kết cứng vào cây cài đặt của dự án. Nhiều dự án dùng cùng dữ liệu gói có thể chia sẻ các tệp đó thay vì lưu một bản riêng cho mỗi dự án. Vì vậy, cộng kích thước hiển thị của mọi tệp trong node_modules có thể phóng đại lượng đĩa vật lý mà một checkout pnpm mới tiêu thụ.

Lợi ích này rõ nhất khi lập trình viên giữ nhiều checkout hoặc nhiều dự án có phụ thuộc trùng nhau trên cùng ổ đĩa. Store vẫn cần không gian riêng; trên CI, dữ liệu được lưu làm cache còn phải được truyền và khôi phục cho runner. Một phép so dung lượng hữu ích cần ghi cả phần đĩa tăng thêm khi tạo checkout từ store đã ấm lẫn tổng dung lượng store và các checkout đang được duy trì. Đó là hai câu hỏi khác nhau, và chỉ đo riêng node_modules sẽ không trả lời đầy đủ câu nào.

Chuyển công cụ tốn công ở đâu?

Tài liệu npm về npm ci quy định lệnh này cần lockfile khớp với package.json, không sửa lockfile và xóa node_modules hiện có trước khi cài. Cách làm đó phù hợp để đo một job CI bắt đầu từ cây phụ thuộc trống. Nếu dùng npm ci cho thao tác lặp lại trên máy đã cài đủ gói, thời gian đo sẽ bao gồm cả việc xóa và dựng lại cây; nó không đại diện cho một lượt npm install khi không có thay đổi.

Tài liệu pnpm import cho biết lệnh có thể tạo pnpm-lock.yaml từ package-lock.json và yêu cầu khai báo các workspace trong pnpm-workspace.yaml trước khi nhập phụ thuộc của chúng. Việc tạo được lockfile mới chỉ là một phần của chuyển đổi. Nhóm còn cần chạy build và test để phát hiện mã nguồn dùng dependency chưa khai báo, gói nội bộ liên kết không đúng hoặc công cụ phụ thuộc vào cấu trúc node_modules cũ. Thời gian xử lý những vấn đề đó cần được đặt cạnh số giây tiết kiệm được trong CI.

Đo trên runner của nhóm

Để so sánh công bằng, hãy giữ cố định mã nguồn, phiên bản Node.js, runner, registry và những tác vụ phụ đi kèm lệnh cài. Mỗi package manager cần lockfile và cache riêng của nó. Đo thời gian thực từ lúc tiến trình bắt đầu đến lúc kết thúc, đồng thời ghi riêng thời gian dịch vụ CI dùng để tải và lưu cache; con số công cụ tự in ra có thể không bao gồm toàn bộ khoảng chờ đó.

  1. Đo lượt cài sạch sau khi xóa lockfile, node_modules và mọi vị trí cache liên quan. Lượt này phản ánh việc thiết lập dự án từ đầu.
  2. Đo CI cache miss với lockfile có sẵn, node_modules trống và không khôi phục dữ liệu gói. Kiểm tra cả cache phụ ngoài thư mục được cấu hình để tránh một lượt tưởng là trống nhưng vẫn tái sử dụng dữ liệu.
  3. Đo CI cache hit trên runner mới: khôi phục cache của đúng công cụ, giữ node_modules trống và cài theo lockfile đã khóa. Ghi cả thời gian khôi phục cache.
  4. Đo lượt cài lại khi cả cache và node_modules đã có, bằng lệnh nhóm thực sự dùng trên máy phát triển. Báo kết quả này riêng với CI.

Lặp lại từng trạng thái và báo trung vị cùng khoảng dao động để một lượt tải mạng bất thường không quyết định kết luận. Sau đó đối chiếu kết quả với tỉ lệ cache hit của pipeline và mức tăng dung lượng đĩa khi nhóm tạo thêm checkout. Nếu CI thường xuyên cache miss, ưu tiên sửa cấu hình cache rồi đo lại. Nếu cache được khôi phục ổn định, lợi thế thời gian và khả năng chia sẻ store của pnpm có thể đủ lớn để bù công chuyển đổi.

Đọc thêm:

Chia sẻ:

Đăng ký bản tin

Nhận tin Web3, AI và tiền mã hóa mới nhất ngay trong hộp thư.

0