Optimization

Caching nâng cao VPS bằng Varnish và Redis

Website chậm dần khi traffic tăng, database liên tục bị query lặp lại, đó là lúc caching không còn là tùy chọn mà là bắt buộc. Với một VPS có RAM giới hạn, bạn cần hai lớp cache hoạt động đúng vai trò: Varnish đứng trước web server để cache toàn bộ trang HTML, và Redis đảm nhiệm object cache cho database, session, API. Bài này mình sẽ hướng dẫn cài đặt cả hai trên Ubuntu 24.04, cấu hình cho Nginx + PHP-FPM (hoặc WordPress), và quan trọng là cách kiểm tra xem cache có thực sự ăn tiền hay không.

Yêu cầu trước khi bắt đầu

  • VPS chạy Ubuntu 24.04 LTS hoặc Debian 12, tối thiểu 2GB RAM (4GB sẽ thoải mái hơn khi chạy cả Varnish + Redis + web server).
  • User sudo có quyền root, hoặc đăng nhập trực tiếp bằng root.
  • Web server đã hoạt động: Nginx + PHP-FPM (bài này giả định bạn đã có sẵn stack LEMP).
  • Domain đã trỏ A record về IP VPS nếu muốn test qua tên miền thật.

Vì sao cần hai lớp cache, không phải một?

Nhiều người cài Redis xong tưởng là xong, nhưng Redis chỉ giải quyết phần query lặp lại từ database. Khi 1000 người cùng xem một trang sản phẩm, PHP vẫn phải chạy, vẫn phải gọi Redis để lấy dữ liệu, vẫn phải render HTML. Việc đó tốn CPU và RAM đáng kể.

Varnish giải quyết tầng trên cùng: nó giữ bản HTML hoàn chỉnh trong RAM và trả thẳng cho khách truy cập mà không cần chạm tới Nginx hay PHP. Kết hợp lại, bạn có kiến trúc phân tầng rõ ràng:

LớpCông cụNhiệm vụVị trí
1 - Full page cacheVarnishCache toàn bộ phản hồi HTTP (HTML, ảnh, CSS)Port 80, trước Nginx
2 - Object cacheRedisCache query database, session, dữ liệu APIPort 6379, cạnh ứng dụng
3 - Opcode cachePHP OPcacheCache mã PHP đã biên dịchTrong PHP-FPM

Cả ba lớp này bổ sung cho nhau. Nếu chỉ có Redis mà không có Varnish, ứng dụng vẫn phải xử lý request. Nếu chỉ có Varnish mà không có Redis, thì các request động (giỏ hàng, user đăng nhập) vẫn phải query database liên tục.

Bước 1 - Cài đặt và cấu hình Varnish Cache

Varnish có trong kho lưu trữ mặc định của Ubuntu 24.04, nhưng phiên bản đó khá cũ. Bạn nên dùng kho chính thức của Varnish để có bản mới nhất (Varnish 7.x).

sudo apt update
sudo apt install -y debian-archive-keyring curl gnupg
curl -fsSL https://packagecloud.io/varnishcache/varnish73/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/varnish.gpg
echo "deb https://packagecloud.io/varnishcache/varnish73/ubuntu/ jammy main" | sudo tee /etc/apt/sources.list.d/varnish.list
sudo apt update
sudo apt install -y varnish

Lưu ý: dòng jammy ở trên tương ứng với phiên bản Ubuntu 24.04 (mã nội bộ vẫn dùng chung với 22.04). Nếu bạn dùng Debian 12, thay bằng bookworm.

Kiểm tra phiên bản đã cài:

varnishd -V

Kết quả mong đợi: dòng đầu tiên hiển thị varnishd (varnish-7.x.x).

Bước 2 - Cấu hình Varnish chạy trên port 80

Varnish mặc định lắng nghe trên port 6081, còn Nginx chiếm port 80. Bạn cần đảo ngược: cho Varnish chiếm port 80, đẩy Nginx xuống port 8080.

Mở file cấu hình systemd của Varnish:

sudo systemctl edit --full varnish

Tìm dòng ExecStart và sửa thành:

ExecStart=/usr/sbin/varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,256m

Tham số -s malloc,256m nghĩa là Varnish dùng tối đa 256MB RAM làm bộ nhớ cache. Với VPS 2GB RAM, con số này hợp lý. VPS 4GB có thể nâng lên 512m.

sudo systemctl daemon-reload
sudo systemctl restart varnish
sudo systemctl status varnish

Kiểm tra Varnish đang lắng nghe port 80:

sudo ss -tlnp | grep varnish

Output phải hiển thị port 80 do varnishd giữ.

