Tối ưu tốc độ tải trang với Progressive rendering

Tối ưu tốc độ ứng dụng web luôn là một vấn đề hay và gây nhiều chú ý với các developer. Tốc độ tải trang web ảnh hưởng rất nhiều tới khách truy cập, website chậm đi sẽ làm giảm trải nghiệm người dùng và làm giảm hiệu quả của trang web. Có nhiều phương pháp để làm giảm tốc độ tải trang như:

  • Tối ưu, giảm dung lượng truyền tải giữa serverside và browser
  • Bật nén (Gzip)
  • Sử dụng Browser caching
  • Nén các tài nguyên cần thiết
  • Nén ảnh trên web
  • Sử dụng CDN

Các phương pháp trên tuy rất đơn giản nhưng mang lại hiệu quả rất lớn trong việc tối ưu tốc độ tải của trang web. Hôm nay mình sẽ giới thiệu một phương pháp "không mới" nhưng giường như đã bị lãng quên từ lâu, đó là sử dụng kỹ thuật Progressive rendering để làm giảm thời gian tải trang "thật", và thời gian tải trang "cảm nhận được". Trước khi bắt đầu thì mình có một số quy ước như sau:

  • Thời gian tải trang thật(1): Là thời gian thật mà trình duyệt cần thiết để có thể tải và render toàn bộ nội dung, bao gồm html page, resource (js, css, fonts...)
  • Thời gian tải trang cảm nhận được(2): Là thời gian mà người sử dụng website cảm nhận về việc tải trang, yếu tố này mang tính chủ quan của từng người truy cập và rất khó có thể đưa ra một cách chính xác để tính toán thời gian này.

"Thời gian tải trang cảm nhận được" có giá trị quan trọng không kém "Thời gian tải trang thật" bởi vì nó được phản ánh bởi nhận thức của người dùng. Nhận thức này thể hiện ở việc người dùng nhìn thấy toàn bộ hoặc một phần website hiển thị ra trong quá trình load.

Thử tưởng tượng bạn có một tác vụ cần xử lý trong một khoảng thời gian đủ dài, và người dùng phải chờ đợi để tác vụ của bạn hòan thành trước khi có thể thấy toàn bộ nội dung. Vấn đề đặt ra ở đây đó là đối với các website, phần lớn sẽ tồn tại những phần thành phần chung như Menu, Sidebar mà sẽ được cache hoặc có thời gian để lấy ra rất nhanh. Nếu có thể hiển thị trước các thành phần này thì sẽ giúp cung cấp cho người dùng tín hiệu rằng Chúng tôi vẫn đang tiếp tục xử lý công việc, đây là một số thứ chúng tôi đã xử lý xong, tuy nhiên vẫn còn nữa :D (loading indicator vẫn hiển thị), đồng thời sẽ tạo cho người dùng cảm giác rằng Nó vẫn load được; web hiển thị được một phần rồi, chắc sắp xong...

Ví dụ Không sử dụng Progressive rendering Sử dụng Progressive rendering Như demo trên hình thì rõ ràng là sử dụng Progressive rendering cho trải nghiệm tốt hơn nhỉ 😄 Để có cái nhìn rõ ràng hơn về Progressvie rendering, mình sẽ mô tả thêm một chút về cơ chế của nó (3) (1). Without Progressive rendering: Chờ cho tất cả các tác vụ hoàn thành xong, gom các output lại thành một string rồi response lại cho phía client (2). With Progressive rendering: Mỗi tác vụ chạy xong sẽ có output, ngay lập tức đẩy (stream) ouput này xuống client cho trình duyệt xử lý

Ngoài ưu điểm là đánh vào cảm nhận của người dùng thì Progressive rendering có tác dụng gì đối với thời gian tải trang thực không nhỉ?

