NoSQL 1 — Tổng quan & khi nào dùng

13 thg 7, 2026 2 lượt xem
#sql
#cap-theorem
#nosql
#mongodb
#redis

NoSQL 1 — Tổng quan & khi nào dùng

Trong một hệ thống ngân hàng thực tế, không có một database duy nhất nào "thắng tất cả". Sổ cái giao dịch lõi (core banking) cần đảm bảo tuyệt đối rằng chuyển 10 triệu từ tài khoản A sang B không bao giờ làm mất hay nhân đôi tiền — đó là địa hạt của RDBMS với ACID. Nhưng cùng lúc đó, phiên đăng nhập mobile banking của 3 triệu khách cần được đọc/ghi trong dưới một mili-giây, danh mục sản phẩm vay cần đổi cấu trúc field liên tục mà không phải chạy ALTER TABLE khoá bảng, và dashboard giám sát gian lận cần đếm sự kiện theo thời gian thực. Ép mọi nhu cầu đó vào một PostgreSQL duy nhất là sai lầm kiến trúc.

NoSQL sinh ra để lấp những khoảng trống mà mô hình quan hệ truyền thống không giải tốt ở quy mô lớntốc độ cao. Bài này là nền móng của cả series. Mục tiêu không phải dạy cú pháp — mà dựng một mô hình tinh thần đúng: NoSQL là gì, có mấy họ, đánh đổi ở đâu, và quan trọng không kém — khi nào không nên dùng. Có mô hình đó rồi, các bài sau về mô hình dữ liệu MongoDB, CRUD, index & hiệu năng, Redis cơ bản, pattern Redis, vận hành Redisuse case ngân hàng sẽ ăn khớp thay vì rời rạc.

Lưu ý về sandbox: SQL sandbox của Knowledge Base chạy trên PostgreSQL (chỉ đọc). Trong bài này chỉ có một khối SQL đánh dấu "▶ Chạy được" (để đối chiếu điểm mạnh của RDBMS). Mọi lệnh MongoDB và Redis đều không chạy được ở đây — chúng chỉ mang tính minh hoạ.

NoSQL là gì và vì sao ra đời

Tên "NoSQL" gây hiểu lầm. Nó không có nghĩa "không dùng SQL" (nhiều hệ NoSQL vẫn có ngôn ngữ giống SQL), mà nên hiểu là "Not Only SQL" — không chỉ có mô hình quan hệ. Đây là một nhóm các database từ bỏ (một phần) mô hình bảng-quan-hệ chuẩn hoá để đổi lấy những thứ khác: khả năng mở rộng ngang, schema linh hoạt, hoặc độ trễ cực thấp.

Vì sao chúng ra đời? Vào cuối những năm 2000, các công ty như Google, Amazon, Facebook chạm trần ba giới hạn của RDBMS đơn máy:

  1. Quy mô (scale). RDBMS truyền thống mở rộng bằng cách nâng cấp phần cứng của một máy (scale dọc — vertical): thêm CPU, RAM, đĩa. Nhưng một máy có trần vật lý và giá tăng theo cấp số nhân. Khi dữ liệu vượt sức một máy, việc sharding (chia dữ liệu ra nhiều máy) trên RDBMS quan hệ rất khó vì JOIN và transaction xuyên nhiều máy đắt đỏ. NoSQL được thiết kế để scale ngang (horizontal) từ gốc — thêm máy giá rẻ thay vì nâng cấp một máy.

  2. Linh hoạt schema. Mô hình quan hệ đòi hỏi định nghĩa schema cứng trước khi ghi. Mỗi lần thêm field là một lần ALTER TABLE, có thể khoá bảng và migration đau đớn trên bảng tỷ dòng. Với các ứng dụng web thay đổi nhanh, dữ liệu bán cấu trúc (JSON từ API, log, sự kiện), sự cứng nhắc này thành gánh nặng.

  3. Tốc độ và độ trễ. Một số nhu cầu (cache, đếm counter, session, leaderboard) cần độ trễ dưới mili-giây với hàng trăm nghìn thao tác/giây. Đi qua tầng parser SQL, planner, MVCC, đọc từ đĩa của RDBMS là quá nặng. Một key-value store trong bộ nhớ như Redis giải bài này gọn hơn nhiều bậc.

