12/08/2018, 12:16

Một số biện pháp cải thiện performance của ứng dụng rails

Chúng ta đều biết rằng rails là 1 framework khá dễ sử dụng, ta có thể không cần nắm nhiều kiến thức lập trình mà vẫn có thể dễ dàng xây dựng 1 trang web, chỉ cần nắm qua một chút về gem, add-on... Tuy nhiên đó mới chỉ là dừng ở mức xây dựng 1 ứng dụng sơ khai. Khi đi vào tìm hiểu sâu về framework ...

Chúng ta đều biết rằng rails là 1 framework khá dễ sử dụng, ta có thể không cần nắm nhiều kiến thức lập trình mà vẫn có thể dễ dàng xây dựng 1 trang web, chỉ cần nắm qua một chút về gem, add-on... Tuy nhiên đó mới chỉ là dừng ở mức xây dựng 1 ứng dụng sơ khai. Khi đi vào tìm hiểu sâu về framework này, bạn mới càng ngày càng nhận thấy nó ẩn chứa nhiều điều thực sự phức tạp. Và cũng như nhiều framework khác, hầu hết lập trình viên đều tỏ ra đau đầu khi tìm cách cải thiện performance nếu muốn trang web của mình muốn ngày càng mở rộng . Bài này chúng ta cùng liệt kê 1 số phương pháp để cải thiện performance của ứng dụng rails.

Sử dụng Unicorn làm Rails server mặc định

Đây là 1 cách khá phổ biến tuy nhiên nhiều người lại bỏ qua. Một ứng dụng rails thông thường dùng mặc định Webrick server để chạy rails, một số khác lại thay thế bằng Thin.

Image

Tuy nhiên vấn đề là cả 2 loại server trên đều xử lý những kết nối đồng thời không tốt, và không thích hợp với môi trường production. Và cách hiệu quả và cũng đơn giản nhất là dùng unicorn làm rails server.

Unicorn là 1 webserver đa luồng. Mỗi server đều có một con số worker nhất định mà trong giờ “cao điểm ” có thể đáp ứng được đồng thời nhiều yêu cầu bằng cách sắp xếp hàng đợi . Unicorn biết rõ được các worker đang xử lý tiến trình nào , hay bao bao lâu mỗi worker sử lý một yêu cầu. Thay vì xếp chồng hàng đợi chồng chất Unicorn sẽ hủy bỏ worker và ngay lập tức tạo 1 worker mới để phục vụ yêu cầu.

Ví dụ, ta thiết lập số worker là 5 cho phép 5 kết nối hoạt động đồng thời. Với Heroku, bạn chỉ có thể tăng số lượng kết nối lên tối đa bằng 5 Với Unicorn, ví dụ khi thiết lập số Dyno cho unicorn thì số lươnghj kết nối đồng thời tối đa có thể là 20

4 x Dynos x 5 workers = 20 kết nối.

Vậy bạn có thể chạy bao nhiêu worker ? điều đó phụ thuộc vào ứng dụng của bạn nặng cỡ nào và bạn còn dư bao nhiêu bộ nhớ.

Host toàn bộ Assets ở bên ngoài

Khi bạn đã dùng unicorn làm webserver và có thể xử lý được đồng thời nhiều requets hơn, tuy nhiên vẫn xuất hiện trường hợp ứng dụng của chúng ta load rất chậm. Lí do là vì ứng dụng của chúng ta chứa quá nhiều resouce JavaScript, CSS, images, favicon, fonts, điều này làm cho user khi load trang sẽ đồng thời gửi rất nhiều request 1 lúc. Và nếu chúng ta set cho unicorn 5 worker, thì khi resource của chúng ta có đến 30,40 assets thì ứng dụng sẽ load rất chậm.

Mỗi 1 trình duyệt đều có quy định 1 số lượng HTTP request tối đa để ngăn ngừa việc user gửi quá nhiều request 1 lúc. Số lượng request tối đa phụ thuộc vào Browser và domain.

Vậy giải pháp bây giờ là gì ? Bạn đã bao giờ nghe nói đến việc nén các thư viện JS và CSS thành 1 file để giảm số lượng HTTP request ? và bạn sẽ gửi những request này đến 1 domain khác ? Bằng cách này, bạn đã có thể giảm được rất nhiều số lương request đến server của chúng ta. Và phố biến nhất là Amazon Web Services S3

Image

Khi thực hiện đièu này, ta có thế thấy ngay kết quả khi nhìn log server của chúng ta, và ta thấy rằng số lượng request đã giảm đi rất nhiều. Nếu bật cửa sổ network panel của Chrome bạn có thể thấy request đã được chia ra 2 loại là request đến rails sẻver và request đến S3.

Automatic Remote Asset Hosting

Tất nhiên khi move assets sang S3 server, bạn ko muốn làm điều đó thủ công đúng ko nào? Thật may là có gem để làm việc đó tự động là Asset Sync.

gem "asset_sync"

Asset Sync hoạt động bằng cách tự động push tất cả các file tĩnh của bạn lên Amazon S3 khi bạn deploy hoặc nếu không bạn chạy

rake assets:precompile

Sử dụng Active Record Associations

Có thể nói, Active Record Associations của rails làm cho việc query db trở nên vô cùng đơn giản. Khi sử dụng các mối quan hệ giữa các bảng thông qua active record association, ta đã rút gọn được rất nhiều câu lệnh truy vấn SQL. Lấy ví dụ

class User < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  has_many :comments
  belongs_to :user
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

Bây giờ trong trang user profile, ta muốn show ra các comment của user này

@user = User.find(id)

