07/09/2018, 15:41

Precompile Rails asset trên nhiều server

Asset Pipeline là một chức năng khá "hay" của Rails, được xây dựng dựa trên Sprockets, giúp chúng ta nén và hợp nhất các assets (Javascript & CSS, hình ảnh) thành một file duy nhất (application.js, application.css, v.v.), ngoài ra nó còn giúp tạo ra fingerprint cho từng file asset để tối ưu ...

Asset Pipeline là một chức năng khá "hay" :trollface: của Rails, được xây dựng dựa trên Sprockets, giúp chúng ta nén và hợp nhất các assets (Javascript & CSS, hình ảnh) thành một file duy nhất (application.js, application.css, v.v.), ngoài ra nó còn giúp tạo ra fingerprint cho từng file asset để tối ưu hóa cache ở phía client.

Cách sử dụng thì vô cùng đơn giản.

# Ví dụ trong view

= image_tag 'logo.png' 
#=> <img src="/assets/logo-asdf897868sdfasd7f687623.png" alt="" />

Giả sử ta có một ứng dụng Rails được triển khai trên ba server: hai web server để handle HTTP requests và một worker server để chạy những tác vụ chạy nền (gửi emails, cache dữ liệu, backup data), và cả ba server đều cần dùng đến static assets (email templates, file đính kèm, vv). Nếu asset trên server không được precompile, bạn sẽ không lấy được finger print của nó.

Cách làm thông thường là dùng capistrano-rails và gắn thêm cho mỗi server 1 cái role web trong config/deploy/<environment>.rbvà capistrano-rails sẽ giúp bạn precompile assets trên cả ba server đó.

# Production server
server 'xxx.xxx.xxx', user: 'deployer', roles: %w{app web}
server 'yyy.yyy.yyy', user: 'deployer', roles: %w{app web}

# Worker server
server 'zzz.zzz.zzz', user: 'deployer', roles: %w{worker web}

# asset role mặc định của capistrano-rails là web.

Tuy nhiên cách làm trên vẫn có một số hạn chế:

  • Asset Precompilation là một quá trình ngốn RAM và khá chậm.
  • Hoang phí .. "dung lượng ổ cứng" :trollface:, worker server của bạn hoàn toàn không serve những assets đó, assets chỉ được tạo ra để lấy finger print.
  • Nếu bạn dùng những giải pháp CDN như CloudFront, thì precompile cả đống assets trên cả hai web server là vô ích.

Nếu ta chỉ precompile asset một lần, thay vì là ba, thì sẽ vừa tăng tốc độ deploy, vừa bớt tốn tài nguyên tính toán phiền phức.

Fingerprint của mỗi file được sinh ra từ nội dung của file đó, nên khi nội dụng file thay đổi, fingerprint cũng sẽ thay đổi theo. Tuy nhiên để tối ưu hóa tốc độ thì sau mỗi lần precompile, Sprockets sẽ cache lại tất cả fingerprint vào trong file .sprockets-manifest-<random>.json

Một số điểm nhấn:

  • Fingerprint của asset sẽ giống nhau nếu nội dụng file giống nhau, dù trên bất kì server nào.
  • Khi bạn hỏi fingerprint của một file nào đó, Sprockets không quan tâm có file đó hay không, mà nó sẽ hỏi file manifest đó.

Suy ra, câu trả lời của câu hỏi ở trên là: Bạn chỉ cần có file manifest là đủ.

Nói thì dài dòng nhưng mà làm thì rất đơn giản, và có thể làm theo nhiều cách.

  • Precompile asset trên một server nào đó, download manifest về local, rồi upload nó lên những server nào cần.
  • Precompile assets trên máy local, rồi upload nó lên những server nào cần.
  • Precompile asset trên một server nào đó, rồi dùng rsync để sync file manifest cho những server nào cần.
  • ...

Vì khá làm biếng nên mình sẽ dùng Rsync.

Chú ý: Code được viết theo kiểu mã giả (theo cú pháp của Capistrano 3.x), đồng thời chưa được test, chỉ phù hợp để tham khảo, cân nhắc trước khi sử dụng.

# config/deploy.rb

set :manifest_roles, [:web, :worker]

namespace :assets do
  task :sync_manifest do
    on roles(fetch(:manifest_roles)) do
      execute :rsync, "-a -m --delete --include='.sprockets-manifest-*.json' --include='*/' --exclude='*' [email protected]:/path/to/your/manifest /path/to/your/manifest"
    end
  end
end

# Hook tác vụ sync file manifest của chúng ta vào sự kiện sau khi compile assets và sau khi rollback assets.

after 'deploy:compile_assets', 'assets:sync_manifest'
after 'deploy:rollback_assets', 'assets:sync_manifest'

Bài viết hơi dài dòng và cảm ơn các bạn đã đọc.

0