Redis qua VPN có cảm giác chậm: cắt bớt số lượt khứ hồi mỗi yêu cầu
VPN không biến một lệnh gọi Redis từ xa thành cục bộ. Hãy tính ngân sách lượt khứ hồi cho mỗi yêu cầu, dùng lại kết nối và giữ pipeline tránh xa giao dịch.
Một trang trả lời trong 200 ms với Redis cục bộ sẽ mất bốn giây khi máy khách nằm sau VPN, trong khi các bộ đếm của máy chủ vẫn ngồi không. Slow Log không cho thấy gì vượt quá một mili giây, đồ thị CPU phẳng lì, và instance vẫn phục vụ các máy khách khác mà không phàn nàn. Thứ đã thay đổi là khoảng cách, và thứ nhân khoảng cách lên là số lần mã của bạn dừng lại và chờ. Hãy đi dọc đường đi của yêu cầu từ máy khách ra ngoài và cách sửa hầu như luôn là ít lượt chờ tuần tự hơn, chứ không phải một máy chủ nhanh hơn.
Hãy bắt đầu từ máy khách, không phải từ số liệu của máy chủ
Quan sát tiến trình máy khách giữa lệnh đầu tiên và lệnh cuối cùng. Nếu vết cho thấy một phản hồi, một khoảng dừng, rồi lệnh tiếp theo, bạn đang trả một lượt khứ hồi trọn vẹn cho mỗi lệnh và không lượng tinh chỉnh máy chủ nào thay đổi được điều đó. Slow Log là công cụ sai ở đây, vì nó ghi thời gian thực thi và cố ý loại trừ I/O liên lạc. Một Slow Log sạch bên cạnh một endpoint chậm chính là dấu hiệu của vấn đề lượt khứ hồi.
Hãy đo chính đường đi trước khi chạm vào mã. redis-cli -h redis.example.internal --latency gửi PING lặp lại và in ra thời gian trung bình của yêu cầu và phản hồi, đó là chi phí mỗi lần trao đổi của bạn. Nhân nó với số lệnh tuần tự trong một thao tác người dùng và bạn có mức sàn cho endpoint.
Đếm số lượt khứ hồi mà một thao tác người dùng thực sự cần
Chọn một endpoint và ghi lại theo thứ tự mọi lệnh gọi Redis, kể cả những lệnh ẩn trong ORM, session middleware hay một lần tra feature flag. Phần lớn endpoint chậm thực hiện hai mươi hay ba mươi lệnh gọi ở nơi ba lệnh là đủ, và những lệnh ẩn thường chiếm đa số.
Một chuỗi phụ thuộc không thể gộp lại: nếu lệnh thứ hai cần khóa do lệnh thứ nhất trả về, thì việc chờ là thật và lựa chọn duy nhất là một mô hình dữ liệu khác hoặc một thao tác phía máy chủ diễn đạt toàn bộ bước trong một lần. Các lần đọc độc lập là chuyện khác, vì không gì buộc bạn phải chờ phản hồi đầu trước khi gửi cái thứ hai. Hãy đánh dấu mỗi lệnh gọi là phụ thuộc hay độc lập trước khi sửa bất cứ thứ gì, và hình dạng của cách sửa sẽ trở nên rõ ràng.
Con số quan trọng là số lượt chờ tuần tự, không phải tổng số lệnh. Ba mươi lệnh trong một batch vẫn chỉ tốn một lượt chờ.
Chỉ batch những lệnh không phụ thuộc lẫn nhau
Pipeline xếp các lệnh độc lập vào hàng đợi và thu phản hồi theo thứ tự, biến N lượt chờ thành một. Phần lớn máy khách hiển thị nó như một khối, một callback hay một context manager thay vì một chế độ kết nối riêng, nên hãy kiểm tra API mà phiên bản bạn cài đặt cung cấp. Lợi ích tăng theo thời gian khứ hồi, và đó chính là lý do nó quan trọng trên VPN và gần như không thấy trên localhost.
import redis
r = redis.Redis(host="redis.example.internal", port=6379, socket_timeout=2)
pipe = r.pipeline(transaction=False)
for key in ("user:1001", "user:1002", "user:1003"):
pipe.get(key)
print(pipe.execute())
Hãy truyền transaction=False khi bạn chỉ muốn batch. Với giá trị mặc định, máy khách bọc batch trong MULTI và EXEC, điều này làm nó có tính nguyên tử và thay đổi những gì máy khách khác có thể quan sát giữa chừng. Tính nguyên tử là một quyết định đúng đắn, không phải núm hiệu năng, và pipeline không phải là giao dịch chỉ vì cả hai đều giảm lượt khứ hồi.
Hãy đọc mọi phản hồi chứ không chỉ việc gửi batch đi. Một lệnh trong hàng đợi có thể trả lỗi trong khi cả batch vẫn hoàn tất, và một máy khách vứt bỏ danh sách phản hồi sẽ biến một lần ghi thất bại thành một lần ghi im lặng. Hãy so độ dài danh sách phản hồi với số lệnh bạn đã xếp vào hàng đợi.
Giữ kết nối sống giữa các thao tác
Mỗi kết nối mới phải trả cho việc phân giải tên, một lần bắt tay TCP, một lần bắt tay TLS và một lần AUTH, và trên VPN mỗi thứ trong đó là một lượt khứ hồi bạn không thể batch bớt. Hãy tạo máy khách một lần cho mỗi tiến trình và dùng lại, hoặc dùng pool mà thư viện của bạn cung cấp với kích thước có giới hạn. Kết nối lại theo từng yêu cầu là nguyên nhân phổ biến thứ hai của triệu chứng này, sau việc chờ theo từng lệnh.
Giữ hostname riêng trong cấu hình thay vì một địa chỉ nguyên văn. Việc kiểm tra chứng chỉ và danh tính dịch vụ vẫn áp dụng trên mạng riêng, và ghim một IP sẽ hỏng ngay khi nhà cung cấp đánh số lại subnet. Nếu phân giải tên là phần chậm, hãy sửa đường đi của resolver chứ không phải chuỗi kết nối.
Đặt một socket timeout rõ ràng để một đường hầm bị kẹt thất bại nhanh thay vì treo một worker. Sau đó xác nhận pool thực sự được dùng lại bằng cách ghi log việc tạo kết nối trong máy khách.
Định cỡ batch trước khi nó thành vấn đề bộ nhớ
Redis giữ các phản hồi đã xếp hàng trong suốt thời gian của batch, và máy khách giữ kết quả đã phân tích sau đó. Một pipeline mười nghìn giá trị lớn đổi lượt chờ mạng lấy những cú nhảy bộ nhớ và những khoảng dừng dài ở cả hai đầu.
Chọn kích thước batch theo khối lượng phản hồi chứ không theo số lệnh. Vài trăm lệnh nhỏ trong một batch thường là thoải mái, còn một trăm payload vài kilobyte thì không. Hãy chia theo byte lẫn theo số lượng, và giữ mỗi batch trong ngân sách độ trễ mà endpoint thực sự có.
Đo từ đầu đến cuối sau khi thay đổi: redis-cli -h redis.example.internal --latency-history cho thấy bản thân đường đi có ổn định không, và bộ đếm thời gian yêu cầu của bạn cho thấy endpoint có cải thiện không. Nếu endpoint vẫn chậm trong khi lịch sử độ trễ phẳng, chi phí còn lại là công việc phía máy chủ hoặc serialization của chính bạn, và bước tiếp theo là profile handler chứ không phải thêm một thay đổi batch nữa.