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ữ ...
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(" ") << " " 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(" ") << " " 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é.