[System Design] P7 - SQL & NoSQL

Ha Ngoc HieuHa Ngoc Hieu
50 min read

I. Từ Lưu trữ Đơn giản đến Cơ sở dữ liệu Phức tạp

Hành trình quản lý dữ liệu ứng dụng thường bắt đầu bằng các phương pháp sơ khai, phát triển thành các hệ thống phức tạp hơn khi yêu cầu tăng lên. Hiểu được sự phát triển này làm nổi bật các vấn đề cơ bản mà các database hiện đại được thiết kế để giải quyết.

A. Những Hạn chế của Lưu trữ Cơ bản

Trong giai đoạn đầu phát triển ứng dụng, dữ liệu có thể được hardcode trực tiếp vào các trang HTML. Tuy nhiên nhược điểm của nó là việc hoàn toàn không thực tế đối với dữ liệu động cần được cập nhật hoặc quản lý.

Một bước tiến là sử dụng các công cụ sẵn có như phần mềm bảng tính (spreadsheet), chẳng hạn như Microsoft Excel. Đối với các ứng dụng nhỏ, đơn giản với dữ liệu hạn chế và ít người dùng, một spreadsheet ban đầu có thể đủ dùng. Tuy nhiên, những hạn chế của việc lưu trữ dựa trên spreadsheet nhanh chóng trở nên rõ ràng khi ứng dụng trưởng thành.

Sự tiến triển từ dữ liệu hardcodedspreadsheets sang các hệ thống có cấu trúc hơn không chỉ là vấn đề sở thích mà còn là phản ứng trực tiếp đối với những hạn chế cố hữu này. Khi các ứng dụng yêu cầu truy cập đồng thời, lưu trữ dữ liệu đáng tin cậy, truy vấn hiệu quả trên các tập dữ liệu lớn và khả năng mở rộng, nhu cầu về một Hệ thống Quản lý Cơ sở dữ liệu (Database Management System - DBMS) chuyên dụng trở nên không thể phủ nhận. Các hệ thống này được thiết kế đặc biệt để giải quyết những thiếu sót của việc lưu trữ dựa trên tệp đơn giản hơn.

B. Giới thiệu về Cơ sở dữ liệu (Databases)

Về cơ bản, database là một tập hợp dữ liệu có tổ chức, được cấu trúc để cho phép quản lý, lưu trữ, truy xuất và thao tác hiệu quả. Nó cung cấp một cách có hệ thống để xử lý thông tin thông qua một tập hợp các hoạt động được xác định.

Các hoạt động cơ bản nhất là: Create, Read, Update và Delete và Search. Các ứng dụng hiện đại đặt ra những yêu cầu đáng kể đối với database nền tảng của chúng. Một số thuộc tính chính rất được mong đợi, mặc dù cần có sự đánh đổi nhất định trong thiết kế hệ thống:

  • Tính Sẵn sàng Cao (High Availability): Hệ thống database phải duy trì hoạt động và có thể truy cập được cho người dùng ngay cả khi đối mặt với các sự cố ảnh hưởng đến hệ thống.

  • Tính Nhất quán Mạnh (Strong Consistency): Sau khi một thao tác cập nhật hoàn tất, tất cả các lần đọc tiếp theo phải trả về giá trị đã cập nhật. Trong một hệ thống phân tán, điều này có nghĩa là tất cả các bản sao dữ liệu có thể truy cập đều phản ánh các ghi (writes) thành công mới nhất.

  • Khả năng Mở rộng Cao (High Scalability): Hệ thống phải có khả năng xử lý một cách trơn tru sự tăng trưởng về khối lượng dữ liệu, tốc độ giao dịch và tải người dùng mà không làm suy giảm đáng kể hiệu suất.

  • Hiệu suất và Tốc độ (Performance & Speed): Các hoạt động database, đặc biệt là đọc (read) và ghi (write) phải thực thi nhanh chóng với độ trễ thấp đáp ứng trải nghiệm người dùng.

Điều quan trọng là phải nhận ra rằng những thuộc tính mong muốn này thường liên kết với nhau và đôi khi mâu thuẫn dẫn đến việc cần sự đánh đổi nhất định trong thiết kế database (Định lý CAP).

II. Thế giới Cơ sở dữ liệu Quan hệ (Relational Databases - SQL)

Relational databases, thường được truy vấn bằng Ngôn ngữ Truy vấn Cấu trúc (Structured Query Language - SQL), đã là mô hình thống trị trong quản lý dữ liệu trong nhiều thập kỷ. Cấu trúc và các đảm bảo giao dịch của chúng cung cấp một nền tảng vững chắc cho nhiều ứng dụng.

A. Mô hình Quan hệ (Relational Model)

Một Hệ thống Quản lý Cơ sở dữ liệu Quan hệ (Relational Database Management System - RDBMS) tổ chức dữ liệu thành các bảng (tables), chính thức được gọi là quan hệ (relations).

Mỗi table bao gồm các hàng (rows - tuples), đại diện cho các bản ghi riêng lẻ, và các cột (columns - attributes), đại diện cho các thuộc tính cụ thể của các bản ghi đó

Ví dụ, một database đại học có thể có table Lecturers với các columns như LID (Lecturer ID) và LectureName, và một table Sessions với các columns như LID, SID (Student ID), và SessionDate, liên kết giảng viên với các buổi học của sinh viên.

Trong lịch sử, đặc biệt là khi chi phí lưu trữ đắt đỏ hơn và khối lượng dữ liệu nhỏ hơn, một động lực chính cho mô hình quan hệ là tối ưu hóa lưu trữ và giảm dư thừa dữ liệu. Điều này đạt được thông qua một quá trình gọi là chuẩn hóa (normalization).

Normalization bao gồm việc chia nhỏ dữ liệu thành nhiều tables, mỗi table tập trung vào một thực thể hoặc khái niệm duy nhất, và sử dụng các khóa (keys) để xác định mối quan hệ giữa chúng. Điều này giảm thiểu việc lưu trữ dữ liệu dư thừa; ví dụ, tên của một giảng viên chỉ được lưu trữ một lần trong table Lecturers, thay vì được lặp lại cho mỗi buổi học mà họ thực hiện.

Mặc dù normalization rất hiệu quả về lưu trữ và đảm bảo tính nhất quán dữ liệu khi có sửa đổi, nó có một hệ quả đáng kể: việc truy xuất một cái nhìn hoàn chỉnh về thông tin liên quan thường yêu cầu kết hợp dữ liệu từ nhiều tables bằng cách sử dụng các phép toán JOIN. Khi khối lượng dữ liệu và độ phức tạp của truy vấn tăng lên, các phép toán JOIN này có thể trở thành một nút thắt hiệu suất chính, một thách thức quan trọng mà các mô hình database thay thế sau này đã tìm cách giải quyết.

B. Thuộc tính ACID: Đảm bảo Độ tin cậy

Một đặc điểm xác định của hầu hết các RDBMS truyền thống là việc tuân thủ các thuộc tính ACID, đảm bảo độ tin cậy của các giao dịch (transactions). Một transaction là một chuỗi các hoạt động database được thực hiện như một đơn vị công việc logic duy nhất.

