1 điểm bởi GN⁺ 4 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Cache được đưa vào để giảm tải cho cơ sở dữ liệu, nhưng công cụ dễ dùng như Redis theo thời gian rất dễ bị phụ thuộc như một kho lưu trữ bền vững
  • Vấn đề không nằm ở tính năng persistence của Redis, mà ở quy trình vận hành khi một thành phần ban đầu được đưa vào như cache volatile lại dần gắn với trạng thái cốt lõi của ứng dụng
  • Ngay từ định nghĩa chính thức, memcached là hệ thống cache đối tượng trong bộ nhớ phân tán, không giả định lưu trữ trên đĩa, nên dễ xử lý như một workload cache không trạng thái
  • Nhiều instance memcached không được chia bởi máy chủ mà bởi client thông qua danh sách URL và hash key; node lỗi sẽ bị loại khỏi hasher rồi sau đó được thử kết nối lại
  • Thay vì thêm cache trước chỉ vì “cơ sở dữ liệu chậm”, nên kiểm tra truy vấn chậm và index bị thiếu trước

Khi Redis biến từ cache thành storage

  • Khi vận hành hạ tầng, yêu cầu kiểu “cần cache” xuất hiện rất thường xuyên, và Redis quen thuộc, nhiều tính năng dễ trở thành lựa chọn đầu tiên
  • Trang chủ Redis đang đưa Redis Iris, công cụ real-time context engine cho ứng dụng AI, lên vị trí nổi bật, nhưng đây cũng là hướng đi dễ hiểu với một công ty phải tạo doanh thu như Redis
  • Chỉ cần triển khai Redis và đưa chuỗi kết nối vào là ban đầu nó hoạt động như một cache đáng tin cậy

Những vấn đề phát sinh vài tháng sau

  • Theo thời gian, vì cache.set("key", "value") đơn giản hơn nhiều so với INSERT INTO table VALUES ('key', 'value'), mọi người bắt đầu đối xử với Redis như sau
    • Một thành phần luôn tồn tại, là nơi giữ dữ liệu. Trên thực tế là một cơ sở dữ liệu
    • REmote DIctionary Server bắt đầu được nhìn nhận như kho lưu trữ lâu dài chứ không còn là cache volatile
  • Cả bạn lẫn các đồng nghiệp trong đội vận hành đều không nhận ra điều này, và vì nghĩ rằng cache sẽ được giả định là volatile nên hệ thống cảnh báo (alerting) cũng không phát hiện ra
    • Chỉ đến khi bạn làm gì đó với Redis như nâng cấp, chuyển node, hoặc gặp sự cố như khay HDD RAID0 của máy chủ bị rút ra thì vấn đề mới lộ rõ
  • Vấn đề cốt lõi không phải Redis thiếu khả năng persistence, mà là giả định sai rằng mọi người sẽ đối xử với Redis được đưa vào làm cache đúng như một cache
  • Khi phát hiện sự phụ thuộc quá muộn, Redis đã bị gắn quá sâu vào ứng dụng đến mức khó loại bỏ, và cuối cùng phải được bảo trì và giám sát như một “thú cưng”