Bước 3 - Đẩy Nginx xuống port 8080

Mở file config Nginx chính:

sudo nano /etc/nginx/nginx.conf

Sửa dòng listen 80 default_server; trong block server (thường nằm trong /etc/nginx/sites-available/default hoặc file cấu hình site của bạn) thành:

listen 8080 default_server;
listen [::]:8080 default_server;

Sau đó test và reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Lúc này truy cập thẳng vào Nginx qua port 8080 vẫn hoạt động, còn Varnish sẽ đứng chặn port 80 phía trước.

Bước 4 - Viết VCL (Varnish Configuration Language)

File /etc/varnish/default.vcl là nơi bạn định nghĩa luật cache. Dưới đây là bản VCL cơ bản nhưng đủ dùng cho hầu hết website:

vcl 4.1;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {
    # Chỉ cache method GET và HEAD
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }

    # Bỏ qua cache cho admin, login, checkout
    if (req.url ~ "^/(wp-admin|wp-login|cart|checkout|my-account|administrator)") {
        return (pass);
    }

    # Không cache khi có cookie đăng nhập
    if (req.http.cookie) {
        return (pass);
    }

    # Chuẩn hóa URL, bỏ query string rác
    if (req.url ~ "\?utm_") {
        set req.url = regsub(req.url, "\?.*$", "");
    }

    return (hash);
}

sub vcl_backend_response {
    # Cache 1 giờ cho tất cả nội dung
    set beresp.ttl = 1h;

    # Tôn trọng header Cache-Control từ backend
    if (beresp.http.Cache-Control) {
        if (beresp.http.Cache-Control ~ "no-cache|no-store|private") {
            set beresp.ttl = 0s;
        }
    }

    # Nén trước khi lưu vào cache để tiết kiệm RAM
    if (beresp.http.content-type ~ "text/html|text/css|application/javascript") {
        set beresp.do_gzip = true;
    }
}

sub vcl_deliver {
    # Thêm header để dễ debug
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
    set resp.http.X-Cache-Hits = obj.hits;
}

Phần vcl_deliver thêm header X-Cache vào response, cực kỳ hữu ích để kiểm tra cache có hoạt động không. Kiểm tra cú pháp và reload:

sudo varnishd -C -f /etc/varnish/default.vcl
sudo systemctl reload varnish

Bước 5 - Cài đặt Redis Server

Redis có sẵn trong kho Ubuntu 24.04, bản 7.x. Cài đặt rất đơn giản:

sudo apt install -y redis-server
sudo systemctl enable --now redis-server
sudo systemctl status redis-server

Kiểm tra Redis hoạt động bằng lệnh ping:

redis-cli ping

Output mong đợi:

PONG

Bảo mật tối thiểu: chặn truy cập từ ngoài. Mặc định Redis chỉ lắng nghe 127.0.0.1 nhưng nên kiểm tra lại:

sudo ss -tlnp | grep 6379

Output phải hiển thị địa chỉ 127.0.0.1:6379, không phải 0.0.0.0. Nếu nó hiển thị 0.0.0.0, sửa file /etc/redis/redis.conf, đảm bảo dòng bind 127.0.0.1 không bị comment.

Bước 6 - Cấu hình Redis để không bị OOM

Redis mặc định lưu dữ liệu ở RAM và không tự giới hạn. Trên VPS RAM thấp, đây là thảm họa. Mở file config:

sudo nano /etc/redis/redis.conf

Ba tham số quan trọng nhất:

# Giới hạn bộ nhớ tối đa Redis được dùng
maxmemory 256mb

# Khi đầy, xóa key cũ nhất theo LRU
maxmemory-policy allkeys-lru

# Không lưu snapshot xuống disk (chỉ dùng làm cache)
save ""

Giải thích: maxmemory 256mb an toàn cho VPS 2GB RAM, VPS 4GB có thể đặt 512mb. allkeys-lru nghĩa là khi đầy, Redis tự động xóa các key ít được dùng nhất, tránh crash. save "" tắt ghi snapshot vì dữ liệu cache không cần phục hồi sau khi mất điện, tiết kiệm I/O NVMe đáng kể.

sudo systemctl restart redis-server

Bước 7 - Kết nối Redis vào ứng dụng

Redis chỉ là server, cần client để ứng dụng dùng nó. Tùy vào stack của bạn:

WordPress: cài plugin Redis Object Cache từ kho plugin, bật tab "Advanced", chọn connection 127.0.0.1:6379, database 0. Sau đó kích hoạt, plugin tự thêm dòng define('WP_REDIS_HOST', '127.0.0.1'); vào wp-config.php.

