Cải thiện performance cho Rails applications

Mở đầu

Có rất nhiều developer phàn nàn về việc các ứng dụng Rails của họ chạy chậm. Nhưng hầu hết trong số đó không hoặc chưa thực sự quan tâm đến việc cải thiện performance cho các ứng dụng của họ. Trong bài viết này, chúng ta sẽ cùng tìm hiểu xem làm thế nào để kiểm soát, phát hiện và khắc phục các performance issue để giúp các ứng dụng rails chạy mượt mà hơn.

Cũng xin nói qua một chút về performance, performance của hệ thống là khả năng đáp ứng yêu cầu của một hệ thống trong một khoảng thời gian. Nó khác với Scale, performance thường nói đến việc làm sao để đáp ứng nhiều nhất yêu cầu của hệ thống mà không cần update phần cứng, còn Scale là update phần cứng để sao cho đáp ứng được nhiều yêu cầu hơn.

Frontend performance

Để cái thiện performance cho frontend, chúng ta có thể sử dụng một số phương pháp sau:

1. Minify css và Javascript

     #config/environtments/production
     config.assets.compress = true

2. Sử dụng Gzip

    # config.ru
    require ::File.expand_path('../config/environment', __FILE__)
    use Rack::Deflater # Thêm dòng này
    run Rails.application

3. Compress images

      #Gemfile
      gem 'paperclip-compression'
      # Model
      class User < ActiveRecord::Base
      has_attached_file :avatar,
        styles: { medium: '300x300>', thumb: '100x100>' },
        processors: [:thumbnail, :compression]
      end

4. Javascript at the bottom

 <script src = "/assets/common.js"></script>

Backend performance

Để hỗ trợ việc tìm kiếm performance issues ở backend, chúng ta có thể sử dụng một số công cụ như New Relic hay Skylight. Với New Relic chẳng hạn, đây là một dịch vụ server monitor dùng để theo dõi giám sát các thông số quan trọng của hệ thống.

Dưới đây là một số performance issue thường gặp và cách khắc phục

1. Avoid N+1 query

Nguyên nhân đầu tiên dẫn đến việc ứng dụng load chậm phải kể đến đó là việc truy vấn nhiều lần vào cơ sở dữ liệu để lấy ra thông tin mong muốn. Ví dụ


# Controller
@comments = Comment.limit(10)

#View
 <% @comments.each do |comment| %>
   <tr>
     <td><%= comment.article.name %> <%= comment.name %></td>
   </tr>
 <% end %>

#Console
Article Load (0.5ms)  SELECT  "articles".* FROM "articles" LIMIT 10
(5.6ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
   (1.2ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 2]]
   (1.1ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 3]]

.............................
(1.0ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 10]]
?

Câu lệnh trên sẽ lấy ra 10 comments, sau đó thực hiện vòng lặp với các comments này truy vấn vào DB lấy ra các article của chúng. Chuyện gì sẽ xảy ra nếu như số lượng comments tăng lên gấp 10 hoặc 100 lần. Để khắc phục điều này chúng ta có thể sử dụng eager loading methods

# Controller
  @comments = Comment.includes(:article).limit(10)
#Query
 Article Load (0.3ms)  SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, 2)

2. Sử dụng counter_cache

counter_cache là cơ chế cho phép cache số lượng items của associated model. Có rất nhiều lần chúng ta muốn đếm số object con và hiển thị số lượng object đó ra View, đồng nghĩa với nó là sẽ có tương ứng bấy nhiêu câu query access vào database. Bằng việc thêm mới 1 attribule comments_count trong bảng article và sử dụng counter_cache thì sẽ không có câu query nào được thực thi, thay vào đó rails chỉ trỏ đến trường comments_count trong Article table.

  # migrate
  add_column :articles, :comments_count, integer
  Article.all.each do |article|
    Article.reset_counters(article_id, :comment)
  end
  # Model
  class Comment < ActiveRecord::Base
    belongs_to :article, counter_cache: true
  end

3. Small Payload

Chỉ lấy những dữ liệu bạn thực sự cần, tránh việc lấy thừa dữ liệu

# Controller
@articles = Article.limit(10)
# View
  <% @articles.each do |article| %>
      <tr>
        <td><%= article.name %></td>
      </tr>
  <% end %>

Thay vào đó hãy sử dụng

@articles = Article.select('name').limit(10)

Kết luận

Có rất nhiều nguyên nhân khiến cho ứng dụng Rails của bạn chạy chậm, nguyên nhân có thể xuất phát từ Frontend hay backend. Sử dụng các công cụ hỗ trợ detective giúp bạn sớm tìm ra được vấn đề , hãy tái hiện các perfomance issue đó ở local và khắc phục chúng dựa vào một số cách trên. Hi vọng một số gợi ý trên có thể phần nào giúp bạn tháo gỡ vấn đề.