Export data to zip files on background using sidekiq in Rails
Bài đăng này đã không được cập nhật trong 7 năm
Export data to zip files on background using sidekiq in Rails
Mở đầu
Bài viết này mình sẽ giải quyết yêu cầu với đầu vào là một hệ thống có databases cần export ra file và nén trong thư mục zip, tất cả xử lý ở background.
Ví dụ chúng ta có một bảng posts
và cần xuất dữ liệu ra file data_feed.txt
và nén trong data_feed.zip
.
Bảng posts
và chúng ta xuất dữ liệu ra cần một vài trường cụ thể, và ở ví dụ bài viết này mình sẽ xuất ra các trường như sau:
id
, title
, content
, user_name
, created_at
. Và các cột này cách nhau bởi dấu tab
.
Tới đây với yêu cầu bài toán vậy mình sẽ tiến hành sử dụng sidekiq
gem trong Rails để xử lý background. Nén file mình sử dụng
gem rubyzip
để thao tác với zip
files.
Cài đặt sidekiq
Cài đặt và sử dụng sidekiq
cũng đơn giản. Chúng ta add gem vào Gemfile
và chạy bundle install
# Gemfile
gem 'sidekiq'
Cài đặt gem
$ bundle install
Để sử dụng sidekiq
chúng ta cần cài đặt redis
nữa. Cài đặt redis
đơn giản như sau:
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
sudo cp src/redis-server /usr/local/bin/
sudo cp src/redis-cli /usr/local/bin/
Chạy redis-server
và khởi động sidekiq
là xong.
# Khởi động redis
$ redis-server
# Khởi động sidekiq trong rails root app
$ bundle exec sidekiq
Export data
Tới đây mình sẽ tạo một worker để làm nhiệm vụ export data vào zip file như yêu cầu ban đầu.
Tạo ExportDataWorker
$ rails g sidekiq:worker ExportData # will create app/workers/export_data_worker.rb
class ExportDataWorker
include Sidekiq::Worker
def perform
# do something
end
end
Sử dụng gem 'rubyzip'
# Gemfile
gem 'rubyzip'
Cài đặt gem
$ bundle install
Export data to zip files
Chúng ta quay lại export_data_worker.rb
class ExportDataWorker
include Sidekiq::Worker
def perform
attributes = %w(id title content user_name created_at)
headers = attributes.join("\t") << "\n"
buffer = Zip::OutputStream.write_buffer do |out|
# Ghi vào file `data_feed.txt`
out.put_next_entry("#{Rails.root}/public/data_feed.txt")
# Ghi nội dung dòng header vào file
out.write headers
# Ghi data vào file
Post.includes(:user).find_in_batches(batch_size: 10000) do |group|
group.each do |post|
row_body = attributes.map{|a| post.send("export_#{a}")}.join("\t") << "\n"
out.write row_body
end
end
end
File.open("#{Rails.root}/public/data_feed.zip", "wb") {|f| f.write(buffer.string) }
end
end
Ở trên mình sử dụng hàm post.send("export_#{a}")
để có thể customize giá trị đầu ra như mong muốn.
Như vậy mình cần viết một số hàm trong post.rb
như sau:
# Post
def export_id
id
end
def export_title
title.titleize
end
def export_content
content
end
def export_user_name
user.name
end
def export_created_at
created_at
end
Cuối cùng chúng ta có thể chạy worker đó trong background.
Chạy worker bất đồng bộ
ExportDataWorker.perform_async
Chạy worker sau khi gọi lệnh 5 phút.
ExportDataWorker.perform_in 5.minutes
Và nhiều cấu hình chạy nền khác xem ở https://github.com/mperham/sidekiq/wiki/Getting-Started
Sidekiq worker trong Heroku
Nếu bạn deploy trong heroku thì chỉ cần cài thêm addons redistogo
.
Chạy lệnh sau để cấu hình biến môi trường cho redis
$ heroku config:set REDIS_PROVIDER=REDISTOGO_URL
Tạo Procfile tại root directory trong main app và thêm dòng sau:
# Procfile
worker: bundle exec sidekiq -c 3
Tham số -c
là concurrency
. Với heroku app host hobby
thì mình cần cấu hình giá trị concurrency
thấp như vậy thôi.
Hết rồi. Cảm ơn các bạn theo dõi bài viết nhé.
All rights reserved