12/08/2018, 17:59

Async download with Rails and Sidekiq Status

Trong dự án hiện tại mình đang tham gia có chức năng export dữ liệu từ các bảng trong database ra file Excel (*.xlsx). Việc export dữ liệu ít (trên môi trường development hoặc testing) thì không có vấn đề gì nghiêm trọng cả. Nhưng khi lên môi trường production với lượng dữ liệu rất lớn sẽ cần rất ...

Trong dự án hiện tại mình đang tham gia có chức năng export dữ liệu từ các bảng trong database ra file Excel (*.xlsx). Việc export dữ liệu ít (trên môi trường development hoặc testing) thì không có vấn đề gì nghiêm trọng cả. Nhưng khi lên môi trường production với lượng dữ liệu rất lớn sẽ cần rất nhiều thời gian để export file và gây ra chết trang. Vậy nên mình đã được giao tìm hiểu làm sao để có thể thực hiện công việc này mà không gây ra vấn đề gặp phải. Và mình đã làm theo thằng Google!Drive là thực hiện công việc export file ở background, client sẽ thực hiện gọi lên server theo một khoảng thời gian cố định để kiểm tra xem công việc đã hoàn tất chưa. Nếu hoàn tất thì thực hiện download file đó. Nghe có vẻ khó hiểu nhỉ? Vậy chúng ta đi vào chi tiết bằng bài viết dưới đây của mình nhé!

Đầu tiên, chúng ta sẽ tạo một project Rails để thực hiện test nhé!

rails new simple-async-download -d mysql --skip-action-mailer --skip-action-cable --skip-turbolinks --skip-test

Sau đó, chúng ta thêm một số gem sau vào Gemfile để làm việc với background job:

# Background Job
gem "resque"
gem "resque-scheduler"
gem "sidekiq"
gem "sidekiq-status"

Tiếp, chúng ta thêm gem sau vào Gemfile để làm việc với file Microsoft Excel (*.xlsx):

gem "axlsx"
gem "zip-zip"

Rồi chạy lệnh

bundle install

để thực hiện cài các gem mới đã thêm. Tiếp theo, chúng ta tạo migration và seed data để thực hiện export ra file *.xlsx. Đầu tiên là file migration:

rails g model user --migration
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :address
      t.string :phone
      t.timestamps
    end
  end
end

Tiếp theo, tạo seed dữ liệu cho bảng user. Bạn hãy thêm gem faker vào Gemfile để tạo cho tiện nhé.

(1..100).each do |idx|
  name = Faker::Name.unique.name
  email = Faker::Internet.email
  addr = Faker::Address.full_address
  phone = Faker::PhoneNumber.cell_phone

  puts "Create user #{idx}:
	- Name: #{name}
	- Email: #{email}
	- Address: #{addr}
	- Phone: #{phone}"

  User.create name: name, email: email, address: addr, phone: phone
end

Đã xong phần dữ liệu mẫu. Giờ chúng ta sang phần cài đặt cho Sidekiq và Resque. Đầu tiên là include Resque tasks vào Rake tasks:

rails g task resque setup
# lib/tasks/resque.rake
require "resque/tasks"
require "resque/scheduler/tasks"

task "resque:setup" => :environment do
  ENV["QUEUE"] = "*"
end

Tiếp, cài đặt cho Resque kết nối với Redis

# config/initializers/resque.rb
require "resque/scheduler"
require "resque/scheduler/server"

redis_configs = Rails.configuration.database_configuration[Rails.env]["redis"]
Resque.redis = Redis.new redis_configs
Resque.redis.namespace = "resque:AsyncDownload"

Dir[Rails.root.join("app", "jobs", "*.rb")].each{|file| require file}

Tiếp theo là Sidekiq với Redis và Sidekiq Status:

# config/initializers/sidekiq.rb
REDIS_SERVER_CONFIG = Rails.application.config
  .database_configuration[Rails.env]["redis"]
REDIS_HOST = REDIS_SERVER_CONFIG["host"]
REDIS_PORT = REDIS_SERVER_CONFIG["port"]
REDIS_DB = REDIS_SERVER_CONFIG["db"]

Sidekiq.configure_server do |config|
  config.redis = {url: "redis://#{REDIS_HOST}:#{REDIS_PORT}/12"}
  Sidekiq::Status.configure_server_middleware config
end

Sidekiq.configure_client do |config|
  config.redis = {url: "redis://127.0.0.1:#{REDIS_PORT}/12"}
  Sidekiq::Status.configure_client_middleware config
end

Vậy là khâu chuẩn bị đã gần xong rồi. Giờ chúng ta chỉ cần một view hiển thị nút Export nữa là có thể đi vào phần chính rồi. Chúng ta làm nốt nào. Tạo controller:

rails g controller users index --no-stylesheets --no-javascripts --no-assets --no-helper

Update lại 2 file là config/routes.rb và app/views/users/index.html.erb

# config/routes.rb
Rails.application.routes.draw do
  root to: "users#index"
end
<!-- app/views/users/index.html.erb -->
<h1>Export users</h1>
<button id="export">Export</button>
<div class="export-status"></div>

Vậy là xong, phần chuẩn bị nghe có vẻ vật vã nhỉ             </div>
            
            <div class=

0