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ỉ