Vì sao memcached trực diện hơn cho vai trò cache

  • memcached là “hệ thống cache đối tượng trong bộ nhớ phân tán, miễn phí, mã nguồn mở, hiệu năng cao”, một cache đa dụng để giảm tải cơ sở dữ liệu và tăng tốc các ứng dụng web động
  • Trong các framework hỗ trợ caching dạng plugin như Django, có thể thay đổi backend cache
  • Dù có ít tính năng hơn Redis, vẫn có lý do để chọn memcached vì đặc tính vận hành của nó đơn giản
    • Xử lý downtime dễ hơn: thư viện client thường bỏ qua ngoại lệ kết nối, và get đơn giản vẫn có thể trả về giá trị mặc định hoặc None khi máy chủ ngừng hoạt động
    • memcached không tích hợp sẵn tính năng clustering nên trái lại việc clustering trở nên thuận tiện hơn
      • Chỉ cần cấu hình nhiều URL trong thư viện client, nó sẽ chọn instance đích bằng hash key
      • Nếu lệnh gọi từ client phát hiện instance bị down, nó sẽ loại node khỏi hasher rồi tự động thử kết nối lại sau một khoảng thời gian
    • Gánh nặng persistence được giảm về mặt cấu trúc: memcached không lưu xuống đĩa nên rất phù hợp để schedule ở bất cứ đâu như một workload không trạng thái
  • Có thể xây dựng cách vận hành tương tự với Redis, nhưng kiến trúc của memcached gần với hướng này hơn nên trực quan hơn khi xử lý như một cache
  • memcached là một ứng dụng tương đối đơn giản, và việc chạy hàng chục instance với kích thước cache khoảng 64MB hầu như không có overhead cũng là một lý do để chọn nó
  • Nhiều vấn đề kiểu “cơ sở dữ liệu chậm” thực ra bắt đầu từ truy vấn chậm hoặc index bị thiếu, vì vậy cùng với việc thêm cache cũng cần xem xét tối ưu truy vấn
  • Nếu tò mò về các quyết định thiết kế của memcached, có thể đọc nhiều bài thú vị trên memcached blog, trong đó có bài đăng vào tháng 5 là “Thực sự thì mất bao lâu để nhận được phản hồi? (How Long Does That Response Take… For Real?)”