PHP thuần / Laravel: dùng predis/predis hoặc ext-redis:

sudo apt install -y php8.3-redis
sudo systemctl restart php8.3-fpm

Trong code PHP, ví dụ cache một query đơn giản:

// Kết nối
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// Lấy cache
$data = $redis->get('product_123');
if ($data === false) {
    // Query database
    $data = $db->query("SELECT * FROM products WHERE id = 123")->fetch();
    // Lưu cache 10 phút
    $redis->setex('product_123', 600, serialize($data));
} else {
    $data = unserialize($data);
}

Bước 8 - Kiểm tra hiệu quả cache

Phần quan trọng nhất: cache có thật sự hoạt động không, hay chỉ cài xong cho vui.

Kiểm tra Varnish HIT/MISS:

curl -I http://your-domain.com/ | grep X-Cache

Lần đầu sẽ thấy X-Cache: MISS. Chạy lại lệnh tương tự, lần này phải thấy X-Cache: HIT. Nếu vẫn MISS, nghĩa là request của bạn đang bị pass do cookie hoặc URL không cache được.

Xem thống kê Varnish:

sudo varnishstat

Chú ý hai số: MAIN.cache_hitMAIN.cache_miss. Tỷ lệ hit lý tưởng trên 90%.

Xem Redis có key không:

redis-cli INFO stats | grep keyspace_hits

Số keyspace_hitskeyspace_misses cho biết Redis đang phục vụ cache tốt đến đâu.

Đo hiệu năng tổng thể:

sudo apt install -y apache2-utils
ab -n 1000 -c 20 http://your-domain.com/ | grep "Requests per second"

Xử lý lỗi thường gặp

Lỗi: X-Cache luôn MISS
Nguyên nhân phổ biến nhất là cookie. Nếu trang của bạn set cookie cho mọi khách (ví dụ cookie của Google Analytics, cookie chặn quảng cáo), Varnish sẽ pass toàn bộ. Sửa VCL để bỏ qua cookie analytics:

if (req.http.cookie) {
    if (req.http.cookie ~ "(__ga|_gid|PHPSESSID)") {
        unset req.http.cookie;
    } else {
        return (pass);
    }
}

Lỗi: website trả về 503 sau khi chuyển sang Varnish
Kiểm tra Nginx còn lắng nghe port 8080 không:

sudo ss -tlnp | grep 8080
sudo tail -20 /var/log/nginx/error.log

Lỗi: Redis bị OOM kill
Kiểm tra log kernel:

sudo journalctl -k | grep -i oom | tail -10

Nếu thấy redis trong danh sách, giảm maxmemory xuống. Nếu website của bạn có VPS Linux RAM 1GB, đặt maxmemory 128mb.

Câu hỏi thường gặp

Varnish và Nginx cache khác nhau thế nào?

Nginx có sẵn fastcgi_cacheproxy_cache để cache phản hồi, nhưng Varnish chuyên sâu hơn: kiểm soát TTL chi tiết theo từng URL, có ngôn ngữ VCL linh hoạt, và tối ưu bộ nhớ tốt hơn cho workload cache thuần. Varnish nhẹ hơn Nginx khi xử lý hàng nghìn request cache.

VPS bao nhiêu RAM là đủ để chạy Varnish và Redis?

Với VPS 2GB RAM, bạn cấp 256MB cho Varnish, 256MB cho Redis, phần còn lại cho Nginx + PHP-FPM + database. VPS 4GB thoải mái hơn nhiều, cấp 512MB cho mỗi loại. Dưới 2GB thì nên cân nhắc chỉ dùng một trong hai.

Khi nào nên xóa cache Varnish hoặc Redis?

Sau khi deploy code mới, cache cũ có thể hiển thị nội dung lỗi thời. Xóa toàn bộ: sudo varnishadm ban req.url "~ /" cho Varnish, redis-cli FLUSHALL cho Redis. Với WordPress, plugin Redis Object Cache có nút "Flush Cache" ngay trong admin.

Redis dùng làm session storage có lợi gì?

Khi bạn chạy nhiều PHP-FPM worker hoặc nhiều server, session lưu trong file sẽ gặp lỗi "session not found". Redis lưu session tập trung, truy cập nhanh hơn, và tự động xóa session hết hạn. Cấu hình trong php.ini:

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"

Bài viết liên quan

Lưu ý: Bài viết mang tính tham khảo, tổng hợp kiến thức chung. Mỗi hệ thống, hạ tầng và nhu cầu có đặc thù riêng, nên kiểm thử trong môi trường an toàn và tham vấn kỹ sư trước khi triển khai thực tế.