NoSQL trả lời ba giới hạn đó, nhưng không miễn phí — nó đánh đổi tính nhất quán mạnh, khả năng JOIN tuỳ ý và ràng buộc toàn vẹn. Hiểu đánh đổi này là mấu chốt của toàn series.

Bốn họ NoSQL

NoSQL không phải một khối đồng nhất mà là bốn họ với mô hình dữ liệu và use case khác hẳn nhau.

Document (MongoDB, Couchbase)

Lưu dữ liệu dưới dạng document — về bản chất là JSON (MongoDB dùng BSON, phiên bản nhị phân). Mỗi document là một object tự chứa, có thể lồng array và sub-object nhiều tầng. Không cần schema cố định: hai document trong cùng collection có thể có tập field khác nhau.

  • Đặc điểm: dữ liệu "gói" theo đối tượng nghiệp vụ (một hồ sơ khách hàng = một document gồm cả địa chỉ, danh sách tài khoản, lịch sử liên hệ). Đọc một đối tượng = một lần truy vấn, không cần JOIN nhiều bảng.
  • Use case: hồ sơ khách hàng, catalog sản phẩm, CMS, cấu hình ứng dụng, dữ liệu bán cấu trúc thay đổi cấu trúc thường xuyên.

Key-Value (Redis, Amazon DynamoDB, Memcached)

Mô hình đơn giản nhất: một khoá ánh xạ tới một giá trị. Truy cập bằng khoá gần như O(1), thường lưu toàn bộ trong RAM.

  • Đặc điểm: đọc/ghi cực nhanh (Redis đạt hàng trăm nghìn ops/giây, độ trễ sub-millisecond) nhưng chỉ tra được theo khoá — không query theo nội dung giá trị như SQL.
  • Use case: cache, session store, rate limiting, counter, hàng đợi tác vụ, leaderboard, pub/sub thời gian thực.

Wide-Column (Apache Cassandra, HBase, Google Bigtable)

Còn gọi là column-family. Dữ liệu tổ chức thành hàng, mỗi hàng có thể chứa số lượng cột khác nhau (thưa — sparse), cột được nhóm thành family. Thiết kế cho ghi cực nặng và phân tán trên hàng trăm node.

  • Đặc điểm: tối ưu cho throughput ghi lớn và mở rộng ngang gần như tuyến tính; truy vấn phải bám theo partition key được thiết kế sẵn (không JOIN, không truy vấn tuỳ ý). Đừng nhầm với column-store OLAP như ClickHouse — mục tiêu khác nhau.
  • Use case: dữ liệu time-series, log sự kiện, IoT, dữ liệu ghi liên tục theo thời gian.

Graph (Neo4j)

Lưu dữ liệu dưới dạng node (thực thể) và edge (quan hệ), mỗi cái mang thuộc tính. Tối ưu cho việc đi theo quan hệ nhiều bậc — thứ mà SQL phải làm bằng chuỗi JOIN đắt đỏ hoặc truy vấn đệ quy.

  • Đặc điểm: trả lời câu hỏi kiểu "khách hàng A liên hệ với ai, người đó liên hệ với ai nữa, có ai trong danh sách đen không" trong vài bước duyệt đồ thị.
  • Use case: chống gian lận (fraud ring), phân tích mạng lưới quan hệ sở hữu, hệ gợi ý, quản lý danh tính.

Bảng tổng hợp nhanh:

