Hướng dẫn sử dụng gem SQL Tracker

1. Tổng quan

Khi phát triển hay khi tối ưu một hệ thống được viết bằng Rails, một trong những mối quan tâm chính là SQL queries. Lúc đó chúng ta sẽ đặt ra những câu hỏi như:

  • Có bao nhiêu câu query được gọi qua mỗi lần resquest tới server?
  • Mất bao nhiêu thời gian để chạy xong một query?
  • Query nào mất nhiều thời gian nhất?
  • Có query nào bị gọi lặp lại ở những chỗ khác nhau hay không?
  • Có những query nào được thực hiện nhiều hơn so với những query khác?

Theo cách thông thường, chúng ta đơn giản chỉ kiểm tra trong log file nhưng nếu muốn phân tích một request phức tạp hoặc nếu theo dõi đồng thời nhiều request thì với phương pháp trên chúng ta khó có thể lọc ra được những thông tin chính mà chúng ta quan tâm.

Vậy để trả lời những câu hỏi trên một cách đơn giản nhất, tôi sẽ hướng dẫn các bạn sử dụng gem Sql Tracker.

2. Cài đặt

thêm vào gem file

group :development, :test do
  gem “sql_tracker”
end

Config:

SqlTracker::Config.enable = true
SqlTracker::Config.tracked_paths = %w(app lib)
SqlTracker::Config.tracked_sql_command = %(SELECT INSERT UPDATE DELETE)
SqlTracker::Config.output_path = File.join(Rails.root.to_s, “tmp”)

Sau đó chạy lệnh

$ bundle

3. Phương thức hoạt động

3.1. Theo dõi

sql_tracker sử dụng http://guides.rubyonrails.org/active_support_instrumentation.html (instrumentation API) để theo dõi các câu query được gọi từ Active Record bằng cách subscribe tới sql.active_record:

ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload|
  sql_query = payload[:sql]
end

hoặc bạn có thể tạo một object để implement phương thức call ví dụ:

class Handler
  def call _name, _start, _finish, _id, payload
    sql_query = payload[:sql]
  end
end

ActiveSupport::Notifications.subscribe(“sql.active_record”, Handler.new)

sql_tracker sẽ được khởi tạo và bắt đầu theo dõi các query khi mà hệ thống Rails chạy và dừng khi mà hệ thống thoát

3.2. Lọc

Có thể bạn không muốn theo dõi toàn bộ các query. Ví dụ bạn chỉ muốn theo dõi các câu SELECT hoặc bạn muốn theo dõi các query được gọi từ môt vùng nhất định. Mặc định sql_tracker ghi lại tất các query SELECT, INSERT, UPDATE, DELETE nhưng nó cho phép bạn thay đổi bằng cách sử dụng tracker_sql_command :

SqlTracker::Config.tracked_sql_command = %w(SELECT)

Bạn cũng có thể thay đổi thư mục bạn muốn theo dõi bằng các sử dụng tracked_paths :

SqlTracker::Config.tracked_paths = %w(app/controllers/api)

Mặc định sql_tracker theo dõi toàn bộ forder applib

sql_tracker sử dụng phương thức caller của Ruby và backtrace_cleaner của Rails để lọc theo đường dẫn folder. caller trả về một array của strack hiện tại và backtrace_cleaner giúp dọn dẹp thông tin của stack sử dụng regex.

Rails.backtrace_cleaner.add_silencer do |line|
  line !~ %r{^(#{tracked_paths.join('|')})\/}
end

cleaned_trace = Rails.backtrace_cleaner.clean(caller)

return false if cleaned_trace.empty?

3.3. Nhóm query

sql_tracker cố gắng nhóm các query giống nhau bằng cách thay thế các giá trị cụ thể với xxx. Ví dụ bạn thực hiện một request thì query sẽ được thực hiện :

SELECT users.* FROM users WHERE users.id = 1

Một ví dụ khác :

SELECT users.* FROM users WHERE users.id = 2

sql_tracker sẽ nhóm chúng thành một query:

SELECT users.* FROM users WHERE users.id = xxx

sql_tracker suer dụng các biểu thức thông thường để biên tập lại các query. Ví dụ cố gắng tìm và thay thê các giá trị sau khi so sánh như : =, <, >, <> hoặc thậm chí BETWEENAND

Sau khi được nhóm lại thì sẽ dễ dàng hơn để tổng hợp các query và tính toán tổng số thời gian cũng như thời gian trung bình …

3.4. Lưu trữ

sql_tracker lưu lại tất cả các dữ liệu theo dõi vào bộ nhớ và chuyển nó thành một file JSON có dạng:

{
  key1: {
    sql: 'SELECT users.* FROM users ...',
    count: 1,
    duration: 0.34,
    source: ['apps/model/user.rb:57:in ...', ...]
  },
  key2: { ... },
  ... ...
}

Mặc định file ày sẽ được lưu tại folder tmp nhưng bạn có thể config lại:

SqlTracker::Config.output_path = File.join(Rails.root.to_s, "my_folder")

Nếu server của bạn sử dụng puma và set nhiều worker thì sẽ xuất ra nhiều hơn 1 file JSON vì mỗi một worker sẽ theo dõi và lưu trữ riêng.

3.5. Báo cáo

Cuối cùng sql_tracker hỗ trợ chúng ta tạo ra một báo cáo từ file JSON ở trên

sql_tracker tmp/sql_tracker-*.json

Báo cáo sẽ có dạng :

Total Unique SQL Queries: 24

Count Avg Time (ms) SQL Query Source
8 0.33 SELECT users.* FROM users WHERE users.id = xxx LIMIT 1 app/controllers/users_controller.rb:125:in `create'
. . . app/controllers/projects_controller.rb:9:in `block in update'
4 0.27 SELECT projects.* FROM projects WHERE projects.user_id = xxx AND projects.id = xxx LIMIT 1 app/controllers/projects_controller.rb:4:in `update'
2 0.27 UPDATE projects SET updated_at = xxx WHERE projects.id = xxx app/controllers/projects_controller.rb:9:in `block in update'
2 1.76 SELECT projects.* FROM projects WHERE projects.priority BETWEEN xxx AND xxx ORDER BY created_at DESC app/controllers/projects_controller.rb:35:in `index'

... ...

Tổng số, thời gian trung bình chạy và dòng code gọi mỗi câu query. Từ báo cáo này bạn có thể nắm bắt được query nào chạy nhiều nhất và từ đó lên ý tưởng để có thể tối ưu code chạy tốt hơn.

4. Tổng kết

Thông qua bài viết trên tôi mong giới thiệu cho các bạn một công cụ tuy đơn giản nhưng rất hữu dụng trong việc. Các bạn có thể tìm hiểu thêm thông tin tại: https://github.com/steventen/sql_tracker


All Rights Reserved