Bỏ qua tới nội dung
Kiên

Cache key là một bản cam kết về "thế nào là cùng một search"

· 4 phút đọc

1 key · 2 tenant

Một cái key nói rằng hai request này xứng đáng nhận cùng một câu trả lời, và mọi field bị bỏ ra ngoài key đều đang khẳng định rằng field đó không làm đổi kết quả. Trong gom supplier thì tenant, tập supplier và markup của nó đều làm đổi kết quả — nên một cái key dựng từ mỗi tham số tìm kiếm của khách sẽ trả giá của tenant này cho tenant khác, mà chẳng có lỗi nào bắn ra.

  • Redis
  • cache
  • kiến trúc
  • supplier
Nội dung
  1. Cái key đang khẳng định điều gì
  2. "Cùng một search" ở đây nghĩa là gì
  3. Sai ngược lại cũng tốn tiền
  4. Invalidate là nửa còn lại của bản cam kết
  5. Cái đã thay đổi
  6. Cái rút ra

Cái key đang khẳng định điều gì

Cache key không phải một cái định danh. Nó là một lời khẳng định: hai request nào cùng sinh ra key này thì xứng đáng nhận cùng một câu trả lời. Đọc theo kiểu đó thì mọi field bạn bỏ ra ngoài đều thành lời khẳng định thứ hai — rằng field đó không thể làm đổi kết quả. Những bug cache mình thật sự phải đi sửa gần như chưa bao giờ là bug evict hay bug serialize. Chúng là một trong những lời khẳng định thứ hai đó sai.

"Cùng một search" ở đây nghĩa là gì

Search hotel chạy bất đồng bộ: một queue, mỗi supplier một job, mỗi job được canh bằng một cái lock Redis, worker ghi vào một cache hash dùng chung cộng một meta hash đánh dấu supplier nào đã xong, tầng HTTP gom những gì đã về và trả ra một phân số tiến độ, bên trên tất cả là một deadline tuyệt đối 30 giây. Cache ở đây không phải thứ tối ưu gắn thêm bên hông cái search. Nó là chỗ câu trả lời được ráp lại. Nên cái key chính là danh tính của câu trả lời, và một cái key sai thì không làm search chậm đi — nó trả về search của người khác.

Phần tham số phía khách là phần hiển nhiên: điểm đến, ngày, số khách. Cái bẫy là chỉ bấy nhiêu thì chưa đủ. Mỗi tenant — trong từ vựng của tụi mình là "switch" — có tập supplier riêng và cấu hình markup riêng. Hai tenant gửi hai cái search giống nhau đến từng byte thật ra không hỏi cùng một câu: tenant này có thể có supplier mà tenant kia không được thấy, và mỗi bên cộng markup khác nhau lên cái gì trả về. Bỏ tenant ra khỏi key thì tenant thứ hai được phục vụ bằng giá của tenant thứ nhất, từ đúng đám supplier của tenant thứ nhất. Không có exception nào. Giá đó là giá thật, chỉ là không phải giá của họ.

White-label làm chuyện này gắt thêm chứ không nhẹ đi. Một tích hợp được bán lại dưới nhiều brand code, với một bảng settings thưa theo từng supplier, chỉ giữ những key khác mặc định. Một setting đã khác đi thì theo định nghĩa là một field làm đổi câu trả lời — nên hoặc chính nó phải nằm trong key, hoặc cái tenant quyết định ra nó phải nằm trong key.

Sai ngược lại cũng tốn tiền

Chỉnh quá tay thì gặp cái hỏng đối xứng: một cái key có nhét request ID, hoặc timestamp tới giây, hoặc bất cứ thứ gì duy nhất theo từng lần gọi. Thế là key không bao giờ được dùng lại. Mọi lượt search lại fan-out sang toàn bộ supplier — mỗi supplier một job, mỗi job cõng đúng độ trễ của supplier đó trong ngân sách 30 giây — trong khi cache vẫn đầy lên, vẫn evict, vẫn ăn memory, và không hit lấy một lần.

Cái này im hơn vụ trả nhầm giá của tenant khác, vì có gì sai đâu. Nó chỉ chậm và tốn, và nhìn từ bên ngoài thì y hệt "supplier chậm" — một chẩn đoán không ai buồn cãi. Hai kiểu hỏng là cùng một lỗi với hai dấu ngược nhau: không ai viết ra cái key này định khẳng định điều gì.

Invalidate là nửa còn lại của bản cam kết

Bản cam kết có một vế thứ hai — và khi điều này thôi đúng thì phải có người flush. Cái kích hoạt thường gặp nhất là dữ liệu mapping nằm dưới một region: đổi xem khách sạn nào thuộc region đó thì mọi search đã cache trên region đó thành câu trả lời cũ, được tính hoàn toàn đúng trên một tập dữ liệu không còn tồn tại.

"Flush hết" không phải cách chữa, nó là một cú giẫm đạp. Xoá sạch cache tức là đẩy mọi lượt search sau đó cùng lúc bắn ra toàn bộ supplier, đâm thẳng vào đúng cái rate limit mà cache sinh ra để né. Cú flush phải khoanh đúng những key mà thay đổi đó chạm vào — và chỉ khoanh được nếu cấu trúc key cho phép gọi tên chúng. Đó là một tính chất mà thiết kế của key đã quyết trước cả mấy tháng, trước khi có ai cần tới.

Cái đã thay đổi

Cái key thôi được "dựng" và bắt đầu được "khai báo". Trước khi viết key thì viết lời khẳng định ra bằng chữ, trong ghi chú thiết kế: hai request được hưởng cùng một câu trả lời khi chúng cùng tenant, cùng tập supplier, cùng cấu hình markup, cùng điểm đến, cùng ngày và cùng số khách. Khi đó mỗi field trong key đều có một câu đứng sau, mỗi field bị bỏ ra đều phải được cãi thành lời, và vế invalidate có chỗ để nằm — thay đổi này chạm vào những key nào.

Mình không có con số hit rate trước/sau cho chuyện này. Hit rate theo từng kiểu key không phải thứ tụi mình có theo dõi, và mình sẽ không bịa ra một con số; lập luận ở đây là về tính đúng của lời khẳng định, còn phần tiền bạc thì mình chỉ mô tả được chứ không đo được.

Cái rút ra

Viết ra cái key khẳng định điều gì, bằng chữ, trước khi viết cái key. Nếu câu đó viết mãi không xong thì cái key chưa sẵn sàng — còn nếu câu đó viết ra dễ dàng mà lại thiếu mất một field, thì bạn vừa tìm ra đúng cái tenant sắp bị người khác nhìn thấy giá.