1 bình luận

 
Ý kiến Hacker News
  • Redis là một công nghệ tuyệt vời, nhưng tôi nghĩ nó gặp khó khăn khi cố одновременно làm tốt hai vai trò khác nhau: cấu trúc dữ liệu bền vữngbộ nhớ đệm tạm thời
    Ngay trong Redis thực tế, hai thứ này cũng không hòa trộn tốt với nhau, nên tính bền vững thường được bật hoặc tắt ở phạm vi toàn cục
    Với cache thuần túy thì tôi sẽ dùng memcached hoặc thứ tương đương, và chỉ dùng Redis có bật persistence khi cần các cấu trúc dữ liệu như bảng điểm
    Ở $WORK, chúng tôi không dùng cả hai; với tầng cache cho các tác vụ chậm, dữ liệu được đặt ở cả hệ thống tệp lẫn các bảng DB dùng như kho khóa-giá trị
    DB giúp điều phối thundering herd, các lượt đọc từ cùng máy chủ chỉ chạm vào hệ thống tệp, còn các lượt đọc từ máy chủ khác sẽ xem DB một lần rồi giữ lại trong hệ thống tệp
    Có thể thay tầng hệ thống tệp bằng memcached, nhưng cho đến nay cách hiện tại đang hoạt động rất tốt

    • Sau khi từng làm với Memcachedb (memcache + bdb cho persistence) vào cuối những năm 2000, tôi đã đi đến gần như cùng một kết luận
      Redis rõ ràng có nhiều tính năng hơn, và antirez cũng là một người cuốn hút nhưng đáng kinh ngạc là rất khiêm tốn, nên tôi hiểu vì sao Redis trở nên phổ biến hơn
      Dù vậy, với tôi memcached luôn là đỉnh cao của triết lý hãy chọn công nghệ nhàm chán
      Với vai trò kỹ sư nền tảng, tôi có thể hỗ trợ cả hai, nhưng khi nhà phát triển bắt đầu dùng các tính năng nâng cao của Redis như persistence, replication, clustering thì tôi sẽ muốn chắc rằng họ thực sự hiểu mặt trái của quyết định đó
    • Chỉ bằng cách dùng bảng DB như kho khóa-giá trị và kết hợp thêm hệ thống tệp, bạn đã có thể làm được rất nhiều việc trước khi phải trả chi phí thiết lập một kho cache chuyên dụng
      Mỗi lần đề xuất kiểu giải pháp này, tôi lại phải tranh cãi không biết bao nhiêu lần với những người còn non kinh nghiệm và cảm thấy cache nhất định phải nằm trong một kho chuyên dụng
  • Không phải cứ dùng memcache là tránh được những vấn đề này
    Vào giữa những năm 2000, tôi từng làm với một hệ thống mở rộng dùng memcache, và các lập trình viên đã rơi đúng vào những cái bẫy mà bài viết lấy Redis làm ví dụ
    Họ cố lách các quy luật của hệ phân tán bằng memcache, và do nghiện cache, họ định cỡ cụm máy chủ dựa trên giả định memcache luôn hoạt động; đến khi sự cố xảy ra thì mọi thứ bùng nổ như một cuộc DDoS
    Cũng có khuếch đại ghi khi một host làm rơi một key có TPS cao thì mọi host khác đều đập vào dịch vụ phụ thuộc để nạp lại key đó; hot key tạo ra hot host, và việc chạy memcached cùng daemon dịch vụ còn dẫn đến các đợt tăng CPU bí ẩn
    Các lời gọi memcache đôi khi cũng rơi vào hố đen vì tính cố chấp của các bản ghi DNS cũ
    Tất cả đều có thể tránh được nếu dùng memcache tốt hơn, nhưng cám dỗ lạm dụng nó quá lớn

  • Tôi nghĩ mình đã thấy gần như toàn bộ các vấn đề Redis/Valkey mà tác giả nhắc tới trong môi trường production
    Đã từng có sự cố Valkey không có chính sách bộ nhớ nên ăn hết RAM và gây lỗi ghi append-only file, và cũng có trường hợp việc ghi AOF thất bại vì chính đĩa đã đầy
    Cũng từng có lúc người ta hoàn toàn kỳ vọng Redis đang sống, đang chạy và được lấp đầy bằng toàn bộ dữ liệu người dùng, nhưng lại không có cơ chế quay về đường chậm nên trả ra lỗi 500
    Cũng có trường hợp dùng sorted set và các cấu trúc dữ liệu khác một cách sáng tạo, đồng thời phụ thuộc vào việc tập hợp đó tuyệt đối không bao giờ bị trục xuất
    Dù quan sát thực tế là vậy, tôi vẫn thấy khó để khuyến nghị memcache trước Redis
    Thiết kế ứng dụng để có cách bố trí cache thân thiện với memcache có thể khá khó, và nếu một đội ngũ đủ lớn dùng memcache thì khả năng rất cao là cuối cùng họ cũng sẽ tìm ra con đường dẫn đến việc cần Redis
    Khi đó phải duy trì 2 công nghệ cache

    • Nếu ai đó quyết định dùng Redis cho mục đích không phải cache thì thực ra bạn đã có 2 công nghệ cache rồi
      Một instance Redis cấu hình cho cache thì không thể dùng cho mục đích khác; instance cache phải có eviction, còn instance không phải cache thì không được có eviction
      Kết cục là bạn vẫn cần một Redis thứ hai với cấu hình khác
      Thành thật mà nói, thiết kế ứng dụng theo cách bố trí cache thân thiện với memcache cũng giống hệt thiết kế theo cách bố trí cache thân thiện với Redis
      Mẫu hình của loại cache ở tầng ứng dụng này là như nhau: lấy dữ liệu, nếu không có thì tính toán rồi set vào
    • Nếu chưa có thì tôi sẽ tạo một giao diện trừu tượng hóa, để khi yêu cầu key có thể truyền vào một hàm bất đồng bộ hoặc lambda lấy giá trị từ nguồn gốc khi cache miss
      var value = cache.lookup( keyname, () => db.query(...), TimeSpan.FromMinutes(5) // or CacheOptions );
      Làm vậy thì khi cache miss có thể lập tức đi theo đường thay thế hoặc chèn giá trị vào
    • Không phải duy trì 2 công nghệ cache luôn là một lập luận chiến thắng
  • Một đặc điểm khác của memcache ít được nhắc tới là mọi thao tác theo thiết kế đều là O(1)
    Đó là lựa chọn có chủ đích của các tác giả, nên dù có ràng buộc nhưng nó đảm bảo sẽ không có chuyện bị khựng ngẫu nhiên trong các thao tác đơn giản
    Ngược lại, Redis có thiết kế lõi đơn luồng nên có thể chạy các phép toán với độ phức tạp tùy ý; từ góc nhìn của lập trình viên, dùng những thứ đó có thể khiến bạn cảm thấy mình thông minh hơn, nhưng toàn bộ phần còn lại sẽ phải chờ cho đến khi phép toán đó kết thúc

  • Với các dự án mã nguồn mở hoặc chương trình được duy trì lâu dài, chuyện này xảy ra khá thường xuyên
    Khi codebase lớn lên, cuối cùng nó bắt đầu hỗ trợ những thứ vốn không có trong kế hoạch ban đầu
    Khi tính năng tăng lên thì người dùng cũng nhiều hơn; có người chỉ dùng tính năng cũ, có người chấp nhận tính năng mới, và rốt cuộc một số giá trị nào đó trở thành mặc định trên thực tế đến mức không còn trông như là tùy chọn nữa
    Lấy Redis làm ví dụ, nếu tắt AOF thì nó hoạt động như một bộ nhớ đệm in-memory dễ mất dữ liệu, nhưng đa số thậm chí không nghĩ về nó theo cách đó
    Vì vậy mới xuất hiện lập luận rằng ít tính năng hơn và đơn giản hơn thì tốt hơn, và trong ngữ cảnh này Memcached là một ví dụ của cách tiếp cận gò bó có chủ đích
    Điều này hoàn toàn hợp lý với các đội lớn, nhưng các dự án mã nguồn mở cần cập nhật định kỳ để tiếp tục thu hút tài trợ hoặc đóng góp, nên có một sự căng thẳng nội tại
    Đôi khi điều đó dẫn tới các bản fork hoặc dự án phái sinh chuyên biệt cho một ngách nào đó
    Cá nhân tôi cho rằng không có đáp án đúng duy nhất, mọi thứ tùy vào bối cảnh
    Vì bản thân giao tiếp cũng không phải miễn phí

    • Việc giao tiếp không miễn phí chính là vấn đề tôi gặp với microservices
      Có vẻ như các lập trình viên hoàn toàn không nhận ra điều đó
    • Ví dụ rõ ràng nhất là mọi người nghĩ Redis chỉ hoạt động như một bộ nhớ đệm có thể mất dữ liệu khi crash hoặc tắt máy
      Tôi cho rằng vì họ thay Memcached bằng Redis và mong đợi đúng y hệt như vậy
    • Ở quy mô lớn, AOF gây ra sự cố nên người ta sẽ tắt nó đi
      Dù vậy nó vẫn là một bộ nhớ đệm tuyệt vời
  • Trong vài năm qua tôi đã làm khá nhiều với Flask, và dù không phải toàn thời gian, tôi đã dùng nó như một phần stack công nghệ cho một doanh nghiệp thương mại điện tử nhỏ
    Với MongoEngine, SQLAlchemy, Celery, và stack Python cho Google/eBay/Shopify, tôi từng gặp đủ kiểu mìn chôn và sự kỳ quặc, nhưng với Redis thì chưa từng
    Có lẽ cũng vì tôi không trao quyền admin cho bất kỳ ai nghĩ Redis là một kho lưu trữ bền vững, nhưng thành thật mà nói tôi muốn mô tả Redis là một công nghệ cực kỳ vững chắc và được thiết kế tốt
    API cực kỳ đơn giản, và mỗi khi cần làm điều gì hơi lạ, luôn có một cách hợp lý và được suy nghĩ kỹ càng để làm

    • Tôi đang bắt đầu một dự án với Flask, SQLAlchemy và Celery, nên muốn nghe thêm về lý do nên tránh Celery và nên dùng gì thay thế
    • Trong thế giới của tôi, các hệ thống cache như memcached và Redis chỉ đơn giản là cache để ghi vào rồi lấy ra
      Có thể dùng các hệ thống vô hiệu hóa như tagging
      Tôi thực sự tò mò về những việc kỳ lạ có thể làm với hệ thống cache, ngoài việc chỉ cache dữ liệu thì mọi người còn dùng cache để làm gì
  • Tôi thích memcached, nhưng nếu bạn cấu hình Redis làm cache dễ mất dữ liệu mà mọi người lại đối xử với nó như kho lưu trữ dữ liệu bền vững, thì đó không phải lỗi của Redis
    Việc so sánh này đặc biệt kỳ lạ vì memcached cũng không có tính bền vững

    • Ở nhiều công ty, có lẽ là đa số, Redis không được xem là cache có thể biến mất bất kỳ lúc nào mà là cơ sở dữ liệu production có độ bền thực sự, và được vận hành theo cách đó
      Nếu không được nói riêng, việc một lập trình viên mới giả định như vậy cũng không phải là vô lý
  • Memcached là vị cứu tinh của caching vào thời điểm nó ra mắt
    Việc nó được Brad Fitzpatrick tạo ra cho LiveJournal vào năm 2003 cũng rất thú vị
    Mỗi bài đăng trong feed người dùng có thể có giới hạn truy cập khác nhau, và nhờ đó có thể cache từng bài đăng hoặc cả trang
    Tôi đã dùng nó nhiều năm cùng với Ruby on Rails, các trang nhanh hơn và nó đơn giản là hoạt động tốt
    Nhược điểm, đồng thời cũng là ưu điểm về tốc độ, là cache được lưu trong bộ nhớ chứ không phải trên đĩa
    Nếu dữ liệu cần cache rất lớn và là một site quy mô lớn, chi phí hosting có thể trở nên đắt đỏ
    Trong những trường hợp đó, Solid Cache là vị cứu tinh với tôi
    Dự án tôi đang làm hiện có hơn 100GB cache, được lưu trên đĩa PostgreSQL, tra cứu nhanh bằng chỉ mục và Rails tự động xử lý hết hạn để xóa các hàng đó
    Nếu quy mô cache nhỏ hơn và bạn đã dùng Redis rồi, có lẽ tôi sẽ cứ dùng Redis
    Nhưng nếu tốc độ là ưu tiên số một, tôi sẽ benchmark Memcached và Redis

  • Tính chất tạm thời của memcached và chuyện mọi người có dùng nó như thể nó là thứ bền vững hay không là hai vấn đề khác nhau
    Nếu tỷ lệ cache hit trông như 99,9% và dữ liệu gần như luôn hiện diện, sớm muộn gì cũng sẽ có người viết code phụ thuộc vào hành vi đó
    Tôi tự hỏi liệu ở chế độ phát triển, thư viện client có thể giúp bằng cách trả về null khoảng 10% số lần hay không

  • memcached nhanh hơn Redis một cách vô lý trong tác vụ cache key-value đơn giản
    Nó có thread và được tối ưu hóa rất cao để làm thật tốt đúng một việc
    Trong khi đó Redis giống một Python heap dùng chung tùy ý hơn, với đủ loại cấu trúc dữ liệu và mô hình đơn luồng các thứ
    Ở Notion, họ dùng Redis cho nhiều mục đích, nhưng giao việc cache thực sự cho memcached

    • Có thể xác nhận rằng ở key-value, chênh lệch không đến mức như vậy
      Trung bình vào khoảng 300 micro giây so với 350 micro giây mỗi lần đọc
      Việc đơn luồng cũng không quá quan trọng, vì nút thắt không nằm ở CPU mà ở I/O đáp ứng
    • Thread không phải là miễn phí
      Nó cho phép dùng nhiều lõi CPU hơn, nhưng nếu tải không quá cao thì memcached đơn luồng sẽ dùng ít CPU hơn bản đa luồng