HọMô hình dữ liệuTruy vấn mạnh ởĐại diện
DocumentJSON/BSON lồng nhauTruy vấn theo field, đối tượng tự chứaMongoDB
Key-Valuekhoá → giá trịTra theo khoá, tốc độ cực caoRedis, DynamoDB
Wide-Columnhàng thưa, cột độngGhi nặng, quét theo partitionCassandra, HBase
Graphnode + edgeDuyệt quan hệ nhiều bậcNeo4j

SQL vs NoSQL: ba trục khác biệt

1. Schema cứng vs linh hoạt

RDBMS theo schema-on-write: bạn định nghĩa cấu trúc trước, dữ liệu ghi vào phải khớp. Điều này bảo vệ tính toàn vẹn (không thể ghi số tiền vào cột tên), nhưng làm việc thay đổi cấu trúc chậm. NoSQL (nhất là document) theo schema-on-read: ghi gì cũng được, ứng dụng chịu trách nhiệm diễn giải. Linh hoạt hơn, nhưng rủi ro dữ liệu "bẩn" đẩy về phía ứng dụng.

2. ACID vs BASE

RDBMS đề cao ACID: Atomicity (toàn bộ hoặc không), Consistency (dữ liệu luôn thoả ràng buộc), Isolation (giao dịch không đạp chân nhau), Durability (đã commit thì không mất). Đây là lý do sổ cái giao dịch phải nằm ở RDBMS — chi tiết ở bài Transaction & ACID.

Nhiều hệ NoSQL phân tán chọn BASE thay thế:

  • Basically Available — hệ luôn phản hồi, kể cả khi một phần bị lỗi.
  • Soft state — trạng thái có thể thay đổi theo thời gian dù không có input mới (do đồng bộ giữa các bản sao).
  • Eventual consistency — nếu ngừng ghi, sau một khoảng thời gian mọi bản sao sẽ hội tụ về cùng giá trị.

BASE ưu tiên tính sẵn sàng và tốc độ hơn nhất quán tức thời. Lưu ý: đây là phổ chứ không phải nhị phân — MongoDB hiện đại có transaction đa document ACID, còn PostgreSQL có thể chạy async replication kiểu eventual. Đừng dán nhãn tuyệt đối.

3. Scale dọc vs scale ngang

RDBMS truyền thống scale dọc (một máy mạnh hơn) là chính; sharding được nhưng phức tạp. NoSQL scale ngang bằng cách chia dữ liệu (sharding/partitioning) và nhân bản (replication) trên nhiều node giá rẻ, coi node lỗi là chuyện bình thường phải chịu được.

Định lý CAP và PACELC

Đây là khung tư duy cốt lõi để hiểu vì sao hệ phân tán phải đánh đổi.

Định lý CAP phát biểu: một hệ dữ liệu phân tán không thể đồng thời đảm bảo cả ba khi có partition (mất kết nối giữa các node):

  • C — Consistency: mọi node đọc ra cùng một giá trị mới nhất.
  • A — Availability: mọi request đều nhận phản hồi (không lỗi, không treo).
  • P — Partition tolerance: hệ vẫn chạy dù mạng giữa các node bị đứt.

Trong hệ phân tán thực tế, partition là điều không tránh được (mạng sẽ có lúc đứt), nên P là bắt buộc. Do đó lựa chọn thật sự chỉ còn giữa C và A khi partition xảy ra:

  • CP (chọn Consistency): khi mạng đứt, hệ từ chối trả về dữ liệu có thể cũ — thà báo lỗi còn hơn trả sai. Ví dụ tư duy: sổ cái tiền.
  • AP (chọn Availability): khi mạng đứt, hệ vẫn trả lời bằng dữ liệu có thể chưa mới nhất, đồng bộ lại sau. Ví dụ tư duy: giỏ hàng, đếm lượt xem.

