12/08/2018, 16:09

Tips To Boost Up Performance Of Your Ruby On Rails Application

Ví dụ: class Employee < ActiveRecord::Base belongs_to :team end class Team < ActiveRecord::Base has_many :employees end employees = Employee.limit(10) employees.each do |employee| puts employee.title.name end Nhìn vào code thì có vẻ là ổn, tuy nhiên xét về performance thì ...

Ví dụ:

class Employee < ActiveRecord::Base
  belongs_to :team
end

class Team < ActiveRecord::Base
  has_many :employees
end
employees = Employee.limit(10)
employees.each do |employee|
   puts employee.title.name
end

Nhìn vào code thì có vẻ là ổn, tuy nhiên xét về performance thì không ổn chút nào.

SELECT  `employees`.* FROM `employees` LIMIT 10
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 1 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 2 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 3 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 4 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 5 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 6 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 7 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 8 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 9 LIMIT 1
SELECT  `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` = 10 LIMIT 1

Ứng dụng đã truy cập và database nhiều lần để lấy dữ liệu, điều này thực sự không tốt với ứng dụng lớn. Để giải quyết vấn đề đấy chúng ta sử dụng kỹ thuật Eager loading.

employees = Employee.includes(:title).limit(10)
employees.each do |employee|
 puts employee.title.name
end

Với Eager loading:

