SydexaSydexa
Trang chủKhoá họcBài viết
Một thuật toán từng bị xem là chậm... và cú “phản đòn” ngoạn mục của nó
Backend

Một thuật toán từng bị xem là chậm... và cú “phản đòn” ngoạn mục của nó

Hai Phuong
|
21/10/2025
|
5 phút đọc

Có những nhận định và kiến thức được truyền bá rộng rãi khiến nhiều người nghĩ rằng chúng chỉ có thể là như vậy.

Trong những số đó là một thuật toán được sử dụng để implement cơ chế thay thế cache khi bể cache đầy: LFU cache (Least Frequently Used) chỉ có thể đạt được average-case time complexity là O(log n) vì thuật toán này phải sử dụng một cây min-heap. Điều này đã được chấp nhận suốt nhiều năm, khiến nhiều developer chọn LRU (Least Recently Used) với average-case time complexity là O(1) thay vì LFU cho dù LFU phù hợp hơn cho use case của họ.

🔍 Tại sao lại là min-heap?

Bài toán cốt lõi là:

Khi cache đầy và ta cần thêm một phần tử mới vào cache, làm sao tìm được phần tử có tần suất truy cập thấp nhất một cách nhanh nhất để loại bỏ nó khỏi cache và nhường chỗ cho phần tử mới? Cây min-heap trở thành lựa chọn vì tính chất của nó là luôn giữ phần tử nhỏ nhất (tần suất thấp nhất) ở root node nên việc xác định phần tử cần loại chỉ mất O(1).

Cách làm truyền thống với min-heap:

Hãy tưởng tượng bạn là một thủ thư và trên bàn làm việc của bạn có một chồng sách top 10 cuốn sách hay được mượn, quy tắc để quản lý chồng sách chỉ có 10 cuốn đó của bạn là “luôn loại bỏ cuốn sách ít được mượn nhất để thay thế khi có một cuốn sách mới nổi lên lọt vào top 10”. Bạn sẽ sắp xếp tất cả sách thành một kim tự tháp, với sách ít được mượn nhất luôn đặt ở đỉnh tháp để lúc nào cũng có thể lấy nó ra ngay lập tức.

Mỗi khi có người mượn một cuốn sách bất kỳ, bạn cập nhật lại số lần được mượn của cuốn sách đó, rồi sắp xếp lại toàn bộ kim tự tháp để đảm bảo cuốn có tần suất truy cập thấp nhất vẫn ở đỉnh. Quá trình sắp xếp lại này gọi là rebalancing, bạn sẽ phải so sánh và hoán đổi vị trí nhiều cuốn sách từ đỉnh xuống đáy hoặc ngược lại, nên sẽ có time complexity là O(log n).

Điều tồi tệ nhất là việc rebalancing không chỉ xảy ra mỗi khi bạn thực hiện thay đổi tần suất truy cập cho một cuốn sách (access), mà nó sẽ xảy ra ngay cả khi bạn thực hiện loại bỏ cuốn sách có tần suất được mượn thấp nhất (deletion), hay kể cả sau khi loại bỏ và có một cuốn sách mới được thêm vào để thế chỗ cuốn sách đã bị loại bỏ (insert). Đó là lý do tại sao LFU bị "kẹt" ở O(log n).

Cho đến năm 2010, một nhóm nghiên cứu gồm Giáo sư Ketan Shah, Anirban Mitra và Dhruv Matani đã cho ra đời một technical report "An O(1) algorithm for implementing the LFU cache eviction scheme” (paper được đăng tải chính thức trên arXiv năm 2021).

💡 Điều đặc biệt ở đây là gì?

Họ đã tạo ra một cách thông minh hơn để implement thuật toán sử dụng Hash Table và các Doubly Linked List. Cách implement này đã giúp thuật toán LFU có thể đạt average-case time complexity là O(1).

Đột phá của giải pháp O(1) nằm ở việc họ hoàn toàn thay đổi cách impement thuật toán. Thay vì một kim tự tháp dọc, họ tạo ra các kệ sách ngang (mỗi kệ là một Doubly Linked List). Mỗi kệ chứa tất cả sách có cùng số lần được mượn. Kệ đầu tiên chứa sách mượn 1 lần, kệ thứ hai chứa sách mượn 2 lần, và cứ thế. Các kệ này được liên kết với nhau bằng Doubly Linked List, và một hash table giúp bạn tìm ngay vị trí của bất kỳ cuốn sách nào trong chớp mắt.

Khi có người mượn một cuốn sách đang ở kệ 1, sau này bạn chỉ cần chuyển nó sang lưu trữ tại kệ 2 là đã cập nhật được số lần cuốn sách được mượn, một thao tác đơn giản không cần sắp xếp lại gì cả. Khi cần loại bỏ, bạn chỉ việc lấy cuốn đầu tiên trên kệ đầu tiên. Mọi thao tác đều là di chuyển trực tiếp, không cần so sánh hay sắp xếp, do đó đạt được time complexity là O(1).

Và tất nhiên, không có thuật toán nào là hoàn hảo, để đạt được cái này chúng ta cần phải chấp nhận đánh đổi một cái khác.

Cách tiếp cận mới của giáo sư Ketan Shah cũng cần một số sự đánh đổi trong các phương diện khác:

  • Việc sử dụng cách tiếp cận mới sẽ phải tốn nhiều bộ nhớ của RAM hơn để quản lý hash table và các Doubly Linked List.
  • Cách tiếp cận này cũng khó implement, khó maintain hơn so với cách truyền thống.

🎯 Bài học cho chúng ta:

Trong thế giới công nghệ đang thay đổi chóng mặt, đừng bao giờ chấp nhận một giới hạn chỉ vì "mọi người đều nói vậy". Những gì được coi là "tối ưu nhất" hôm nay có thể sẽ bị phá vỡ vào ngày mai bởi một ý tưởng sáng tạo.

Đối với các developer thế hệ mới:

  • Hãy đặt câu hỏi với những giả định cũ
  • Dám thử nghiệm những hướng tiếp cận khác biệt
  • Đừng sợ thách thức "common knowledge"

Bởi vì, đôi khi một bài toán được cho là "đã giải quyết" vẫn đang chờ người đủ tò mò và kiên trì để tìm ra giải pháp tốt hơn.

Link paper: https://arxiv.org/pdf/2110.11602

Bài viết liên quan

Mỗi Tuần Một Database #6: MySQL vs. PostgreSQL: Cuộc chiến vương quyền của thế giới mã nguồn mở
Backend
30/12/2025 · 6 phút đọc

Mỗi Tuần Một Database #6: MySQL vs. PostgreSQL: Cuộc chiến vương quyền của thế giới mã nguồn mở

Nếu ví thế giới Database như ngành công nghiệp ô tô, thì MySQL chính là Toyota: Phổ biến, dễ sửa chữa, phụ tùng thay thế ở khắp nơi và cực kỳ thực dụng. Trong khi đó, PostgreSQL (hay Postgres) giống như Mercedes-Benz: Kỹ thuật cơ khí chính xác, nhiều tính năng cao cấp, an toàn tuyệt đối nhưng đòi hỏi người lái phải có kiến thức. Câu hỏi "Nên dùng MySQL hay PostgreSQL?" có lẽ là câu hỏi gây tranh cãi nhiều nhất trong các buổi phỏng vấn System Design. Nhiều người hiện nay có xu hướng mặc định chọ

Chi tiết bài viết
Mỗi Tuần Một Database #5: TiDB: Khi MySQL mọc thêm đôi cánh "Phân tán"
Backend
22/12/2025 · 7 phút đọc

Mỗi Tuần Một Database #5: TiDB: Khi MySQL mọc thêm đôi cánh "Phân tán"

Hãy quay lại thời điểm bạn là CTO của một sàn thương mại điện tử đang tăng trưởng nóng (giống như Shopee hay Lazada giai đoạn đầu). Ban đầu, bạn dùng một con MySQL duy nhất. Mọi thứ thật tuyệt vời. Nhưng rồi ngày "Black Friday" đến. Dữ liệu đơn hàng tăng vọt lên 5TB. Con server MySQL bắt đầu "thở dốc", CPU 100%, ổ cứng đỏ lòm. Bạn quyết định làm điều mà mọi kỹ sư đều sợ: Sharding (Chia cắt dữ liệu). Bạn chia dữ liệu ra 10 con server MySQL khác nhau: Khách hàng A ở Server 1, Khách hàng B ở Serv

Chi tiết bài viết
Mỗi Tuần Một Database #4: InfluxDB: "Cỗ máy thời gian" lưu giữ nhịp đập của thế giới số
Backend
15/12/2025 · 7 phút đọc

Mỗi Tuần Một Database #4: InfluxDB: "Cỗ máy thời gian" lưu giữ nhịp đập của thế giới số

Hãy thử tưởng tượng bạn là Kỹ sư Vận hành (Operations Engineer) tại CERN (Tổ chức Nghiên cứu Hạt nhân Châu Âu). Bạn đang chịu trách nhiệm giám sát Máy gia tốc hạt lớn (LHC). Hệ thống này không ngủ: hơn 100.000 cảm biến liên tục gửi về các chỉ số nhiệt độ, áp suất, từ trường... với tần suất 100 mili-giây một lần. Đột nhiên, một cảnh báo đỏ xuất hiện: Nhiệt độ tại nam châm siêu dẫn số 4 tăng bất thường. Để ngăn chặn một vụ nổ trị giá hàng tỷ đô la, bạn cần ngay lập tức vẽ biểu đồ biến động nhiệt

Chi tiết bài viết
Mỗi Tuần Một Database #3: Elasticsearch: Gã khổng lồ đứng sau ô tìm kiếm của GitHub
Backend
08/12/2025 · 6 phút đọc

Mỗi Tuần Một Database #3: Elasticsearch: Gã khổng lồ đứng sau ô tìm kiếm của GitHub

Hãy thử tưởng tượng bạn là kỹ sư tại GitHub vào những năm 2013. Bạn đang lưu trữ hàng tỷ dòng mã nguồn (source code) từ hàng triệu kho chứa (repository) khác nhau. Một người dùng gõ vào thanh tìm kiếm từ khóa function login(). Nhiệm vụ của bạn là phải tìm chính xác đoạn code đó trong vài mili-giây. Nếu bạn dùng câu lệnh SQL LIKE %login()% trên MySQL, hệ thống sẽ sập ngay lập tức vì phải quét toàn bộ bảng (full table scan). GitHub cần một giải pháp không chỉ "lưu" dữ liệu, mà phải "hiểu" và "đán

Chi tiết bài viết
Sydexa
Sydexa
SydexaSydexa

Nền tảng học lập trình với animation trực quan và bài tập tương tác, giúp bạn hiểu sâu thay vì chỉ đọc lý thuyết.

Sydexa FanpageBackend GroupFrontend Group

We learn, We share, We grow

Powered By
Cộng đồng System Design Việt Nam

contact@sydexa.com
0971489013