Giải thích trực giác: Tưởng tượng hai chi nhánh ghi cùng một số dư nhưng đường truyền giữa chúng đứt. Nếu bạn muốn nhất quán (CP), chi nhánh không chắc mình có bản mới nhất phải từ chối phục vụ cho tới khi kết nối lại. Nếu bạn muốn sẵn sàng (AP), cả hai vẫn phục vụ theo bản mình đang giữ, chấp nhận rằng trong thời gian đứt kết nối hai bên có thể thấy số dư khác nhau, rồi hoà giải sau. Không có lựa chọn thứ ba khi mạng đã đứt.

PACELC bổ sung phần CAP bỏ sót: CAP chỉ nói về lúc có partition, nhưng phần lớn thời gian mạng bình thường. PACELC đọc là: if Partition then choose A or C; Else (bình thường) choose Latency or Consistency.

Nghĩa là ngay cả khi mạng ổn, hệ vẫn phải chọn: chờ đồng bộ đủ các bản sao để đảm bảo nhất quán (chậm hơn — ưu tiên C) hay trả lời ngay từ một bản gần nhất (nhanh hơn — ưu tiên L, latency). Ví dụ: Cassandra thường là PA/EL (ưu tiên sẵn sàng khi partition, ưu tiên độ trễ thấp khi bình thường); một hệ như hệ quan hệ đồng bộ mạnh nghiêng về PC/EC. PACELC giải thích vì sao "eventual consistency" tồn tại cả khi mạng khoẻ — đó là cái giá của độ trễ thấp.

Eventual consistency: hiểu đúng để không sợ

Eventual consistency không phải "dữ liệu sai" mà là "dữ liệu đúng, chỉ hơi trễ". Khi bạn ghi vào một node, thay đổi lan tới các bản sao sau một khoảng (thường mili-giây tới giây). Trong khoảng đó, đọc từ bản khác có thể ra giá trị cũ. Nếu ngừng ghi, mọi bản sẽ hội tụ về cùng giá trị.

Điều này chấp nhận được cho: đếm lượt xem, danh mục sản phẩm, feed thông báo, cache số dư hiển thị. Điều này không chấp nhận được cho: trừ tiền tài khoản, hạn mức thẻ, kiểm tra số dư trước khi cho rút. Quy tắc thực chiến: tiền và quyết định tài chính đi qua RDBMS ACID; phần hiển thị/tối ưu trải nghiệm có thể dùng NoSQL eventual.

Mô hình dữ liệu query-driven, không chuẩn hoá

Đây là cú lật tư duy lớn nhất với người quen SQL. Trong thế giới quan hệ, bạn chuẩn hoá (normalize): tách dữ liệu ra nhiều bảng để tránh trùng lặp, rồi JOIN lại khi cần. Thiết kế xuất phát từ cấu trúc dữ liệu.

Trong NoSQL, bạn thiết kế xuất phát từ mẫu truy vấn (query-driven): câu hỏi "ứng dụng sẽ đọc dữ liệu này như thế nào, thường xuyên ra sao" quyết định cách lưu. Vì phần lớn NoSQL không JOIN tốt (hoặc không JOIN được), bạn gom sẵn dữ liệu hay đọc chung vào một chỗ (denormalize / embed). Ví dụ: thay vì bảng customers và bảng addresses riêng rồi JOIN, MongoDB nhúng địa chỉ ngay trong document khách hàng — đọc một phát ra đủ.

Đánh đổi: đọc nhanh hơn (một truy vấn), nhưng dữ liệu trùng lặp và cập nhật khó hơn (đổi một thông tin phải sửa nhiều nơi). Đây là lựa chọn có chủ đích, không phải "thiết kế xấu". Bài mô hình MongoDB sẽ đào sâu embed vs reference.

Điểm mạnh RDBMS để đối chiếu