SELECT  `employees`.* FROM `employees` LIMIT 10
SELECT `titles`.* FROM `titles` WHERE `titles`.`deleted_at` IS NULL AND `titles`.`id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Blank? hay empty? đều là cách để kiểm tra một mảng object có rỗng hay không?

# Using `blank?`
User.where(screen_name: ['user1','user2']).blank?

# 1. Queries database for all user data
#   SELECT "users".* FROM "users" WHERE "users"."screen_name" IN ('user1','user2')

# 2. Loads users into an array
#   [<#User:0x007fbf6413c510>,<#User:0x007fbf65ab1c70>]

# 3. Checks to see if the array size is zero
#   => true
#Using `empty?`
User.where(screen_name: ['user1','user2').empty?

#1. Queries database for ONLY a count
#SELECT COUNT(*) FROM "users" WHERE "users"."screen_name" IN ('user1','user2')

#2. Checks to see if the count is zero
#=> true

blank? sẽ load toàn bộ các phần tử thỏa mãn sau đó kiểm tra xem mảng có rỗng k?. Còn empty? sẽ đếm số phần tử thỏa mãn sau đó sẽ kiểm tra tổng đó có bằng 0 hay k. Điều này không gây khác biệt với những database nhỏ (như development) nhưng nó nó làm nên khác biệt lớn với dữ liệu lớn (như production) .

Với cách này, điều làm như thế này sẽ giúp cho bạn sẽ có thể quản lý source code tốt hơn, chia để trị. Những phần đặc thù cho từng function sẽ được tách riêng ra, hay những phần được tại sử dụng nhiều lần (reuse) sẽ được tách ra và share cho nhiều phần khác của dự. Dễ đọc và dễ lưu và sử dụng cache hơn rất nhiều so với việc cho tất cả code vào 1 file view, rất khó maintain lần xử lý.

if(Player.find_by_id(1).name == "Tamim")
return Player.find_by_id(1)
else
return nil
end

Nó lên được viết :

player = Player.find_by_id(1)
if(player.name == "Tamim") then player else nil end

Database indexing đây là một cách đơn giản nhất để cải thiện hiệu suất của cơ sở dữ liệu. Tuy nhiên các hành động thêm dữ liệu sẽ chậm hơn nhưng lấy dữ liệu ra sẽ nhanh hơn cho ứng dụng web.

Đây cũng là cách giúp tăng tốc ứng dụng rails của bạn. Dưới đây là một số lại cache:

  • Page Caching
  • Action Caching
  • Fragment Caching:
  • SQL Caching
  • Asset caching

CDN viết tắt của Content Delivery Network, tạm dịch là "Mạng phân phối thông tin". CDN là một hệ thống các server phân tán trên khắp thế giới, đưa nội dung đến với người dùng một cách nhanh nhất có thể dựa theo vị trí địa lý của người dùng và các servers lưu trữ thông tin. Chi tiết có thể tìm hiểu tại bài viết này: https://viblo.asia/p/su-dung-cdn-de-giam-tai-cho-server-ymwGXOxoM4p1

Điều cuối cùng, nhưng cũng rất quan trọng đó là phải làm giảm kích thước của các file stylesheét và javácript một cách đáng kể bằng Minifying. Chúng sẽ nâng cao hiệu năng rất là nhiều thông qua việc làm giảm thời gian request/response

Qua đây mình đã giới thiệu cho các bạn 10 cách để có thể làm tăng tốc độ RoR application nói chung và các sản phẩm phần miềm nói riêng. Trong bài viết tới, mình sẽ viết kỹ và phân tích rõ ràng về từng cách tăng tốc độ xử lý ra sao.

Giả sử ta muốn lấy trường screen_names

# Using `map?`
User.where(email: ['jane@example.com', 'john@example.com']).map(&:screen_name)

#1. Queries database for all user data
#SELECT "users".* FROM "users" WHERE "users"."email" IN ('jane@example.com','john@example.com')

#2. Loads users into an array
#[<#User:0x007fbf6413c510>,<#User:0x007fbf65ab1c70>]

#3. Iterates over users to collect screen_names
#['user1','user2']
# Using `pluck?`
User.where(email: ['jane@example.com', 'john@example.com']).pluck(:screen_name)

#1. Queries database for only screen_names
#SELECT "users"."screen_name" FROM "users" WHERE "users"."email" IN ('jane@example.com','john@example.com')

#2. Returns those screen_names in an array
#['user1','user2']

Sử dụng map sẽ load các phần tử vào 1 array sau đó mới trả về screen_names. Pluck yêu cầu cơ sở dữ liệu trả về chính xác những gì nó cần và trả về một mảng giá trị kết quả. Một lần nữa ta có thể tăng performance một cách đáng kể khi dữ liệu User lớn

Count, length, size đều là cách để đếm số phần tử. Vậy cách nào dùng tốt hơn? Count:

  • Count đến số phần tử, sử dụng query: (SELECT COUNT(*) FROM…)
  • Kết quả không được lưu trữ trong vòng đời của đối tượng nghĩa là mỗi lần chúng ta gọi phương thức này, truy vấn SQL sẽ được thực hiện lại
  • Nó nhanh hơn so với sử dụng length

[19] pry(main)> a = User.all; nil => nil [20] pry(main)> a.count (0.3ms) SELECT COUNT() FROM users WHERE users.deleted_at IS NULL => 47666 [21] pry(main)> a.count (0.1ms) SELECT COUNT() FROM users WHERE users.deleted_at IS NULL => 47666


Length:

  • Trả về độ dài của 1 mảng các đối tượng mà k cần thực hiện truy vấn bổ sung, miễn là array đã có sẵn
  • Kết quả được lưu trữ k cần thực hiện lại truy vấn
  • Nhanh khi array đã được load sẵn
[11] pry(main)> a = User.all; nil
=> nil
[12] pry(main)> a.length
  User Load (155.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL
=> 47666
[13] pry(main)> a.length
=> 47666

Size:

  • Là sự kết hợp giữa count và length
  • Nếu array đã được load, sẽ đếm số phần tử mà k cần truy vấn thêm
[25] pry(main)> a = User.all; nil
=> nil
[26] pry(main)> a.count
   (0.3ms)  SELECT COUNT(*) FROM `users` WHERE `users`.`deleted_at` IS NULL
=> 47666
[27] pry(main)> a.count
   (0.3ms)  SELECT COUNT(*) FROM `users` WHERE `users`.`deleted_at` IS NULL
=> 47666
[28] pry(main)> a.size
   (0.3ms)  SELECT COUNT(*) FROM `users` WHERE `users`.`deleted_at` IS NULL
=> 47666
[29] pry(main)> a.to_a; nil
  User Load (173.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL
=> nil
[30] pry(main)> a.size
=> 47666

Vậy sử dụng size sẽ là sự lựa chọn tốt nhất và an toàn nhất

Giả sử bạn muốn lấy danh sách user_id chứa người dùng duy nhất từ bảng UserLikeJob. Sử dụng pluck trước chúng ta có thể dễ dàng lấy ra list user id. Kết hợp với uniq, ta có 2 cách có thể lấy được list user_id

UserLikeJob.pluck(:user_id).uniq
UserLikeJob.uniq.pluck(:user_id)

Chúng khác nhau ở cách lọc để lấy các user_id duy nhất. Ở trường hợp đầu tiên, pluck trả về 1 mảng user_id sau đó được lọc bằng Array#uniq. Ở trường hợp thứ 2 thì uniq thực sự là method của ActiveRecord nó sẽ thêm DISTINCT vào câu lệnh sql

SELECT DISTINCT `user_like_jobs`.`user_id` FROM `user_like_jobs`

Cơ sở dữ liệu sẽ nhanh hơn nếu có index ở cột user_id

http://www.nascenia.com/10-tips-to-boost-up-performance-of-your-ruby-on-rails-application/
https://blog.codeship.com/speed-up-activerecord/
https://viblo.asia/p/cac-meo-tang-toc-do-truy-van-active-record-trong-rails-rNkGxxoAGlm
0