Loading indicator: Loading indicator Như các bạn đã biết, loading indicator sẽ chỉ biến mất (thay thế bởi favicon) khi tất cả các tài nguyên cần thiết của website đã được load (css, js, image, iframe). Trình duyệt sẽ bắt đầu tải các tài nguyên này khi nó thực hiện render mã html nhận được và bắt gặp markup yêu cầu sử dụng tài nguyên bên ngoài (<script src="..."></script>, <link href="..." />, <iframe src="..."></iframe>). Nếu làm theo (3.1) thì trình tự xử lý sẽ như sau: All server side tasks completed -----> response -----> browser rendering -----> load all html resource Còn đồi với (3.2) thì trình tự sẽ như sau:

  • service side task 1 completed -----> to response -----> browser rendering -----> load task 1 output resource
  • service side task 2 completed -----> to response -----> browser rendering -----> load task 2 output resource
  • service side task 3 completed -----> to response -----> browser rendering -----> load task 3 output resource
  • ...

Thông thường, để đảm bảo các tài nguyên cần thiết được load trước khi html content được render, chúng ta thường "nhét" hết các file css, fonts, hoặc thậm chí cả js lên trên header (thẻ head) của website; mà thẻ này lại là thành phần chung nhỉ. Do đó ta có thể tách việc output ra header thành một task riêng, nó chỉ có nhiệm vụ là tạo ra mã html của thẻ head rồi stream lại cho trình duyệt. Sau đó trong lúc server side của ta đang bận bịu với việc khác (query db, xử lý logic) thì đồng thời phía client side cũng đã nhận được mã html của header và bắt đầu tải luôn các resource cần thiết mà không cần chờ cho phía server side phải hoàn tất. Cụ thể việc so sánh này sẽ được minh họa bởi các hình dưới đây:

Nếu các bạn đang lo lắng về việc cứ mỗi task lại ghi dữ liệu vào response một cách riêng lẻ có thể làm break mất cái response đó thì hãy yên tâm nhé Html ngay từ ban đầu đã được thiết kế để có thể được render dần dần từng mảnh một mỗi khi nhận được data từ server side. Do đó việc mỗi task lại write vào response một cách riêng rẽ sẽ không gây vấn đề gì cả. Mình dùng từ response trong bài khá nhiều nhưng nó không có nghĩa rằng mỗi task lại có một response riêng về client side, nó chỉ có nghĩa rằng tất cả các task sẽ write vào một response duy nhất về client, và chuỗi response đó sẽ chỉ dừng lại khi task cuối cùng chủ động làm công việc terminate cái response đó. Progressive loading không phải là một kỹ thuật mới, nó là một kỹ thuật đã quá cũ rồi; thậm chí đến năm 2005 người ta "mới" viết bài Progressive HTML Rendering - Nghệ thuật bị đánh cắp 😄 Cho đến nay thì Progressive rendering vẫn còn giá trị rất nhiều, ông lớn FB cũng đang sử dụng kỹ thuật này (giữ kết nối và đẩy dữ liệu thành từng phần) như là một phần của Bigpipe (một ứng dụng giúp tối ưu việc sử lý và response dữ liệu về client side).

Nếu bạn quan tâm và muốn implement Progressive rendering thì có thể tìm hiểu thông qua các gợi ý sau:

  • PHP: Sử dụng flushchunked encoding
  • Node.js: Sử dụng res.write để ghi dữ liệu vào response hoặc Markojs để stream từ Template Engine
  • các ngôn ngữ khác: Mình chưa biết 😄

Liên quan đến Progressive rendering còn một kỹ thuật nữa để hỗ trợ tối ưu page loading đó là Multi pipes; nó sử dụng Progressive rendering và khác với BigPipe của Facebook ở chỗ nó cần chia nhỏ một request lớn thành nhiều request nhỏ, việc còn lại là nhận response và mapping response đúng với vị trí cần thiết sẽ được Multi pipes đảm nhiệm. Các bạn có thể hình dung về Multi pipes thông qua kỹ thuật multiple flushes được sử dụng trong php để làm Progressive rendering.

Nguồn tham khảo: http://www.phpied.com/progressive-rendering-via-multiple-flushes/ https://github.com/marko-js-samples/marko-progressive-rendering https://marko-progressive-rendering.herokuapp.com/?renderMode=single-chunk&jsLocation=middle http://www.phpied.com/progressive-rendering-via-multiple-flushes/ https://blog.codinghorror.com/the-lost-art-of-progressive-html-rendering/ http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/

All Rights Reserved