Làm ấm như thế nào cho hợp lý?

Làm ấm ở đây có nghĩa là warm-up, thứ cần làm ấm là response của website, hem phải là vì trời lạnh nên có bài viết này đâu hehe.

Có một vấn đề mình khá không vừa ý khi sử dụng cache ở frontend, đó là vấn đề Warm-up (làm ấm). Warm-up cache là thuật ngữ được dùng khá nhiều, khi chúng ta “hi sinh” để làm người truy cập đầu tiên vào một URL chưa có cache, có thời gian chờ lâu, để người dùng (thực sự) tiếp theo truy cập vào sẽ có tốc độ nhanh nhất có thể do đã có cache rồi.

Ngoài warm-up cache, người ta còn thường gọi hành động này là prefetching, được hỗ trợ bởi rất nhiều plugin cache và cả Cloudflare.

Tại sao có bài viết này?

Mình sử dụng Varnish làm frontend cache cho các website của mình. Tuy nhiên các plugin hỗ trợ Varnish trên WordPress chưa có nhiều, và đặc biệt là thiếu tính năng warm-up mà mình cần.

Nếu trong trường hợp đã có plugin hỗ trợ thì sao? Mình vẫn muốn tìm một cách tự động không phụ thuộc vào backend, vì mình còn dùng Ghost, còn dùng static landing page phải clear cache bằng tay nữa. Vậy là mình quyết định tìm cách viết cái script này ngay trên cache server, tự động chạy cho tất cả các loại trang mà không cần cài đặt plugin, thay đổi code ở website gốc.

Bắt tay tìm củi…

Trước tiên, làm ấm có nghĩa là phải dùng một chương trình nào đó có thể truy cập vào website ngay sau khi website/bài viết vừa được clear cache xong, mình chọn curl hoặc wget đều có thể làm việc này.

Thứ hai, chúng ta phải tìm cách nào để lấy được những request purge cache, từ đó mới có thể sử dụng curl để gọi. Bước này rất quan trọng vì khi chúng ta update bài viết, chỉ có một số URL được clear, không phải toàn bộ trang. Vì vậy chúng ta chỉ nên warm-up những trang này thôi là đủ.

Vậy sẽ có hai hướng, lấy access log của webserver mình đang dùng và lấy access log của Varnish.

Cả hai cách đều tốt, request tới website đều phải đi qua hai bạn này, vậy chọn bạn nào cho hợp lý? Trước tiên mình nghĩ webserver cũng là ý hay, nhưng có nhược điểm chết người mà mình khó quản lý, đó là seperate log và rotate log.

Với Apache hay Nginx, mỗi domain mình đều có file log riêng, bao gồm abc.com.access.logxyz.com.access.log. Qua ngày hôm sau, các file log này lại được lưu trữ thành abc.com.access.log.20190101. Hai vấn đề này làm việc đọc log trở nên khó khăn hơn rất nhiều. Đối với các bạn ít file log, có thể sử dụng multitail để theo dõi và merge access log, từ đó có thể lấy được những request như yêu cầu.

Không may mắn như webserver, Varnish không có một file log vào để lưu trữ request nếu bạn không setup sẵn. Cơ mà đã có access log của webserver, tội gì mình phải ghi thêm một lần log nữa? Tưởng chừng như hết cách, đọc document lại giúp đỡ mình lần nữa. Tiện ích varnishncsa được cài đặt sẵn khi chúng ta cài đặt Varnish cho chúng ta thông tin về toàn bộ request đi tới Varnish, với format log của Apache, quá tuyệt vời.

Đốt lò

Không đơn giản như vậy, varnishncsa log lại tất cả request bao gồm GET, POST, HEAD, PURGE,… vân vân và mây mây, làm sao chỉ lấy đúng request PURGE mà mình cần? Lại lọc cọc gõ varnishncsa --help xem có manh mối nào không, nếu không sẽ phải dùng grep để filter thôi. Trời không phụ lòng người đẹp trai, có một option trong câu lệnh này giúp ta query được những thứ ta cần, có lẽ vậy:

[-q <query>]              VSL query

Lại phải Google xem VSL query là gì. À! Có vẻ giống VCL syntax. Chúng ta có query ReqMethod eq PURGE trùng khớp ý với bé yêu cầu. Câu lệnh để in ra toàn bộ request có method là PURGE sẽ là:

varnishncsa -q "ReqMethod eq PURGE"

Vậy câu lệnh để gọi URL (tạo cache) khi nhận được purge request sẽ là:

varnishncsa -q "ReqMethod eq PURGE" | awk '{ system("curl -Ls " $7 "> /dev/null") }'

Ok, vậy là xong, nhưng có cái gì đó lấn cấn ở đây. Nếu một thanh niên vui tính nào đó ngồi ở nhà gọi Purge request liên tục lên máy chủ, thì dù không clear được cache Varnish vẫn có thể gọi lệnh curl liên tục làm ảnh hưởng tới tài nguyên máy chủ. Có vẻ căng, vì dù validate được IP của client, nhưng lại phải config điều kiện nhiều lần, nên mình lười lắm. Lúc đó mình lại nghĩ ra, ah, client nào không nằm trong danh sách allowed IP sẽ trả về status 405, còn ngược lại sẽ trả về status 200 OK. Vậy câu lệnh trên chỉ cần thay đổi đơn giản:

varnishncsa -q "RespStatus == 200 and ReqMethod eq PURGE" | awk '{ system("curl -Ls " $7 "> /dev/null") }'

Kết

Bài viết dài như vậy chỉ là cách mình note lại những gì đã biết được, sau này có quên cũng có thể tìm lại được. Tóm lại mình đã tìm cách giải quyết vấn đề warm-up cache cho toàn bộ các website của mình dù cho nó sử dụng Joomla, Drupal, Ghost,… đi chăng nữa, việc của mình chỉ là cấu hình tại cache server thôi.

Ngoài ra, còn rất nhiều vấn đề như mình không cần warm-up /wp-json/ hay /feed/ nên cần loại bỏ ra. varnishncsa có hỗ trợ output theo format (-F), chúng ta có thể dùng options này để tiết kiệm tài nguyên hơn. Hơn thế nữa việc gọi request đến Varnish, chúng ta cần đợi một khoảng thời gian trước khi warm-up. Bên dưới chính là toàn bộ code hiện tại mà mình đang dùng:

varnishncsa -q "RespStatus == 200 and ReqMethod eq PURGE and not (ReqURL ~ 'wp-json' or ReqURL ~ 'feed')" | awk '{ system("sleep 6; (curl -s -S -H \"accept-encoding: gzip, deflate, br\" -A \"Cache Warmer\" -sL $7 -o /dev/null " $7 " && echo [Done] - " $7 " || echo [Failed] - " $7 ") >> /root/warmup.log") }'

Ngoài ra, mình còn tạo một service để giúp script chạy nền, tự khởi động theo hệ thống,… Bạn có thể tạo tập tin warmup.service ở đường dẫn /etc/systemd/system/ với nội dung bên dưới, nhớ sửa /root/warmup.sh thành đường dẫn của bạn nhé:

[Unit]
Description=Warmup server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=simple
PIDFile=/var/run/warmup.pid
ExecStart=
ExecStart=/bin/bash /root/warmup.sh
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Khởi động script với lệnh service warmup start là xong. Để khởi động script với hệ thống, chạy lệnh systemctl enable warmup là được bạn nhé.

Cảm ơn các bạn đã theo dõi bài viết.

Để lại một trả lời

Địa chỉ email của bạn sẽ không được công bố.