Cải thiện tốc độ load trang của Express – Đừng serve static file bằng nodejs

Có một thói quen chúng ta thường gặp phải khi làm việc với Express framework của Node đó là sử dụng middleware express.static để serve các file tĩnh, ví dụ như các file CSS, hình ảnh, JavaScript,…

Vậy có vấn đề gì?

Điều này là hoàn toàn tự nhiên khi mà 99% các tutorial về Express đều hướng dẫn chúng ta làm như vậy.

Tuy nhiên có một hiệu ứng phụ mà ít ai để ý, là nguyên nhân gây ra hầu hết các trường hợp server load chậm. Đầu tiên, mời các bạn xem qua hình sau:

Screen Shot 2016-06-01 at 9.02.16 AM

Đây là mô hình server khi chúng ta sử dụng express.static.

Khi đó, tất cả mọi request gửi tới máy chủ, sẽ được đi qua express server, từ đó tuỳ theo logic của ứng dụng mà sẽ trả về các loại nội dung khác nhau (HTML, JSON, hoặc các file CSS, hình ảnh, JS,…)

Và vấn đề nằm ở đây!

Như chúng ta đã biết, JavaScript là single-threaded, tức là tất cả mọi việc diễn ra trong một chương trình JavaScript đều xảy ra một cách tuần tự, anh này trước đến anh kia sau, chứ không có bất kì một hành động nào chạy song song với hành động khác được cả.

Và đối với express thì cũng vậy, khi chúng ta load một trang web, nếu trong trang web đó tạo ra 10 request lên server để lấy về 10 nội dung khác nhau, thì cả 10 nội dung đó sẽ được load về một cách tuần tự chứ không phải là song song nhau.

Screen Shot 2016-06-01 at 9.02.48 AM

Ví dụ ở hình trên chúng ta có 5 request gửi tới server để trả về 5 loại nội dung khác nhau, trong đó phần màu xanh lục là HTML content và phần màu vàng thì có thể là các file hình ảnh, JavaScript, CSS,…

Chúng ta sẽ cùng đi sâu vào quá trình load này để xem chuyện gì xảy ra (ví dụ minh hoạ chỉ là hư cấu, thời gian được phóng đại lên cho dễ hình dung thôi nha :troll: )

  • Server bắt đầu load
  • 1.0 giây đầu tiên: Nội dung HTML đầu tiên được load ra, và hiển thị lên trình duyệt
  • 1.5 giây tiếp theo: Một file hình được load về, lúc này trang web hiển thị vẫn còn thiếu
  • 1.5 giây tiếp theo nữa: Một file hình khác lại được load về, trang web vẫn chưa hiển thị đầy đủ
  • 2.0 giây sau: Lúc này phần nội dung HTML tiếp theo đã được load xong, và trang web lúc này đã hiển thị cấu trúc HTML đầy đủ, tuy nhiên CSS style hoặc hình ảnh thì vẫn chưa xong
  • 2.0 giây cuối cùng: Hoàn tất việc load trang, khi này trang web của chúng ta đã hiển thị đầy đủ.

Vậy tốn mất 6 giây để trang web tải xong cấu trúc HTML. Và toàn bộ quá trình tốn mất 8 giây để toàn bộ trang web được load xong. Tất cả quá trình load trên đều do một mình process của chúng ta đảm nhiệm và xử lý tuần tự từng request. Một sự lãng phí không hề nhỏ!

process là gì? một ứng dụng đang chạy thì được gọi là một process, ở đây ý nói tới ứng dụng server mà chúng ta đang làm.

Trên thực tế, thời gian load có thể rất nhỏ nên các bạn không kịp nhận ra, nhưng nếu chúng ta có một trang web chạy bằng express và trên đó load 20 tấm hình, chúng ta sẽ đẽ dàng nhận ra nhược điểm này của express.

Làm sao giải quyết?

Cách giải quyết là hạn chế serve static file bằng express mà thay vào đó chúng ta có thể sử dụng nginx hoặc apache.

Đa phần khi đưa lên production thì chúng ta đều sử dụng thêm một lớp nằm trên lớp ứng dụng của chúng ta để quản lý các kết nối đến server, phổ biến nhất vẫn là nginx. Và nếu làm theo cách cũ ở trên, mô hình server của chúng ta sẽ có dạng như sau:

Screen Shot 2016-06-01 at 9.03.16 AM

Tất cả request gửi tới máy chủ, được nginx điều hướng đưa vào ứng dụng express của chúng ta, và cái quy trình load tốn kém đã đề cập bên trên lại xảy ra.

Trong khi đó, bản thân nginx cũng có thể được dùng để serve các static file một cách rất hiệu quả, thay vì dùng express.

Giả sử chúng ta có một node app nằm ở thư mục /var/www/app/ và thư mục chứa static files chúng ta muốn serve là /var/www/app/public. Thay vì làm theo cách cũ, chúng ta phải serve nó bằng express như sau:

app.use(‘/public’, express.static(‘public’));

Thì chúng ta có thể cấu hình cho nginx tự động trỏ luôn tới thư mục đó bằng cách cấu hình như sau:

Và sau đây là một file cấu hình nginx đơn giản để thiết lập một server có một node app đang chạy ở port 5000 và thư mục static file ở /var/www/app/public

Và mô hình tổng quan server của chúng ta lúc này sẽ trở thành:

Screen Shot 2016-06-01 at 9.04.43 AM

Hãy cùng xem xét lại quá trình load trang với cùng một lượng dữ liệu như đầu bài, trong trường hợp này sẽ thế nào nhé:

Screen Shot 2016-06-01 at 9.05.02 AM

  • Server bắt đầu load
  • Giây thứ 1.0 Đoạn HTML content đầu tiên được load và render ra màn hình, bắt đầu load tiếp đoạn HTML thứ 2, phần hình ảnh cũng bắt đầu được load song song luôn, và load được một lúc rồi.
  • Giây thứ 1.5 Hình ảnh đầu tiên đã load xong, hình ảnh tiếp theo cũng bắt đầu được load
  • Giây thứ 3.0 Đoạn nội dung HTML thứ 2 cũng đã được load xong. File hình ảnh thứ 2 cũng vừa load xong luôn. Lúc này cấu trúc HTML đã được render hoàn chỉnh.
  • Giây thứ 5.0 HÌnh ảnh cuối cùng cũng hoàn thành, toàn bộ trang web đã load xong.

Như vậy, tính từ lúc bắt đầu load cho đến 3 giây sau đó, toàn bộ cấu trúc đã được load hoàn chỉnh. Và sau 5 giây thì toàn bộ trang web đã thành hình.

Và chúng ta có bảng so sánh sau:

Giải pháp

Load cấu trúc HTML

Load xong toàn bộ trang

express.static

6 giây

8 giây

kết hợp nginx

3 giây

5 giây

TL;DR

Bài viết hơi dài dòng, đến đây mình xin tổng kết lại một tí:

  • nodejs là single-threaded, tất cả đều chạy trên 1 thread, cho nên việc xử lý nhiều request một lần cũng phải xử lý tuần tự, từng request một
  • Serve static file thông qua middleware express.static cũng chiếm một chỗ trên luồng xử lý duy nhất đó, vì thế nên server tốn nhiều thời gian hơn để xử lý, dẫn đến tốc độ load trang bị chậm đi thấy rõ
  • Để cải thiện, chúng ta tách biệt công việc serve static file cho nginx và serve nội dung HTML, JSON của trang web cho nodejs app

Hy vọng qua bài này các bạn cũng hiểu thêm về tính chất đơn luồng (single-threaded) của JavaScript, và ảnh hưởng của nó trong nodejs/express.

Update: Sau khi đăng lại bài viết này trên Medium, mình nhận được rất nhiều ý kiến góp ý từ các developer khác về vấn đề bảo mật khi thiết lập server theo hướng như trong bài viết.

Theo đó, nginxNodeJS application của chúng ta sẽ chạy với 2 user khác nhau trên server, điều này dẫn đến khả năng phía server app không truy cập được đến các static files (lỗi 403 chẳng hạn), và nếu không cẩn thận, chúng ta sẽ giải quyết vấn đề này bằng cách cấu hình cho cả 2 phía (nginx và server app) chạy với user root thì sẽ tác động rất tai hại. Nên đây là vấn đề cần lưu ý.

Và nếu website cần nhiều traffic cho static files, chúng ta có thể sử dụng CDN thay vì cách trên.

ITZone via kipalog

Chia sẻ bài viết ngay