Trước khi ca ngợi NoSQL, hãy nhớ RDBMS mạnh chỗ nào: JOIN nhiều bảng và tổng hợp tuỳ ý trên dữ liệu chuẩn hoá, với đảm bảo nhất quán. Câu dưới đây tổng hợp doanh số theo thành phố và phòng ban trên nhiều bảng — chạy được trên sandbox PostgreSQL, minh hoạ đúng thứ NoSQL thường phải denormalize thủ công mới làm được:

-- ▶ Chạy được
SELECT c.city,
       d.name AS department,
       COUNT(DISTINCT t.id)      AS so_giao_dich,
       COALESCE(SUM(t.amount), 0) AS tong_tien
FROM customers c
JOIN accounts a     ON a.customer_id = c.id
JOIN transactions t ON t.account_id = a.id
JOIN employees e    ON e.department_id IS NOT NULL
JOIN departments d  ON d.id = e.department_id
GROUP BY c.city, d.name
ORDER BY tong_tien DESC
LIMIT 20;

Một câu SELECT gom năm bảng lại, đếm và tổng hợp có bảo đảm nhất quán ngay tại thời điểm đọc — đó là thứ mà một document store hay key-value store không cho bạn miễn phí. Trong NoSQL, bạn phải quyết định trước sẽ tính sẵn con số này ở đâu.

Polyglot persistence: dùng đúng công cụ cho đúng việc

Polyglot persistence là nguyên tắc: một hệ thống lớn dùng nhiều loại database khác nhau, mỗi loại cho nhu cầu nó giỏi nhất, thay vì ép tất cả vào một. Đây là hệ quả tự nhiên khi ta hiểu rằng không có database nào tối ưu cho mọi bài toán.

Một cổng ngân hàng số điển hình có thể dùng:

  • PostgreSQL/Oracle cho sổ cái giao dịch (ACID, tiền bạc).
  • MongoDB cho hồ sơ khách hàng 360, catalog sản phẩm vay (schema linh hoạt).
  • Redis cho session, cache số dư hiển thị, rate limiting, OTP TTL.
  • Cassandra cho log giao dịch/audit ghi nặng theo thời gian.
  • Neo4j cho phát hiện vòng gian lận.
  • Elasticsearch cho tìm kiếm giao dịch/khách hàng full-text — xem tổng quan Elasticsearch.

Cái giá của polyglot là độ phức tạp vận hành: nhiều hệ để backup, giám sát, đồng bộ. Đừng thêm một store mới trừ khi nhu cầu thực sự vượt khả năng của cái đang có.

KHÔNG phải "NoSQL thay RDBMS"

Nhấn mạnh lại vì đây là hiểu lầm phổ biến nhất: NoSQL bổ sung, không thay thế RDBMS. Suốt những năm 2010 nhiều team chạy theo hype, ném cả dữ liệu tài chính vào MongoDB rồi tự viết lại logic transaction ở tầng ứng dụng — sai lầm đắt giá. Đồng thời RDBMS cũng đã học hỏi: PostgreSQL có kiểu JSONB cho schema linh hoạt, có replication để scale đọc. Ranh giới mờ dần, nhưng nguyên tắc chọn công cụ theo bài toán thì không đổi.

Quy tắc quyết định gọn:

Nhu cầuChọn
Giao dịch tiền, ràng buộc toàn vẹn mạnhRDBMS (ACID)
JOIN/tổng hợp tuỳ ý, báo cáoRDBMS hoặc OLAP
Đối tượng tự chứa, schema đổi liên tụcDocument (MongoDB)
Cache, session, counter, sub-msKey-value (Redis)
Ghi nặng time-series/logWide-column (Cassandra)
Duyệt quan hệ nhiều bậcGraph (Neo4j)

Use case thực tế

Bối cảnh: Ngân hàng NCB triển khai màn hình "Chi tiết khách hàng" trên app mobile. Yêu cầu: mở màn hình phải trả về thông tin khách + danh sách tài khoản + 20 giao dịch gần nhất trong dưới 300ms, với 3 triệu người dùng hoạt động, giờ cao điểm ~8.000 lượt mở/phút.