ACID là từ viết tắt của Atomicity, Consistency, Isolation, và Durability.

  • Tính Nguyên tử (Atomicity - Tất cả hoặc không gì cả): Thuộc tính này đảm bảo rằng một transaction được coi là một đơn vị không thể chia cắt. Hoặc tất cả các hoạt động trong transaction được hoàn thành thành công và được commit vào database, hoặc không có hoạt động nào được thực hiện. Nếu bất kỳ phần nào của transaction thất bại (do lỗi, sự cố hệ thống, v.v.), toàn bộ transaction sẽ được rollback, đưa database về trạng thái trước khi transaction bắt đầu. Một phép loại suy phổ biến là chuyển khoản ngân hàng: việc trừ tiền từ tài khoản A và cộng vào tài khoản B phải xảy ra cùng nhau như một đơn vị atomic duy nhất. Nếu việc ghi có vào B thất bại sau khi ghi nợ từ A, atomicity đảm bảo việc ghi nợ sẽ được hoàn tác.

  • Tính Nhất quán (Consistency - Bảo toàn Trạng thái): Consistency đảm bảo rằng một transaction đưa database từ một trạng thái hợp lệ này sang một trạng thái hợp lệ khác. Database có các quy tắc được xác định trước (ràng buộc - constraints, triggers, kiểu dữ liệu) định nghĩa tính hợp lệ. Một transaction phải đảm bảo rằng khi hoàn thành (nếu thành công), tất cả các quy tắc này đều được thỏa mãn. Ví dụ, nếu một database ngân hàng có quy tắc rằng tổng số dư của tất cả các tài khoản phải không đổi, một transaction chuyển khoản phải bảo toàn tổng số này; nếu không thể, transaction sẽ thất bại và trạng thái database vẫn nhất quán. Một phép loại suy là đảm bảo số lượt thích của một album ảnh khớp với số lượng mục nhật ký liệt kê những người đã thích ảnh đó; consistency ngăn chặn sự không khớp.

  • Tính Cô lập (Isolation - Kiểm soát Đồng thời): Trong các hệ thống nơi nhiều transactions có thể thực thi đồng thời, isolation đảm bảo rằng các transactions này không can thiệp lẫn nhau theo những cách không mong muốn. Hiệu quả của các transactions đồng thời phải giống như thể chúng được thực thi nối tiếp nhau (tuần tự - serially). Nếu không có isolation, một số dị thường có thể xảy ra:

    • Đọc bẩn (Dirty Reads): Một transaction đọc dữ liệu đã được sửa đổi bởi một transaction khác nhưng chưa được commit. Nếu transaction sửa đổi bị rollback, transaction đầu tiên đã đọc dữ liệu "bẩn" hoặc không hợp lệ. (Phép loại suy: Nhìn trộm vào một căn phòng trong khi ai đó vẫn đang dọn dẹp; trạng thái cuối cùng chưa được ổn định).

    • Đọc không lặp lại (Non-Repeatable Reads): Một transaction đọc một phần dữ liệu, một transaction đồng thời khác sửa đổi hoặc xóa dữ liệu đó và commit, transaction đầu tiên đọc lại cùng dữ liệu đó, nhận được một giá trị khác. (Phép loại suy: Đếm táo, ai đó thêm/bớt một quả, đếm lại cho ra một số khác).

    • Đọc Bóng ma (Phantom Reads): Một transaction thực hiện một truy vấn dựa trên một số điều kiện, một transaction đồng thời khác chèn dữ liệu mới thỏa mãn điều kiện đó và commit, transaction đầu tiên thực thi lại truy vấn của mình, tìm thấy các hàng "bóng ma" mới. (Phép loại suy: Kiểm tra một giỏ táo, sau đó phát hiện ra cam xuất hiện trong lần kiểm tra tiếp theo).

    • Mất cập nhật (Lost Updates): Hai transactions đọc cùng một mục dữ liệu, cả hai đều sửa đổi nó dựa trên giá trị đã đọc, sau đó ghi lại. Lần ghi thứ hai ghi đè lên lần ghi thứ nhất, thực sự làm mất bản cập nhật được thực hiện bởi transaction đầu tiên. (Phép loại suy: Hai nghệ sĩ vẽ chồng lên tác phẩm của nhau trên cùng một tấm canvas). Các hệ thống database triển khai isolation bằng cách sử dụng các cơ chế như khóa (locking - kiểm soát bi quan, nơi tài nguyên bị khóa để ngăn xung đột) hoặc kiểm soát đồng thời đa phiên bản (multi-version concurrency control - MVCC) (kiểm soát lạc quan, nơi các phiên bản khác nhau của dữ liệu được duy trì). Các mức isolation khác nhau (ví dụ: Read Uncommitted, Read Committed, Repeatable Read, Serializable) cung cấp các mức độ bảo vệ khác nhau chống lại các dị thường này, đánh đổi các đảm bảo consistency để có được lợi ích về performance tiềm năng.

  • Tính Bền vững (Durability - Tính bền bỉ): Một khi một transaction đã được commit thành công, những thay đổi của nó sẽ được thực hiện vĩnh viễn trong database. Những thay đổi này phải tồn tại sau các sự cố hệ thống tiếp theo, chẳng hạn như mất điện hoặc sự cố (crashes). Điều này thường đạt được bằng cách sử dụng các kỹ thuật như ghi các thay đổi vào nhật ký giao dịch (transaction logs - Write-Ahead Logging hoặc WAL) trước khi áp dụng chúng vào các tệp dữ liệu chính, đảm bảo rằng các quy trình phục hồi có thể khôi phục các thay đổi đã commit sau một sự cố. Phép loại suy là lưu tiến trình trong một trò chơi điện tử; một khi đã lưu, tiến trình vẫn tồn tại ngay cả khi trò chơi bị crash sau đó.

Các đảm bảo ACID tạo thành một hợp đồng cơ bản giữa database và nhà phát triển ứng dụng. Bằng cách dựa vào các thuộc tính này, các nhà phát triển có thể đơn giản hóa logic ứng dụng, vì họ không cần phải xây dựng các cơ chế phức tạp trong chính ứng dụng để xử lý các lỗi cục bộ hoặc các tình huống concurrency control phức tạp. Hệ thống database đảm nhận trách nhiệm này, điều đặc biệt quan trọng đối với các ứng dụng đòi hỏi data integrity cao, chẳng hạn như hệ thống tài chính, nền tảng thương mại điện tử và quản lý hàng tồn kho.

III. Cơ sở dữ liệu Phi Quan hệ (Non-Relational Databases - NoSQL)

Trong khi relational databases cung cấp các đảm bảo mạnh mẽ và một cách tiếp cận được chuẩn hóa, với sự trỗi dậy của các ứng dụng web quy mô lớn, big data, và nhu cầu xử lý dữ liệu linh hoạt hơn đã dẫn đến sự phát triển và áp dụng các database phi quan hệ, thường được nhóm lại dưới thuật ngữ "NoSQL".

A. Giải quyết các Hạn chế của RDBMS

NoSQL databases xuất hiện chủ yếu để giải quyết các hạn chế được nhận thấy của các RDBMS truyền thống:

RDBMS truyền thốngNoSQL databases
Khả năng mở rộng (Scalability)Các RDBMS truyền thống được thiết kế chủ yếu cho vertical scaling (tăng sức mạnh cho một máy chủ duy nhất bằng cách thêm CPU, RAM, bộ nhớ). Tuy nhiên các máy chủ đều sẽ có giới hạn về vật lý và chi phí.NoSQL thường được thiết kế từ đầu cho horizontal scaling (phân phối dữ liệu và tải trên nhiều máy chủ - commodity servers), mang lại khả năng mở rộng không giới hạn.
Tính linh hoạt (Flexibility)SQL yêu cầu định nghĩa trước cấu trúc dữ liệu chặt chẽ. Việc thay đổi cấu trúc như thêm cột mới có thể rất phức tạp và ảnh hưởng đến toàn bộ hệ thống.NoSQL cho phép lưu trữ dữ liệu với cấu trúc linh hoạt (không yêu cầu định nghĩa trước), thậm chí mỗi bản ghi (document, item) có thể có cấu trúc khác nhau
Hiệu suất/Tính sẵn sàng (Performance/Availability)Để đạt tốc độ cao cho các tác vụ đọc/ghi đơn giản với lượng truy cập khổng lồ, NoSQL thường đơn giản hóa mô hình bằng cách phi chuẩn hóa (denormalization) (lưu dữ liệu liên quan cùng một chỗ, tránh phải "join" như SQL) và được thiết kế cho môi trường phân tán (nhiều máy chủ). Nhiều hệ thống NoSQL chấp nhận việc đạt được tính sẵn sàng (Availability) hơn là tính nhất quán mạnh (Strong Consistency) khi có sự cố mạng (khái niệm BASE).

B. Các Khái niệm Cốt lõi và Lợi ích

NoSQL databases bao gồm một số khái niệm chung và lợi ích tiềm năng:

  • Mô hình Dữ liệu Linh hoạt / Thiết kế Không Schema (Flexible Data Models / Schema-less Design):

    • Đặc điểm lớn nhất là việc bỏ đi schema dạng bảng cứng nhắc của RDBMS.

    • NoSQL databases sử dụng các mô hình dữ liệu khác nhau (key-value, document, column-family, graph) cho phép các cấu trúc động. Tính chất "schema-less" hoặc "schema-flexible" này cho phép các ứng dụng lưu trữ dữ liệu mà không ép buộc nó vào các tables được định nghĩa trước.

    • Ví dụ, một sản phẩm thương mại điện tử có thể có hàng trăm hoặc hàng nghìn thuộc tính, nhiều thuộc tính trong số đó chỉ áp dụng cho các loại sản phẩm cụ thể. Việc biểu diễn điều này một cách hiệu quả trong một schema RDBMS cứng nhắc có thể là một thách thức, trong khi một document database có thể dễ dàng lưu trữ tập hợp các thuộc tính duy nhất của mỗi sản phẩm trong document của riêng nó.

  • Mở rộng Ngang (Horizontal Scaling - Scalability):

    • Mục tiêu thiết kế chính của nhiều hệ thống NoSQL là khả năng mở rộng theo chiều ngang.

    • Bằng cách phân vùng (partitioning - sharding) dữ liệu trên nhiều máy chủ, các hệ thống này có thể xử lý các tập dữ liệu khổng lồ và lưu lượng tải truy cập cao mà sẽ làm quá tải một máy chủ RDBMS duy nhất. Việc thêm dung lượng thường liên quan đến việc thêm nhiều máy chủ hơn vào cluster.

  • Truy vấn Nhanh (Fast Queries - Hiệu suất cho các Mẫu Cụ thể):

    • NoSQL databases thường đạt được hiệu suất cao cho các mẫu truy vấn nhất định bằng cách tối ưu hóa tính cục bộ của dữ liệu. Điều này thường liên quan đến việc phi chuẩn hóa (denormalization), nơi dữ liệu liên quan được lưu trữ cùng nhau trong một bản ghi duy nhất (ví dụ: nhúng chi tiết đơn hàng vào trong một document đơn hàng) thay vì được chia nhỏ trên nhiều tables đã chuẩn hóa. Điều này tránh được nhu cầu về các phép toán JOIN tốn kém trong quá trình đọc, dẫn đến việc truy xuất dữ liệu nhanh hơn cho các mẫu truy cập phổ biến.

    • Tuy nhiên, cách tiếp cận này đại diện cho một sự đánh đổi đáng kể. Trong khi relational databases cố gắng loại bỏ sự dư thừa thông qua normalization để đạt hiệu quả lưu trữ và tính nhất quán, NoSQL databases thường chấp nhận sự dư thừa thông qua denormalization để tối ưu hóa hiệu suất đọc. Hậu quả là các cập nhật có thể yêu cầu sửa đổi dữ liệu ở nhiều nơi, làm tăng độ phức tạp ghi và tạo ra khả năng không nhất quán tạm thời nếu việc cập nhật không được quản lý cẩn thận trên tất cả các bản sao dư thừa

  • Tính Sẵn sàng Cao (High Availability):

    • Nhiều NoSQL databases được phân tán và tích hợp các cơ chế sao chép (replication), làm cho chúng có khả năng phục hồi tốt hơn đối với các lỗi máy chủ riêng lẻ. Dữ liệu thường được sao chép trên nhiều node, đảm bảo rằng hệ thống có thể tiếp tục hoạt động ngay cả khi một số node không khả dụng.