@user.posts.each do |post|
   post.comments.each do |comment|
       <%= comment.message %>
   end
end

Và đây là những query bạn đã gọi

User Load (1.1ms)  SELECT `users`.* FROM `users` WHERE 'id' = 1 LIMIT 1
 Post Load (0.7ms)  SELECT `posts`.* FROM `blogs` WHERE `blogs`.`user_id` = 2
 Comment Load (0.7ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 43
 Comment Load (1.7ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 55
 Comment Load (2.2ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 32
 Comment Load (0.9ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 66
 Comment Load (2.2ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 56
 Comment Load (4.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 65
 Comment Load (1.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 68
 Comment Load (0.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 71

User đó đã comment ở 9 bài post khác nhau, như vậy ta cần query 9 lần đến db. Đây chỉ là 1 ví dụ nhỏ nhưng bạn có thể thấy số lượng query đã là khá nhiều. Giải pháp ở đây là chúng ta có thể dùng includes

@user = User.find(id).includes(:posts => :comments)

Khi dùng includes, Active Record chắc chắn rằng sẽ load đầy đủ các mối quan hệ với số lượng query ít nhất và thực tế là các câu query đã được tối ưu hoá cho ngắn gọn nhất. Đây là các query khi ta sử dụng include cho ví dụ trên, rõ ràng là ngắc gọn và hiệu quả hơn.

User Load (1.0ms)  SELECT `users`.* FROM `users` WHERE 'id' = 1 LIMIT 1
Post Load (0.4ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
Comment Load (0.5ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` IN (43, 55, 32, 66, 56, 65, 68, 71)

Sử dụng cache

Caching web page hoặc các action đc sử dụng khá phổ biến và tăng đáng kể performance, khi trang web đã lưu cache, nó ko cần phải connect đến db để get dữ liệu.

Page Caching

Dữ liệu cache được lưu trong thư mục public, và nhanh hơn rất nhiều khi load HTML thông thường.

Lấy vị dụ khi ta truy cập 1 trang web chưa rất nhiểu ảnh, partial, CSS hay JS. Một request thông thường sẽ gọi đến routes để request đến controller và get về view tương ứng. View sẽ chứa nhiều các partial và sẽ chuyển các helper tag thành các thẻ HTML thuần.

Quá trình này xảy ra khá nhanh, tuy nhiên khi thêm nhiều function hơn,performance bị giảm đi rõ rệt.

Nhiều khi, trang web của bạn có những thành phần rất ít khi đc update dữ liệu, vậy bạn chỉ cần load 1 trang tĩnh mà dữ liệu đã được lưu trong cache. Một số trang ví dụ mà sử dụng cache khá hiệu qủa:

-FAQ pages
-Terms and conditions pages
-Home page (for product items or company etc)
-Help pages
-About pages
-Product overview pages

Với những trang này, dữ liệu rất khi thay đổi nên sẽ hạn chế query db để lấy dữ liệu như vậy sẽ tăng đáng kể performance. Để sử dụng cache, ta chỉ việc set trong production.rb

config.action_controller.perform_caching = true

và thêm vào đầu controller bạn muốn dùng cache

set caches_page :your_page_action

Nếu bạn muốn thay đổi dữ liệu cache, nghĩa là xoá cache cũ đi và thay bằng cache mới, ta chỉ cần set ở bất kỳ chỗ nào

ActionController::Base::expire_page("/yourpage")

Có 1 vài cách để cache cho ứng dụng rails, trong đó 2 cách phổ biến nhất là Action Caching và Fragment Caching. Tuỳ từng trường hợp nhất định ( cache cả trang hay cache 1 partial, cache authenticate của user) mà ta sẽ lựa chọn kiểu cache khác nhau.

Action Caching allows us to cache a page whilst still running through the full Rails stack.

Action Cache cho phép chúng ta cache 1 trang trong khi vẫn chạy full stack của ứng dụng. Điều này có nghĩa là nó thực hiện cả trước và sau bộ lọc ứng dụng giống như các request Rails khác. Và tất nhiên là chúng ta sẽ bị giảm performance khi chúng ta request trực tiếp đến file HTML.

Fragment Cache cho phép chúng ta cache từng phần của view vào cache store và sẽ gọi chúng cho lần requets tiếp theo. Loại cache này rất hữu dụng với những page có nhiều fragment.

Sử dụng CloudFlare

CloudFlare là một dịch vụ CDN & DDNS kết hợp để gia tăng tốc độ & tính bảo mật cho website của bạn

Image

CloudFlare sàng lọc tất cả mọi truy cập từ Internet, bao gồm Người dùng thật, hệ thống Crawler, và cả Hacker nữa, cũng sẽ truy cập vào website của bạn thông qua hệ thống của CloudFlare, rồi CloudFlare mới đẩy lượng truy cập (đã được sàng lọc) sang cho website bên bạn.

Cái dễ nhận ra nhất là tiết kiệm băng thông, đây là do CloudFlare đã chứa và phục vụ các file tĩnh thay cho host của bạn. Ngoài ra, làm giảm khả năng highload cho host của bạn. Theo như CloudFlare quảng cáo và so sánh thực tế:

  • Tiết kiệm băng thông: lượng băng thông giảm hẳn chỉ còn ½ hoăc 1/3 so với trước khi dùng.
  • Cập nhật DNS rất nhanh, chỉ trong vài phút.

Tóm lại, một ứng dụng web chỉ hay về nội dung thôi thì chưa đủ, mỗi lập trình viên cần cải thiện tối đa performance. Tuỳ mục đích sử dụng khác nhau mà bạn có thể chọn sử dụng các cách trên sao cho phù hợp.

0