Kiến trúc ban đầu (chỉ RDBMS): mỗi lần mở màn hình chạy 4–5 câu JOIN trên bảng customers, accounts, transactions (bảng transactions ~2 tỷ dòng). Đo được p95 ~ 900ms giờ cao điểm, DB primary CPU chạm 85%, thỉnh thoảng timeout.

Áp dụng polyglot persistence:

  1. Sổ cái vẫn ở PostgreSQL — nguồn chân lý cho số dư và giao dịch (ACID, không đụng tới).
  2. Thêm MongoDB làm read model: mỗi khách hàng là một document gom sẵn hồ sơ + snapshot tài khoản + 20 giao dịch gần nhất (query-driven, denormalize). Màn hình đọc một truy vấn theo customer_id thay vì 5 JOIN.
  3. Thêm Redis cache: document khách sau khi đọc lần đầu được cache với TTL 60 giây. Số dư hiển thị (không phải số dư dùng để trừ tiền) cache 10 giây.
  4. Đồng bộ eventual: khi có giao dịch mới ghi vào PostgreSQL, một tiến trình cập nhật lại document MongoDB và xoá cache Redis. Trễ vài trăm mili-giây là chấp nhận được vì đây là màn hình hiển thị, không phải bước trừ tiền.

Kết quả (minh hoạ theo tình huống điển hình): p95 giảm còn ~120ms; tải đọc lên DB primary giảm hơn 90% nhờ Redis chặn đa số request lặp; PostgreSQL primary rảnh tay lo phần ghi giao dịch. Điểm mấu chốt: quyết định trừ tiền vẫn 100% qua PostgreSQL ACID — NoSQL chỉ gánh phần đọc/hiển thị. Đó chính là tinh thần bổ sung, không thay thế.

Ghi nhớ

  • NoSQL = "Not Only SQL", không phải "không SQL". Nó bổ sung RDBMS cho các bài toán quy mô lớn / schema linh hoạt / tốc độ cao, không thay thế RDBMS cho giao dịch tiền.
  • Bốn họ: document (MongoDB — đối tượng tự chứa), key-value (Redis — cache/session/counter sub-ms), wide-column (Cassandra — ghi nặng time-series), graph (Neo4j — duyệt quan hệ, chống gian lận).
  • SQL vs NoSQL: schema-on-write cứng vs schema-on-read linh hoạt; ACID (nhất quán mạnh) vs BASE (eventual); scale dọc vs scale ngang. Đây là phổ, không phải nhị phân tuyệt đối.
  • CAP: khi mạng partition, phải chọn Consistency hoặc Availability (P là bắt buộc). CP = thà lỗi còn hơn trả dữ liệu cũ; AP = vẫn trả lời, hoà giải sau.
  • PACELC: ngay cả khi mạng bình thường (Else) vẫn phải đánh đổi Latency vs Consistency — giải thích vì sao eventual consistency tồn tại cả khi mạng khoẻ.
  • Eventual consistency = đúng nhưng hơi trễ; ổn cho hiển thị/đếm, không ổn cho trừ tiền/hạn mức.
  • Query-driven, không chuẩn hoá: thiết kế theo mẫu truy vấn và denormalize/embed dữ liệu hay đọc chung — đánh đổi đọc nhanh lấy cập nhật khó.
  • Polyglot persistence: dùng nhiều loại store cho từng nhu cầu; cái giá là độ phức tạp vận hành — đừng thêm store nếu chưa cần.
  • Bối cảnh ngân hàng: RDBMS cho giao dịch lõi (ACID); NoSQL cho cache/session/catalog/real-time. Hai store đi sâu trong series: MongoDB (document) và Redis (key-value).

Bài viết liên quan

Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.

13 thg 7, 2026 4

Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.

13 thg 7, 2026 4

Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.

13 thg 7, 2026 3

Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.

13 thg 7, 2026 3