C. Các Loại Chính của NoSQL Databases

Thuật ngữ "NoSQL" bao gồm một số loại database riêng biệt được tối ưu hóa cho các cấu trúc dữ liệu và trường hợp sử dụng khác nhau như:

  1. Kho Lưu trữ Khóa-Giá trị (Key-Value Stores):
  • Mô hình: Đây là loại đơn giản nhất, giống như một cuốn từ điển hoặc tủ giữ đồ ở siêu thị. Bạn có một cái khóa (key) duy nhất (như số thẻ giữ đồ) và nó trỏ đến một giá trị (value) (cái túi đồ của bạn). Value có thể là bất cứ thứ gì: văn bản, số, ảnh, JSON.

  • Ưu điểm:

    • Siêu nhanh cho các thao tác đơn giản như lấy giá trị, lưu giá trị và xóa giá trị theo khóa.

    • Dễ mở rộng, phù hợp cho dữ liệu đơn giản.

  • Nhược điểm:

    • Chỉ truy vấn được bằng khóa. Khó truy vấn dựa trên nội dung của value hoặc mối quan hệ giữa các khóa. Bạn không thể dễ dàng hỏi "Hãy tìm tất cả những cái túi đồ màu đỏ" mà chỉ có thể hỏi "Cái túi ứng với số thẻ 123 là gì?".

    • Không phù hợp cho dữ liệu phức tạp cần mối quan hệ.

  • Trường hợp Sử dụng: Caching (lưu trữ dữ liệu được truy cập thường xuyên trong bộ nhớ), quản lý session đăng nhập web, hệ thống đấu giá thời gian thực, bảng xếp hạng…

  • Ví dụ: Redis, Memcached, Amazon DynamoDB (cũng phù hợp với mô hình document).

  1. Cơ sở dữ liệu Tài liệu (Document Databases):
  • Mô hình:

    • Lưu dữ liệu dưới dạng các tài liệu (documents), thường là định dạng JSON hoặc BSON.

    • Các document được nhóm vào các bộ sưu tập (collections) (tương tự table trong SQL) nhưng điểm đặc biệt là việc các document trong cùng collection không nhất thiết phải có cấu trúc giống hệt nhau.

        {
          "Mã sách": "001",
          "Tên": "Harry Potter",
          "Giá": 20,
          "Thông tin thêm": {
            "Tác giả": "J.K. Rowling",
            "Đánh giá": 4.5
          }
        }
      
  • Ưu điểm:

    • Linh hoạt về cấu trúc (schema).

    • Dễ dàng ánh xạ với các đối tượng trong lập trình.

    • Cho phép truy vấn dựa trên nội dung bên trong document (ví dụ: tìm tất cả sản phẩm có giá > 1 triệu và màu "Đen").

  • Nhược điểm:

    • Các cấu trúc lồng nhau quá phức tạp có thể trở nên khó quản lý và truy vấn hiệu quả.

    • Có thể kém hiệu quả cho các truy vấn yêu cầu mối quan hệ phức tạp giữa nhiều documents.

  • Trường hợp Sử dụng: Quản lý nội dung (blog, bài báo), danh mục sản phẩm E-commerce, hồ sơ người dùng, dữ liệu ứng dụng di động, logging.

  • Ví dụ: MongoDB, Couchbase, CouchDB, Amazon DynamoDB.

  1. Kho Lưu trữ Cột Rộng (Wide-Column Stores):
  • Mô hình:

    • Dữ liệu được theo bảng, cột, dòng, nhưng khác SQL, mỗi hàng có thể có các cột khác nhau, và cột được nhóm thành "họ cột" (column families).

    • Rất tốt cho dữ liệu thưa (sparse) - tức là nhiều ô có thể bị trống.

    • Ví dụ việc lưu thông tin cảm biến IoT:

      • Row 1: {Cảm biến_001, Nhiệt độ: 25, Độ ẩm: 60}

      • Row 2: {Cảm biến_002, Nhiệt độ: 30}

  • Ưu điểm:

    • Khả năng mở rộng cực tốt cho dữ liệu lớn, đặc biệt cho việc ghi dữ liệu rất nhiều

    • Hiệu quả khi truy vấn một số cột cụ thể.

  • Nhược điểm:

    • Mô hình hóa dữ liệu phức tạp hơn.

    • Ngôn ngữ truy vấn có thể không mạnh mẽ bằng SQL cho các phép JOIN tùy ý.

  • Trường hợp Sử dụng:

    • Dữ liệu chuỗi thời gian: Lưu nhiệt độ, độ ẩm từ cảm biến IoT.

    • Phân tích big data: Xử lý dữ liệu lớn từ mạng xã hội hoặc log hệ thống.

  • Ví dụ: Apache Cassandra, Google Cloud Bigtable, Apache HBase.

  1. Cơ sở dữ liệu Đồ thị (Graph Databases):
  • Mô hình: Dữ liệu gồm các nút (nodes) (đại diện cho thực thể, ví dụ: người, sản phẩm) và các cạnh (edges) (đại diện cho mối quan hệ giữa các nút, ví dụ: "Bạn bè với", "Đã mua”. Cả nút và cạnh đều có thể có thuộc tính. Phù hợp cho dữ liệu có nhiều kết nối và có các mối quan hệ phức tạp.

  • Ưu điểm:

    • Rất hiệu quả cho việc duyệt qua các mối quan hệ phức tạp (ví dụ: tìm bạn của bạn bè, đường đi ngắn nhất).

    • Mô hình hóa rất tự nhiên cho dữ liệu có tính kết nối cao.

  • Nhược điểm:

    • Có thể không hiệu quả bằng các mô hình khác đối với các truy vấn yêu cầu quét hoặc tổng hợp các thuộc tính trên số lượng lớn nodes hoặc edges mà không cần duyệt qua các mối quan hệ.

    • Việc mở rộng có thể đặt ra những thách thức, đặc biệt đối với các truy vấn liên quan đến việc duyệt phức tạp trên dữ liệu phân tán.

    • Có thể là quá mức cần thiết đối với các tập dữ liệu không có độ phức tạp về mối quan hệ đáng kể.

  • Trường hợp Sử dụng: Mạng xã hội (quản lý kết nối), công cụ đề xuất (đề xuất các mục dựa trên hành vi người dùng liên quan), phát hiện gian lận (xác định các mẫu kết nối đáng ngờ), ánh xạ cơ sở hạ tầng mạng và CNTT, đồ thị tri thức, quản lý danh tính và truy cập.

  • Ví dụ: Neo4j, Amazon Neptune, ArangoDB (multi-model), OrientDB (multi-model).

Sự đa dạng trong NoSQL nhấn mạnh rằng nó không phải là một công nghệ duy nhất mà là một tập hợp các cách tiếp cận được thiết kế để giải quyết các vấn đề mà mô hình quan hệ truyền thống có thể kém phù hợp hơn. Sự lựa chọn đòi hỏi phải hiểu cấu trúc dữ liệu cụ thể và các mẫu truy cập của ứng dụng.

D. Các Hạn chế Tiềm ẩn

Mặc dù có những lợi thế, NoSQL cũng đi kèm với những hạn chế tiềm ẩn:

  • Mô hình Nhất quán (Consistency Models):

    • NoSQL thường nới lỏng các yêu cầu nhất quán mạnh ACID của SQL để ưu tiên high availabilitypartition tolerance.

    • Chúng thường cung cấp "eventual consistency", nghĩa là, nếu bạn ghi dữ liệu mới, không phải tất cả các bản sao trên các máy chủ sẽ được cập nhật ngay lập tức. Sẽ có một độ trễ nhỏ, nhưng cuối cùng thì tất cả các bản sao sẽ trở nên nhất quán.

    • Ví dụ: Bạn đổi ảnh đại diện Facebook. Có thể trong vài giây đầu, một số bạn bè vẫn thấy ảnh cũ, một số thấy ảnh mới. Nhưng sau một lúc, mọi người sẽ thấy ảnh mới.

    • Eventual consistency có thể được chấp nhận được với nhiều ứng dụng như mạng xã hội, nhưng không phù hợp với những nơi yêu cầu tính nhất quán tức thì như hệ thống ngân hàng.

    • Việc lập trình với eventual consistency cũng phức tạp hơn.

    • Triết lý BASE: Thường được dùng để mô tả các hệ thống này: Basically Available (Cơ bản là Sẵn sàng), Soft state (Trạng thái mềm - có thể thay đổi theo thời gian), Eventually consistent (Nhất quán Cuối cùng). Đây là sự đánh đổi so với ACID của SQL.

    • Lưu ý: Một số hệ thống NoSQL hiện đại cũng cung cấp tùy chọn để có mức độ nhất quán mạnh hơn, thậm chí tuân thủ ACID.

  • Tiêu chuẩn hóa (Standardization):

    • NoSQL không có một ngôn ngữ truy vấn chung như SQL. Mỗi loại NoSQL (thậm chí mỗi sản phẩm) có thể có API và ngôn ngữ truy vấn riêng (ví dụ: MongoDB dùng MQL, Cassandra dùng CQL...). Điều này gây khó khăn khi chuyển đổi giữa các hệ thống NoSQL khác nhau (vendor lock-in), yêu cầu lập trình viên phải học nhiều giao diện khác nhau.
  • Độ phức tạp Giao dịch (Transaction Complexity):

    • NoSQL không mạnh trong các giao dịch (transaction) phức tạp (như chuyển tiền ngân hàng, cần cập nhật nhiều bảng cùng lúc), đòi hỏi xử lý nhiều logic ở phía ứng dụng so với SQL.
  • Độ trưởng thành (Maturity):

    • Các cơ sở dữ liệu SQL đã tồn tại hàng thập kỷ, có nhiều công cụ hỗ trợ, tài liệu và chuyên gia, trong khi đó, các cơ sở dữ liệu NoSQL mới hơn, nên một số hệ thống (như graph databases) có thể thiếu tài liệu hoặc cộng đồng hỗ trợ.

IV. SQL vs. NoSQL: Phân tích So sánh

A. Xem xét lại các Điểm Khác biệt Cốt lõi

Dựa trên các thảo luận trước đó, các điểm khác biệt cốt lõi giữa SQL và NoSQL điển hình có thể được tóm tắt như sau:

SQLNoSQL
Mô hình Dữ liệuDữ liệu được lưu trong bảng (tables) với hàng (rows) và cột (columns) được định nghĩa rõ ràng. Mỗi bảng đại diện cho một loại thực thể (như "Khách hàng", "Sản phẩm").Dữ liệu được lưu theo nhiều cách khác nhau, tùy thuộc vào loại NoSQL như Key-Value, Document, Wide-Column, Graph
SchemaYêu cầu schema cố định với định nghĩa về các cột của bảng, và mọi hàng phải tuân theo cấu trúc đó.Schema linh hoạt hoặc không cần schema. Bạn có thể thêm dữ liệu mới mà không cần định nghĩa trước.
Khả năng mở rộng (Scalability)Chủ yếu mở rộng dọc (vertical scaling)Mở rộng ngang (horizontal scaling)
Tính nhất quán (Consistency)Tuân thủ ACID (Atomicity, Consistency, Isolation, Durability), đảm bảo dữ liệu luôn nhất quán ngay lập tức.Thường dùng BASE (Basically Available, Soft state, Eventual consistency), ưu tiên tính sẵn sàng và có thể chấp nhận độ trễ nhỏ trong đồng bộ dữ liệu không (Eventual Consistency)
Ngôn ngữ Truy vấnDùng ngôn ngữ chuẩn hóa là SQL.Mỗi loại, mỗi sản phẩm có thể có ngôn ngữ hoặc cách truy vấn riêng
Loại dữ liệuTốt cho dữ liệu có cấu trúc (structured), có mối quan hệ chặt chẽ như bảng lương, tài khoản ngân hàng.Phù hợp với dữ liệu không có cấu trúc (unstructured), bán cấu trúc (semi-structured), hoặc dữ liệu rất đa dạng.

Ngày nay, ranh giới giữa SQL và NoSQL không còn rõ ràng như trước:

  • SQL đang học hỏi NoSQL: Nhiều hệ quản trị CSDL SQL hiện đại (như PostgreSQL, SQL Server) đã bổ sung các tính năng giống NoSQL, ví dụ như hỗ trợ lưu trữ và truy vấn dữ liệu dạng JSON trực tiếp trong bảng, cải thiện khả năng nhân bản (replication) và phân mảnh (partitioning) dữ liệu.

  • NoSQL đang học hỏi SQL: Ngược lại, một số CSDL NoSQL đang cố gắng cung cấp các tính năng mạnh mẽ của SQL như giao dịch đảm bảo ACID, hoặc cung cấp các giao diện truy vấn gần giống SQL để dễ tiếp cận hơn.

  • Sự xuất hiện của "NewSQL" / "Distributed SQL": Có một thế hệ CSDL mới (ví dụ: CockroachDB, YugabyteDB, Google Spanner) ra đời với mục tiêu kết hợp những gì tốt nhất của cả hai thế giới: khả năng mở rộng ngang và tính sẵn sàng cao của NoSQL cùng với sự nhất quán mạnh mẽ và đảm bảo giao dịch (ACID) của SQL truyền thống

Bảng 1: So sánh SQL vs. NoSQL ở Mức độ Cao

Tính năngSQL (Quan hệ)NoSQL (Phi quan hệ - Xu hướng chung)
Data ModelTables (Rows, Columns)Document, Key-Value, Wide-Column, Graph
SchemaPredefined, RigidDynamic, Flexible
Primary ScalabilityVertical (Scale-Up)Horizontal (Scale-Out)
Consistency ModelStrong (ACID)Tunable (Often Eventual/BASE, some offer ACID)
Data Type FocusStructuredUnstructured, Semi-structured, Diverse
Query LanguageStandardized SQLVaried, Database-Specific APIs/Languages
Transaction SupportStrong, Multi-Statement ACIDVaries (Often single-record atomic, some full ACID)
Key StrengthData Integrity, Complex Queries, StandardizationFlexibility, Scalability, Performance (specific use cases)

B. Khi nào Chọn Loại nào

Quyết định phụ thuộc vào các yêu cầu cụ thể của ứng dụng:

Chọn SQL (Quan hệ) khi:

  1. Cần sự An toàn Tuyệt đối (Tuân thủ ACID):
  • Ví dụ: Hệ thống ngân hàng (chuyển tiền phải chính xác tuyệt đối), quản lý kho hàng (số lượng tồn kho phải đúng), các ứng dụng yêu cầu tuân thủ quy định nghiêm ngặt về dữ liệu.

  • Tại sao? SQL với ACID đảm bảo mọi giao dịch diễn ra một cách toàn vẹn (hoặc thành công hết, hoặc thất bại hết, không có trạng thái lưng chừng), dữ liệu luôn nhất quán và chính xác.

  1. Cấu trúc Dữ liệu Ổn định và Rõ ràng:
  • Ví dụ: Thông tin nhân viên trong công ty, danh mục sản phẩm với các thuộc tính cố định, dữ liệu kế toán.

  • Tại sao? Cấu trúc bảng, cột của SQL rất phù hợp để mô hình hóa những dữ liệu này. Bạn biết rõ mình cần lưu gì và mối quan hệ giữa chúng không thay đổi đột ngột.

  1. Thực hiện truy vấn Phức tạp:
  • Ví dụ: Phân tích doanh thu theo nhiều tiêu chí (kết hợp dữ liệu từ bảng khách hàng, đơn hàng, sản phẩm), tạo báo cáo tổng hợp phức tạp.

  • Tại sao? Ngôn ngữ SQL cực kỳ mạnh mẽ cho việc truy vấn, kết hợp (JOIN), lọc, và tổng hợp dữ liệu từ nhiều bảng khác nhau.

Chọn NoSQL khi:

  1. Xử lý Dữ liệu Khổng lồ và Đa dạng:

    • Ví dụ: Lưu trữ hàng tỷ bài đăng trên mạng xã hội, dữ liệu từ cảm biến IoT (nhiệt độ, độ ẩm...), log hệ thống, file ảnh, video.

    • Tại sao? NoSQL thường xử lý tốt hơn với dữ liệu không có cấu trúc rõ ràng hoặc cấu trúc thay đổi liên tục, và có khả năng lưu trữ lượng dữ liệu cực lớn.

  2. Ưu tiên Tốc độ Cực Nhanh (Độ trễ thấp) và Lưu lượng Cao:

    • Ví dụ: Các ứng dụng cần phản hồi tức thì như quảng cáo thời gian thực, game online, cache dữ liệu cho website đông người truy cập.

    • Tại sao? Các mô hình dữ liệu đơn giản hơn (như Key-Value) và kiến trúc phân tán của NoSQL thường cho phép tốc độ đọc/ghi nhanh hơn cho các tác vụ đơn giản.

  3. Cần Schema Linh hoạt, Hay Thay đổi:

    • Ví dụ: Danh mục sản phẩm thương mại điện tử nơi mỗi loại sản phẩm có thể có bộ thông số kỹ thuật riêng, các dự án phát triển nhanh cần thay đổi cấu trúc dữ liệu liên tục mà không muốn gián đoạn.

    • Tại sao? Sự linh hoạt của NoSQL giúp bạn dễ dàng thêm/bớt thuộc tính mà không cần định nghĩa lại toàn bộ cấu trúc, tăng tốc độ phát triển.

  4. Khả năng Mở rộng Ngang là Yếu tố Sống còn:

    • Ví dụ: Các ứng dụng dự kiến có lượng người dùng hoặc dữ liệu tăng trưởng theo cấp số nhân (mạng xã hội, nền tảng SaaS...).

    • Tại sao? NoSQL được thiết kế để dễ dàng "thêm máy chủ" khi cần, giúp hệ thống đáp ứng được tải ngày càng tăng.

C. Cân nhắc về Hiệu suất: B-Trees vs. LSM-Trees

Cấu trúc dữ liệu cơ bản được sử dụng bởi storage engine của database ảnh hưởng đáng kể đến đặc điểm hiệu suất đọc và ghi của nó. Hai cấu trúc phổ biến là B-Trees (và các biến thể như B+ Trees) và Log-Structured Merge Trees (LSM-Trees).

  1. B-Trees (Phổ biến trong RDBMS):
  • Cấu trúc: B-Trees là cấu trúc cây cân bằng được tối ưu hóa cho việc tìm kiếm. Dữ liệu thường được lưu trữ theo thứ tự sắp xếp trong các trang lá (leaf pages), cho phép tra cứu điểm (point lookups) và quét phạm vi (range scans) hiệu quả. Chúng là cấu trúc index tiêu chuẩn trong các database như PostgreSQL, MySQL (InnoDB), và Oracle.

    Tưởng tượng một thư viện được sắp xếp cực kỳ khoa học bằng hệ thống thẻ mục lục (index) theo cây phân cấp (B-Tree). Mọi cuốn sách (dữ liệu) đều được đặt đúng vị trí theo thứ tự. Để tìm sách, bạn chỉ cần tra thẻ mục lục và đi thẳng đến kệ chứa nó.

  • Hiệu suất Đọc: Rất nhanh, đặc biệt nếu dữ liệu đã được tải vào bộ nhớ (cache) hoặc khi cần quét phạm vi (range scan) (như tìm tất cả sách có Mã sách từ 100 đến 200).

  • Hiệu suất Ghi: Chậm hơn khi ghi hoặc cập nhật, vì phải thay đổi dữ liệu trên đĩa. Quá trình điển hình là đọc-sửa-ghi (read-modify-write): tìm nạp trang liên quan từ đĩa vào bộ nhớ, sửa đổi nó, và ghi lại toàn bộ trang. Nếu trang đầy, B-Trees phải "chia trang" (page split), làm tăng chi phí. Bên cạnh đó, việc này có thể dẫn đến khuếch đại ghi (write amplification) khi một thay đổi nhỏ (như sửa giá sách) có thể yêu cầu ghi lại toàn bộ trang (thường là 8KB), dù chỉ thay đổi vài byte.

  1. LSM-Trees (Phổ biến trong NoSQL được Tối ưu hóa Ghi):
  • Cấu trúc: LSM-Trees được thiết kế cho thông lượng ghi cao. Các ghi đến đầu tiên được đệm trong một cấu trúc trong bộ nhớ (thường được gọi là memtable, điển hình là một cấu trúc được sắp xếp như skip list hoặc cây). Khi memtable đầy, nó được đẩy (flushed) xuống đĩa dưới dạng một tệp phân đoạn (segment) bất biến, được sắp xếp (SSTable). Việc đọc có thể phải kiểm tra memtable và nhiều SSTables trên đĩa. Một quy trình nén (compaction) chạy nền định kỳ hợp nhất các SSTables để loại bỏ dữ liệu đã xóa/cập nhật và hợp nhất các tệp, cải thiện hiệu quả đọc.

    Tưởng tượng một bàn lễ tân rất bận. Thư mới (dữ liệu ghi mới) đến được bỏ tạm vào một cái khay "Thư mới đến" rất nhanh (memtable trong bộ nhớ). Khi khay đầy, nhân viên sẽ gom cả lô thư đó, sắp xếp nhanh, đóng vào một thùng dán nhãn ngày tháng (SSTable) và cất vào kho lưu trữ trên đĩa. Họ không cố gắng nhét từng lá thư vào các thùng cũ. Định kỳ, họ sẽ vào kho, gom các thùng cũ lại, loại bỏ thư rác/thư trùng, sắp xếp lại cho gọn (compaction).

  • Hiệu suất Đọc: Việc đọc có thể chậm hơn B-Trees vì dữ liệu có thể tồn tại trong memtable hoặc nhiều SSTables. Databases thường sử dụng bộ lọc Bloom (Bloom filters)—cấu trúc dữ liệu xác suất—để nhanh chóng xác định xem một SSTable có thể chứa khóa hay không, tránh các lần đọc đĩa không cần thiết. Tuy nhiên, một lần đọc logic duy nhất vẫn có thể yêu cầu nhiều lần tìm kiếm/đọc đĩa trong trường hợp xấu nhất, dẫn đến khuếch đại đọc (read amplification) cao hơn.

    Nhiều CSDL NoSQL tối ưu cho việc ghi nhiều như Cassandra, HBase, LevelDB, RocksDB (thường dùng trong các hệ thống ghi log, dữ liệu chuỗi thời gian...).

  • Hiệu suất Ghi: LSM-Trees cung cấp hiệu suất ghi cao, vì các thao tác ghi chủ yếu là thêm (append) vào memtable trong bộ nhớ, sau đó là đẩy (flush) tuần tự các SSTables xuống đĩa. Điều này tránh được I/O ngẫu nhiên và chi phí đọc-sửa-ghi của B-Trees. Khuếch đại ghi (write amplification) vẫn xảy ra, nhưng nó xảy ra trong quá trình compaction nền thay vì trên đường dẫn ghi tiền cảnh. Tùy thuộc vào chiến lược compaction (ví dụ: leveled vs. tiered), tổng write amplification thường có thể thấp hơn so với B-Trees dưới tải ghi nặng. LSM-Trees cũng có thể hấp thụ tốt hơn các đợt ghi bùng nổ tạm thời bằng cách đệm trong bộ nhớ.

Sự lựa chọn giữa các storage engines dựa trên B-TreeLSM-Tree phản ánh một sự đánh đổi hiệu suất cơ bản. B-Trees thường cung cấp hiệu suất đọc tốt hơn và dễ dự đoán hơn, đặc biệt là đối với range scans, làm cho chúng phù hợp với khối lượng công việc RDBMS truyền thống thường thiên về đọc hoặc cân bằng. LSM-Trees xuất sắc về thông lượng ghi, làm cho chúng trở thành lựa chọn phổ biến cho các ứng dụng ghi nhiều thường thấy trong không gian NoSQL, chẳng hạn như hệ thống logging, time-series databases, hoặc các hệ thống như Apache Cassandra. Sự khác biệt này trong chiến lược lưu trữ cơ bản là một lý do kỹ thuật chính đằng sau các hồ sơ hiệu suất khác nhau thường được quan sát giữa SQL và một số NoSQL databases nhất định.

V. Mở rộng Quy mô Databases cho Tăng trưởng

Khi các ứng dụng thành công, chúng chắc chắn phải đối mặt với khối lượng dữ liệu và lưu lượng truy cập người dùng ngày càng tăng. Việc mở rộng quy mô lớp database để xử lý sự tăng trưởng này là một thách thức quan trọng, liên quan đến các kỹ thuật như replicationpartitioning/sharding.

A. Những Thách thức của việc Mở rộng SQL theo Chiều ngang: Việc mở rộng quy mô SQL databases truyền thống theo chiều ngang (trên nhiều máy chủ) có nhiều khó khăn:

  • Duy trì ACID trên các Nodes: Đảm bảo các thuộc tính ACID, đặc biệt là AtomicityConsistency, trên các transactions kéo dài trên nhiều máy chủ database độc lập (shards) vốn đã phức tạp. Các hoạt động như chuyển khoản ngân hàng liên quan đến các tài khoản trên các shards khác nhau yêu cầu các giao thức transaction phân tán (ví dụ: Two-Phase Commit - 2PC). Các giao thức này gây ra độ trễ và chi phí giao tiếp đáng kể, và chúng dễ bị lỗi – nếu một node tham gia vào transaction bị crash, toàn bộ transaction có thể bị chặn hoặc yêu cầu các quy trình phục hồi phức tạp. Việc đạt được latency thấp và high availability trong khi đảm bảo durabilityatomicity trong các transactions phân tán là một trở ngại lớn.

  • Thực thi JOINs Phân tán: Thực hiện các phép toán JOIN trên dữ liệu được phân phối trên các shards khác nhau tốn kém về mặt tính toán và đòi hỏi nhiều tài nguyên mạng. Nó yêu cầu tìm nạp dữ liệu từ nhiều máy chủ, chuyển một lượng lớn dữ liệu qua mạng, và sau đó thực hiện logic join, ảnh hưởng đáng kể đến hiệu suất truy vấn.

  • Độ phức tạp Vận hành: Việc quản lý một cluster các SQL databases được replicated và/hoặc sharded liên quan đến chi phí vận hành đáng kể cho việc cấu hình, giám sát, sao lưu, nâng cấp và thay đổi schema so với việc quản lý một instance duy nhất.

Những thách thức này giải thích tại sao các SQL databases truyền thống thường phụ thuộc nhiều vào vertical scaling và tại sao việc đạt được horizontal scaling thường yêu cầu các distributed SQL databases chuyên biệt hoặc thiết kế cẩn thận ở cấp ứng dụng.

B. Sao chép (Replication): Tăng cường Tính Sẵn sàng và Khả năng Mở rộng Đọc Replication liên quan đến việc tạo và duy trì nhiều bản sao (replicas) của một database trên các máy chủ khác nhau. Mục tiêu chính của nó là cải thiện availability dữ liệu bằng cách loại bỏ các điểm lỗi duy nhất (Single Points of Failure - SPOF) và tăng cường hiệu suất đọc bằng cách phân phối các truy vấn đọc trên nhiều replicas. Nếu một máy chủ database bị lỗi, một replica có thể tiếp quản hoặc tiếp tục phục vụ các yêu cầu. Lưu lượng đọc cao có thể được cân bằng trên nhiều replicas.

Hãy nghĩ về nó giống như có nhiều giao dịch viên ngân hàng mở thay vì chỉ một; khối lượng công việc được phân phối, và dịch vụ vẫn tiếp tục ngay cả khi một giao dịch viên nghỉ giải lao. Hoặc, xem xét physical replication như sao chép toàn bộ ổ cứng so với logical replication như sao chép các tệp cụ thể – một đảm bảo khớp từng byte, trong khi cái kia tập trung vào các thay đổi logic.

Một số mô hình replication tồn tại, mỗi mô hình có những đánh đổi khác nhau về consistency, availability, và độ phức tạp:

  • Sao chép Đơn Lãnh đạo (Single-Leader - Master-Slave):

    • Cơ chế: Một máy chủ được chỉ định, leader (hoặc master), xử lý tất cả các hoạt động ghi (writes). Các writes này sau đó được lan truyền (replicated) đến một hoặc nhiều máy chủ follower (hoặc slave). Các truy vấn đọc có thể được chuyển đến master hoặc bất kỳ slave nào.

    • Ưu điểm: Mô hình consistency tương đối đơn giản, vì tất cả các writes đều đi qua một điểm sắp xếp duy nhất. Tuyệt vời để mở rộng quy mô khối lượng công việc thiên về đọc bằng cách thêm nhiều replicas hơn.

    • Nhược điểm: Master đại diện cho một SPOF tiềm năng; nếu nó bị lỗi, cần có quy trình chuyển đổi dự phòng (failover) để nâng cấp một follower trở thành master mới. Thông lượng ghi bị giới hạn bởi dung lượng của master duy nhất. Có thể có độ trễ sao chép (replication lag), nơi các followers hơi chậm hơn master, có nghĩa là các lần đọc từ followers có thể trả về dữ liệu cũ (eventual consistency cho các lần đọc từ followers).

  • Sao chép Đa Lãnh đạo (Multi-Leader - Master-Master):

    • Cơ chế: Nhiều máy chủ được chỉ định làm leaders, và mỗi máy chủ có thể chấp nhận các hoạt động ghi. Các writes được chấp nhận bởi một leader được replicated đến các leaders khác và bất kỳ followers liên quan nào.

    • Ưu điểm: Cải thiện availability ghi – nếu một leader bị lỗi, những leader khác vẫn có thể chấp nhận writes. Có thể giảm latency ghi cho các ứng dụng phân tán về mặt địa lý, vì người dùng có thể ghi vào một leader gần đó [Ghi chú của người dùng].

    • Nhược điểm: Thách thức chính là giải quyết xung đột ghi (write conflict resolution). Nếu cùng một dữ liệu được sửa đổi đồng thời trên các leaders khác nhau, hệ thống cần một chiến lược để xác định write nào "thắng" (ví dụ: dựa trên dấu thời gian, quy tắc cụ thể, hoặc can thiệp thủ công). Điều này làm phức tạp đáng kể các đảm bảo consistency.

  • Sao chép Không Lãnh đạo (Leaderless - ví dụ: kiểu Dynamo):

    • Cơ chế: Không có leaders được chỉ định; bất kỳ node replica nào cũng có thể chấp nhận yêu cầu ghi. Khi một client ghi dữ liệu, nó thường gửi write đến nhiều replicas đồng thời. Việc đọc cũng có thể truy vấn nhiều replicas để tìm phiên bản gần đây nhất hoặc đảm bảo consistency. Thường sử dụng quorums cho đọc (R) và ghi (W): một write được coi là thành công nếu được xác nhận bởi W nodes, và một lần đọc là thành công nếu R nodes phản hồi. Cấu hình W + R > N (tổng số replicas) thường cung cấp đảm bảo strong consistency, trong khi các giá trị thấp hơn ưu tiên availability.

    • Ưu điểm: High availability cho cả đọc và ghi, vì lỗi của các nodes riêng lẻ không ngăn hệ thống xử lý yêu cầu (miễn là còn đủ replicas). Khả năng chịu lỗi tuyệt vời.

    • Nhược điểm: Các đảm bảo consistency có thể phức tạp để quản lý và lý giải, đặc biệt là với các writes đồng thời dẫn đến các phiên bản dữ liệu có khả năng xung đột. Các cơ chế như sửa chữa khi đọc (read repair - sửa lỗi không nhất quán trong quá trình đọc) hoặc logic giải quyết xung đột rõ ràng có thể cần thiết. Cassandra, ví dụ, sử dụng quorums có thể điều chỉnh để cân bằng consistencyavailability [Ghi chú của người dùng].

  • Sao chép Logic vs. Vật lý (Logical vs. Physical Replication): Replication có thể xảy ra ở các cấp độ khác nhau. Physical replication sao chép các thay đổi cấp thấp vào các tệp database (ví dụ: truyền các bản ghi Write-Ahead Log). Nó thường đơn giản hơn và đảm bảo một bản sao chính xác nhưng kém linh hoạt hơn. Logical replication sao chép các hoạt động logic (ví dụ: câu lệnh SQL hoặc các thay đổi cấp row). Nó linh hoạt hơn, cho phép replication giữa các phiên bản database hoặc nền tảng khác nhau, replication chọn lọc các tables/databases, nhưng có thể có chi phí cao hơn.

Sự lựa chọn mô hình replication ảnh hưởng cơ bản đến hành vi của hệ thống. Các mô hình single-leader cung cấp quản lý consistency đơn giản hơn nhưng giới hạn availability ghi. Các mô hình multi-leaderleaderless ưu tiên availability, đặc biệt là cho ghi, nhưng giới thiệu sự phức tạp đáng kể xung quanh việc quản lý các cập nhật đồng thời và đảm bảo consistency dữ liệu trên các replicas. Điều này phản ánh sự đánh đổi cố hữu giữa consistencyavailability trong các hệ thống phân tán.

Bảng 2: So sánh các Mô hình Sao chép (Replication)

Tính năngSingle-Leader (Master-Slave)Multi-Leader (Master-Master)Leaderless (e.g., Quorum-based)
Write PathTo Master onlyTo any MasterTo W replicas
Read PathMaster or SlavesAny Master or SlavesFrom R replicas
Typical ConsistencyStrong (on Master), Eventual (on Slaves)Eventual (complex conflict resolution)Tunable (via W, R, N)
Conflict HandlingNot applicable (single writer)High complexityModerate to High complexity
Write AvailabilityLower (Master SPOF)HigherHighest
Read ScalabilityHigh (add slaves)HighHigh
Overall ComplexityLowerHigherHigh

Export to Sheets

(Xuất ra Sheets)

C. Phân vùng và Sharding (Partitioning and Sharding): Mở rộng Quy mô Ghi và Kích thước Tập dữ liệu Khi một tập dữ liệu trở nên quá lớn để vừa trên một máy chủ duy nhất, hoặc khi thông lượng ghi vượt quá dung lượng của một máy duy nhất (ngay cả với replication cho đọc), partitioning hoặc sharding trở nên cần thiết. Các kỹ thuật này liên quan đến việc chia một database lớn thành các phần nhỏ hơn, dễ quản lý hơn. Mặc dù đôi khi được sử dụng thay thế cho nhau, partitioning có thể đề cập đến việc chia dữ liệu trong một máy chủ duy nhất, trong khi sharding thường ám chỉ việc phân phối các phân vùng này trên nhiều máy chủ độc lập. Trọng tâm ở đây là sharding để mở rộng theo chiều ngang.

Hãy tưởng tượng một thư viện khổng lồ; sharding giống như chia bộ sưu tập thành các khu vực (ví dụ: Tiểu thuyết, Phi hư cấu, Khoa học) được đặt trong các phòng khác nhau hoặc thậm chí các tòa nhà khác nhau, làm cho mỗi khu vực dễ quản lý và tìm kiếm hơn. Hoặc, tại một bữa tiệc buffet lớn, sharding giống như thiết lập các quầy riêng biệt cho món khai vị, món chính và món tráng miệng.

Có hai cách tiếp cận chính để chia dữ liệu [Ghi chú của người dùng]:

  • Phân vùng/Sharding Dọc (Vertical Partitioning/Sharding): Chia một table dựa trên các columns của nó. Các columns thường được truy cập cùng nhau có thể được giữ trong một phân vùng, trong khi các columns lớn hoặc ít được sử dụng (như BLOBs hoặc trường văn bản) được chuyển sang phân vùng khác.

    • Ưu điểm: Có thể cải thiện hiệu suất truy vấn bằng cách giảm lượng dữ liệu đọc từ đĩa nếu truy vấn chỉ cần các columns từ một phân vùng.

    • Nhược điểm: Không trực tiếp giải quyết việc mở rộng số lượng rows. Nếu cần dữ liệu từ nhiều phân vùng dọc, cần có các phép JOIN tốn kém. Nó cũng có thể dẫn đến tải không đồng đều nếu một số phân vùng được truy cập thường xuyên hơn nhiều [Ghi chú của người dùng].

  • Phân vùng/Sharding Ngang (Horizontal Partitioning/Sharding): Chia một table dựa trên các rows của nó. Table được chia thành nhiều tables nhỏ hơn (shards), mỗi shard chứa một tập hợp con các rows nhưng duy trì cùng một schema. Các shards này sau đó được phân phối trên các máy chủ database khác nhau. Đây là kỹ thuật chính để mở rộng quy mô khối lượng dữ liệu và thông lượng ghi.

Một số chiến lược tồn tại để xác định cách phân phối rows trên các shards trong horizontal sharding:

  • Sharding Dựa trên Phạm vi (Range-Based Sharding): Dữ liệu được phân vùng dựa trên một phạm vi giá trị liên tục trong một column cụ thể, được gọi là khóa shard (shard key) (ví dụ: User IDs 1-1000 trên Shard A, 1001-2000 trên Shard B, Zip Codes 00000-49999 trên Shard C, 50000-99999 trên Shard D).

    • Ưu điểm: Tương đối đơn giản để triển khai và hiểu. Hiệu quả cho các truy vấn yêu cầu dữ liệu trong một phạm vi cụ thể của shard key.

    • Nhược điểm: Dễ bị hotspots – phân phối dữ liệu hoặc tải không đồng đều. Ví dụ, nếu shard key là dấu thời gian, tất cả dữ liệu mới có thể đổ vào shard mới nhất, làm quá tải nó. Tương tự, nếu phân phối dữ liệu bị lệch (ví dụ: nhiều khách hàng hơn trong các phạm vi mã zip nhất định), các shards đó sẽ chịu tải không cân xứng. Việc quản lý phạm vi và chia shards có thể yêu cầu can thiệp thủ công.

  • Sharding Dựa trên Băm (Hash-Based Sharding): Một hàm băm (hash function) được áp dụng cho shard key, và giá trị băm kết quả xác định shard mà dữ liệu thuộc về. Một cách tiếp cận đơn giản phổ biến là shard_id = hash(shard_key) % number_of_shards.

    • Ưu điểm: Thường dẫn đến phân phối dữ liệu đồng đều hơn trên các shards, giảm thiểu vấn đề hotspot phổ biến trong range-based sharding.

    • Nhược điểm: Làm cho các truy vấn phạm vi trên shard key rất kém hiệu quả, vì các giá trị khóa liên tiếp bị phân tán ngẫu nhiên trên các shards. Một nhược điểm đáng kể là độ phức tạp của việc băm lại (rehashing): việc thêm hoặc xóa shards làm thay đổi kết quả của phép toán modulo, thường yêu cầu phân phối lại hàng loạt dữ liệu hiện có trên hầu hết tất cả các shards [Ghi chú của người dùng].

  • Sharding Dựa trên Thư mục (Directory-Based Sharding): Một dịch vụ tra cứu hoặc table riêng biệt (thư mục - directory) duy trì ánh xạ giữa các shard keys (hoặc phạm vi khóa) và vị trí shard vật lý nơi dữ liệu tương ứng cư trú. Để định vị dữ liệu, ứng dụng trước tiên truy vấn thư mục để tìm shard chính xác, sau đó truy vấn shard đó.

    • Ưu điểm: Cung cấp sự linh hoạt tối đa trong phân phối dữ liệu. Các shards có thể được chia hoặc hợp nhất, và dữ liệu có thể được di chuyển đơn giản bằng cách cập nhật ánh xạ thư mục, mà không cần rehashing phức tạp. Cho phép kiểm soát chi tiết về vị trí dữ liệu.

    • Nhược điểm: Bản thân thư mục có thể trở thành nút thắt hiệu suất (yêu cầu một bước tra cứu bổ sung) và một điểm lỗi duy nhất nếu không được làm cho có high availability. Thêm độ phức tạp vận hành để duy trì thư mục.

  • Băm Nhất quán (Consistent Hashing - Giải quyết Vấn đề Băm lại của Hash-Based): Đây không phải là một chiến lược sharding riêng biệt mà là một kỹ thuật băm tiên tiến thường được sử dụng trong hash-based sharding để khắc phục vấn đề rehashing tốn kém khi số lượng shards thay đổi.

    • Cơ chế: Nó ánh xạ cả máy chủ (shards) và khóa dữ liệu lên một vòng tròn trừu tượng (một vòng băm - hash ring, thường đại diện cho một không gian giá trị băm lớn, ví dụ: 0 đến 232−1). Một khóa được gán cho máy chủ đầu tiên gặp phải khi di chuyển theo chiều kim đồng hồ xung quanh vòng từ vị trí của khóa.

    • Lợi ích: Khi một máy chủ được thêm hoặc xóa, chỉ các khóa ánh xạ tới phạm vi ngay liền kề với máy chủ đó trên vòng mới cần được phân phối lại. Trung bình, việc thêm hoặc xóa một máy chủ chỉ yêu cầu di chuyển một phần (k/n, trong đó k là số lượng khóa và n là số lượng máy chủ) của các khóa, giảm đáng kể nỗ lực cân bằng lại so với băm modulo đơn giản.

    • Giảm thiểu Hotspot: Mặc dù tốt hơn băm modulo, consistent hashing cơ bản vẫn có thể dẫn đến tải không đồng đều nếu các máy chủ tình cờ rơi vào các vị trí không đều trên vòng. Điều này thường được giải quyết bằng cách sử dụng các nút ảo (virtual nodes hoặc vnodes), trong đó mỗi máy chủ vật lý được gán nhiều vị trí (virtual nodes) trên hash ring, dẫn đến phân phối khóa mượt mà và cân bằng hơn nhiều.

  • Chọn Khóa Shard (Shard Key): Việc lựa chọn shard key là rất quan trọng cho sự thành công của horizontal sharding. Một shard key lý tưởng nên có cardinality cao (nhiều giá trị duy nhất) và phân phối dữ liệu và tải truy vấn đồng đều trên các shards. Nó cũng nên phù hợp với các mẫu truy vấn phổ biến để giảm thiểu nhu cầu về các truy vấn chéo shard (cross-shard queries), vốn phức tạp và chậm.

Thách thức: Sharding giới thiệu sự phức tạp đáng kể vượt ra ngoài một database đơn máy chủ. Các thách thức chính bao gồm xử lý các transactions kéo dài trên nhiều shards, thực hiện joins trên các shards, cân bằng lại dữ liệu một cách hiệu quả khi các shards được thêm hoặc xóa (ngay cả với consistent hashing, vẫn cần di chuyển một số dữ liệu), và gánh nặng vận hành tổng thể tăng lên khi quản lý một cluster phân tán.

Bảng 3: So sánh các Chiến lược Sharding Ngang

FeatureRange-BasedHash-Based (Modulo)Hash-Based (Consistent Hashing)Directory-Based
MechanismKey range assignmenthash(key) % NMap key/server to hash ringLookup table maps key to shard
Data DistributionPotentially uneven (skewed)Generally evenGenerally even (esp. w/ vnodes)Highly flexible
Range Query (on Key)EfficientInefficientInefficientDepends on directory structure
Hotspot PotentialHighLowLow (esp. w/ vnodes)Low (if directory managed well)
Rebalancing EffortModerate (split/merge ranges)Very High (most keys move)Low (only adjacent keys move)Low (update directory)
Key BenefitSimple, good for range queriesEven distributionMinimal rebalancing disruptionMaximum flexibility

Export to Sheets

(Xuất ra Sheets)

D. Khi nào Nên Shard (và Khi nào Không) Sharding là một kỹ thuật mạnh mẽ, nhưng nó làm tăng thêm độ phức tạp. Quyết định shard nên được cân nhắc kỹ lưỡng:

Cân nhắc một Database Tập trung (Không Sharding) khi: [Ghi chú của người dùng]

  • Đảm bảo ACID mạnh mẽ trên toàn bộ tập dữ liệu là tối quan trọng và dễ dàng quản lý trên một node duy nhất.

  • Yêu cầu kinh doanh và mô hình dữ liệu tương đối ổn định.

  • Kích thước tập dữ liệu và tải lưu lượng truy cập dự kiến có thể được xử lý thoải mái bởi một máy chủ được vertically scaled duy nhất (có thể là một máy chủ rất lớn). Việc sharding sớm có thể thêm độ phức tạp không cần thiết.

Cân nhắc một Database Phân tán (Sharding) khi:

  • Khối lượng dữ liệu vượt quá dung lượng lưu trữ của máy chủ đơn lẻ thực tế lớn nhất.

  • Yêu cầu thông lượng ghi vượt quá dung lượng của một master duy nhất (ngay cả với replication).

  • Đọc/ghi có độ trễ thấp là rất quan trọng và có thể đạt được bằng cách phân phối dữ liệu gần người dùng hơn hoặc xử lý các truy vấn song song trên các shards.

  • High availability là rất quan trọng, và khả năng hệ thống hoạt động ngay cả khi một số shards bị lỗi (chỉ ảnh hưởng đến một tập hợp con dữ liệu) là mong muốn.

VI. Kết luận: Điều hướng các Lựa chọn và Đánh đổi Database Bối cảnh lưu trữ và quản lý dữ liệu trải dài từ các phương pháp dựa trên tệp đơn giản đến các hệ thống database phân tán rất tinh vi. Hành trình này bao gồm việc hiểu thế giới có cấu trúc của relational databases (SQL) với các đảm bảo ACID mạnh mẽ của chúng, khám phá lĩnh vực đa dạng và linh hoạt của NoSQL databases được tối ưu hóa cho các mô hình dữ liệu và mẫu scalability khác nhau, và đi sâu vào các kỹ thuật được sử dụng để mở rộng quy mô hệ thống, chẳng hạn như replicationsharding. Hiệu suất cơ bản bị ảnh hưởng nặng nề bởi các cấu trúc bên trong như B-TreesLSM-Trees.

Trong suốt quá trình khám phá này, một chủ đề trung tâm nổi lên: thiết kế hệ thống database về cơ bản là một bài tập quản lý sự đánh đổi. Hiếm khi có một giải pháp "tốt nhất" duy nhất; thay vào đó, các lựa chọn liên quan đến việc cân bằng các yêu cầu cạnh tranh:

  • Nhất quán vs. Sẵn sàng (Consistency vs. Availability): Định lý CAP nhấn mạnh khó khăn của việc đồng thời đảm bảo strong consistency, high availability, và khả năng chịu lỗi network partitions trong các hệ thống phân tán. Thông thường, các hệ thống phải chọn hai trong ba yếu tố để ưu tiên trong quá trình phân vùng, dẫn đến những đánh đổi như ACID vs. BASE.

  • Nhất quán vs. Hiệu suất/Khả năng mở rộng (Consistency vs. Performance/Scalability): Việc thực thi strong consistency, đặc biệt là trên các nodes phân tán, thường gây ra độ trễ và chi phí, có khả năng hạn chế performancescalability so với các hệ thống cung cấp các mô hình consistency thoải mái hơn.

  • Hiệu suất Đọc vs. Hiệu suất Ghi (Read Performance vs. Write Performance): Các storage engines cơ bản như B-Trees (thường đọc tốt hơn) và LSM-Trees (thường ghi tốt hơn) thể hiện sự đánh đổi này, ảnh hưởng đến sự phù hợp của databases cho các mẫu khối lượng công việc khác nhau.

  • Linh hoạt Schema vs. Toàn vẹn Dữ liệu (Schema Flexibility vs. Data Integrity): Các schema linh hoạt (NoSQL) cho phép phát triển nhanh chóng nhưng có thể yêu cầu xác thực ở cấp ứng dụng nhiều hơn để đảm bảo data integrity so với các schema cứng nhắc và các ràng buộc được thực thi bởi RDBMS.

  • Đơn giản Phát triển vs. Khả năng mở rộng/Hiệu suất (Development Simplicity vs. Scalability/Performance): Một RDBMS đơn node, đơn giản thường dễ phát triển hơn ban đầu, nhưng việc đạt được quy mô lớn có thể đòi hỏi thêm độ phức tạp về kiến trúc và vận hành của các hệ thống NoSQL hoặc distributed SQL (replication, sharding).

  • Chuẩn hóa vs. Phi chuẩn hóa (Normalization vs. Denormalization): RDBMS thường chuẩn hóa (normalize) dữ liệu để giảm dư thừa và cải thiện consistency, có khả năng phải trả giá bằng hiệu suất đọc (yêu cầu joins). NoSQL thường sử dụng phi chuẩn hóa (denormalization) để cải thiện hiệu suất đọc bằng cách đặt dữ liệu liên quan gần nhau, với chi phí là sự dư thừa và các cập nhật có khả năng phức tạp hơn.

Với những phức tạp này, một số khuyến nghị cấp cao xuất hiện:

  1. Bắt đầu Đơn giản: Trừ khi có các yêu cầu rõ ràng về quy mô lớn hoặc tính linh hoạt cực cao ngay từ đầu, hãy bắt đầu với kiến trúc đơn giản nhất đáp ứng nhu cầu hiện tại (thường là một RDBMS đơn node). Tránh tối ưu hóa sớm và sự phức tạp không cần thiết.

  2. Hiểu Rõ Yêu cầu: Trước khi chọn bất kỳ công nghệ database nào, hãy phân tích kỹ lưỡng các nhu cầu cụ thể của ứng dụng liên quan đến cấu trúc dữ liệu (structured, semi-structured, unstructured), mẫu truy vấn (tỷ lệ đọc vs. ghi, độ phức tạp truy vấn, yêu cầu latency), đảm bảo consistency (ACID vs. eventual), và quy mô dự kiến (khối lượng dữ liệu, tăng trưởng lưu lượng truy cập).

  3. Chọn Phù hợp trong các Danh mục: Nếu chọn NoSQL, hãy nhận biết sự đa dạng của nó. Chọn loại NoSQL database (Key-Value, Document, Wide-Column, Graph) phù hợp nhất với mô hình dữ liệu và mẫu truy cập của ứng dụng.

  4. Thừa nhận Độ phức tạp Vận hành: Việc triển khai và quản lý các hệ thống database phân tán (sử dụng replication và/hoặc sharding) giới thiệu những thách thức vận hành đáng kể so với các hệ thống đơn node. Đảm bảo nhóm có chuyên môn và công cụ cần thiết.

  5. Giám sát và Lặp lại: Nhu cầu về hiệu suất và scaling database luôn thay đổi. Liên tục giám sát hiệu suất hệ thống, xác định các nút thắt cổ chai, và chuẩn bị tối ưu hóa cấu hình, truy vấn, indexing, hoặc thậm chí xem xét lại các lựa chọn kiến trúc dựa trên các mẫu sử dụng thực tế.

Cuối cùng, việc thiết kế các hệ thống database hiệu quả đòi hỏi một cái nhìn tổng thể. Nó vượt ra ngoài việc chỉ đơn giản là so sánh danh sách tính năng của các sản phẩm khác nhau. Nó đòi hỏi sự hiểu biết về các nguyên tắc cơ bản—tính toàn vẹn giao dịch (ACID), các ràng buộc hệ thống phân tán (CAP), cấu trúc dữ liệu (B-Trees, LSM-Trees), và các kỹ thuật scaling (replication, sharding)—và áp dụng kiến thức này một cách chiến lược. Mục tiêu là lựa chọn và cấu hình các công nghệ đáp ứng tốt nhất các mục tiêu cụ thể của ứng dụng trong khi quản lý một cách có ý thức các đánh đổi cố hữu ở mọi lớp của hệ thống.

0
Subscribe to my newsletter

Read articles from Ha Ngoc Hieu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ha Ngoc Hieu
Ha Ngoc Hieu