SingleStore 3 — Universal Storage (rowstore & columnstore)
SingleStore 3 — Universal Storage (rowstore & columnstore)
Ở bài 2 chúng ta thấy dữ liệu được chia thành các partition trải trên leaf nodes. Nhưng bên trong một partition, một dòng dữ liệu thực sự nằm ở đâu, được tổ chức thế nào? Câu trả lời phụ thuộc vào kiểu lưu trữ (table type) bạn khai báo khi tạo bảng. SingleStore có hai kiểu vật lý cơ bản — rowstore và columnstore — và một tiến hoá quan trọng gọi là Universal Storage làm mờ ranh giới giữa chúng. Chọn đúng kiểu lưu trữ là quyết định thiết kế quan trọng nhất cho hiệu năng của một bảng.
Mô hình tinh thần: hai cách xếp dữ liệu
Cùng một bảng logic transactions(id, account_id, amount, ts), có hai cách xếp byte trên bộ nhớ/đĩa:
- Row-oriented (hàng-hướng): các cột của cùng một dòng nằm liền nhau. Đọc/ghi một dòng đầy đủ rất nhanh (điểm mạnh OLTP), nhưng quét tổng
SUM(amount)trên 100 triệu dòng phải chạm mọi cột của mọi dòng. - Column-oriented (cột-hướng): mọi giá trị của cùng một cột nằm liền nhau. Quét/gộp một vài cột trên khối dữ liệu lớn cực nhanh và nén rất tốt (điểm mạnh OLAP), nhưng lấy một dòng đầy đủ phải ghép mảnh từ nhiều nơi.
Rowstore — in-memory, độ trễ thấp
Rowstore lưu toàn bộ dữ liệu bảng trong RAM (durability vẫn được bảo đảm qua transaction log + snapshot trên đĩa — sẽ nói ở bài 8). Đặc điểm cốt lõi:
- Hàng-hướng: mỗi dòng là một bản ghi liền mạch trong bộ nhớ.
- Chỉ mục lock-free skiplist: rowstore dùng cấu trúc skiplist không khoá cho index, cho phép nhiều luồng đọc/ghi đồng thời với tranh chấp thấp và hỗ trợ truy vấn theo khoảng (range) và theo thứ tự. Ngoài ra rowstore hỗ trợ hash index cho khớp chính xác (xem bài 6).
- Độ trễ rất thấp cho point lookup, insert/update/delete lẻ — hợp với OLTP, session state, hàng đợi, bảng "nóng" ghi liên tục.
Điểm đánh đổi: dung lượng bị giới hạn bởi RAM của cluster, nên rowstore đắt cho dữ liệu lớn.
-- Rowstore: khai báo tường minh bằng KEY (...) USING HASH hoặc SHARD KEY thường
CREATE TABLE session_state (
session_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
updated_at DATETIME(6) NOT NULL,
payload JSON,
SHARD KEY (session_id),
PRIMARY KEY (session_id) -- primary key rowstore = skiplist/hash
) /*+ ROWSTORE */;
Columnstore — trên đĩa, cột-hướng, nén
Columnstore là kiểu lưu trữ mặc định cho bảng lớn/phân tích. Dữ liệu nằm trên đĩa (với buffer/cache trong RAM), tổ chức thành các đơn vị gọi là row segment:
- Mỗi bảng partition được chia thành các segment (mặc định cỡ khoảng một triệu dòng mỗi segment). Mỗi segment lưu một blob nén riêng cho từng cột — nên nén rất mạnh vì giá trị cùng loại nằm cạnh nhau.
- SingleStore giữ metadata theo segment (min/max của mỗi cột trong segment). Nhờ đó truy vấn có thể bỏ qua (segment elimination) những segment không thể chứa dữ liệu thoả điều kiện
WHERE— tương tự "data skipping". - SORT KEY quyết định dữ liệu được sắp xếp như thế nào bên trong columnstore. Sắp theo cột hay lọc/khoảng (ví dụ
ts) làm min/max của mỗi segment hẹp lại → segment elimination hiệu quả hơn nhiều và nén tốt hơn.SORT KEYlà tham số quan trọng nhất của một bảng columnstore. - Ghi mới trước tiên vào một rowstore-backed segment nhỏ (vùng đệm ghi), sau đó được flush/gộp thành columnstore segment nén ở hậu trường — nên nên insert theo lô lớn.
-- Columnstore: KEY (...) USING CLUSTERED COLUMNSTORE khai báo SORT KEY
CREATE TABLE transactions (
txn_id BIGINT NOT NULL,
account_id BIGINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
ts DATETIME(6) NOT NULL,
channel VARCHAR(16),
SHARD KEY (account_id), -- phân phối theo account
KEY (ts) USING CLUSTERED COLUMNSTORE -- SORT KEY = ts
);
KEY (ts) USING CLUSTERED COLUMNSTORE chính là cú pháp khai báo một bảng columnstore và đặt SORT KEY = ts. SHARD KEY (xem bài 4) quyết định dòng thuộc partition nào; SORT KEY quyết định thứ tự bên trong columnstore của partition đó.
Universal Storage — hợp nhất row và column
Vấn đề kinh điển của columnstore truyền thống: nó tuyệt cho quét/gộp khối lớn, nhưng point lookup (WHERE txn_id = ?) và update/delete một dòng thì đắt, vì phải giải nén cả segment. Trước đây bạn buộc phải chọn: rowstore cho OLTP hay columnstore cho OLAP — và nhiều khi phải nhân đôi dữ liệu.
Universal Storage là hướng đi của SingleStore để xoá bỏ đánh đổi đó: giữ nền tảng columnstore (nén, quét nhanh, trên đĩa) nhưng bổ sung các khả năng để làm việc kiểu OLTP ngay trên đó:
- Hash index phụ (secondary hash index) trên columnstore: một chỉ mục băm ánh xạ giá trị khoá → vị trí trong segment, cho point lookup nhanh mà không phải quét cả bảng.
- Truy cập subsegment (subsegment access) + seekable: thay vì giải nén nguyên một segment triệu dòng, engine có thể "seek" tới đúng vùng con chứa dòng cần và chỉ giải nén phần đó — chi phí một point read giảm mạnh.
- Update/upsert kiểu OLTP trên columnstore: nhờ hash index + subsegment seek, các thao tác
UPDATE/DELETE/UPSERTmột dòng trở nên khả thi và rẻ, thay vì phải viết lại cả segment.
Kết quả: một bảng columnstore duy nhất có thể vừa phục vụ truy vấn phân tích quét lớn, vừa phục vụ point lookup và ghi lẻ kiểu giao dịch — đúng tinh thần HTAP. Đây là lý do CLUSTERED COLUMNSTORE trở thành mặc định hiện đại cho hầu hết bảng trong SingleStore.
Cú pháp thêm hash index phụ trên columnstore để bật point lookup nhanh:
CREATE TABLE transactions (
txn_id BIGINT NOT NULL,
account_id BIGINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
ts DATETIME(6) NOT NULL,
SHARD KEY (account_id),
KEY (ts) USING CLUSTERED COLUMNSTORE, -- SORT KEY cho scan/OLAP
KEY (txn_id) USING HASH, -- secondary hash index cho point lookup
UNIQUE KEY (txn_id) USING HASH -- ràng buộc duy nhất trên columnstore
);
Khi nào chọn rowstore, columnstore, hay Universal Storage
| Tiêu chí | Rowstore | Columnstore (thuần) | Universal Storage (columnstore + hash index) |
|---|---|---|---|
| Vị trí | In-memory (RAM) | Trên đĩa (+ cache) | Trên đĩa (+ cache) |
| Hướng | Hàng | Cột | Cột |
| Điểm mạnh | Point read/write, độ trễ cực thấp | Scan/aggregate khối lớn, nén cao | Cả hai — HTAP thật sự |
| Dung lượng | Giới hạn bởi RAM (đắt) | Rất lớn, rẻ | Rất lớn, rẻ |
| Point lookup | Rất nhanh (skiplist/hash) | Chậm (quét segment) | Nhanh (secondary hash + seek) |
| Update/delete lẻ | Rẻ | Đắt | Rẻ (subsegment) |
| Hợp cho | Session, hàng đợi, bảng nóng nhỏ | Kho phân tích chỉ-đọc/append | Bảng vừa OLTP vừa OLAP, mặc định hiện đại |
Nguyên tắc thực dụng:
- Mặc định dùng
CLUSTERED COLUMNSTOREcho hầu hết bảng, kể cả bảng có point lookup — thêmKEY ... USING HASHkhi cần tra cứu/ghi lẻ nhanh. - Chỉ chọn rowstore khi bảng nhỏ, cực nóng, cần độ trễ dưới mili-giây cho từng thao tác lẻ và vừa RAM (ví dụ bảng trạng thái phiên).
- Dữ liệu lớn, phân tích, log, transaction lịch sử → columnstore/Universal Storage.
Use case thực tế
Một ngân hàng (số liệu minh hoạ) có bảng transactions khoảng 4 tỷ dòng, vừa phải phục vụ dashboard phân tích (tổng chi tiêu theo ngày/kênh) vừa phải tra cứu chi tiết một giao dịch khi khách khiếu nại và cập nhật trạng thái đối soát của từng dòng.
- Nếu để rowstore: 4 tỷ dòng × ~64 byte không thể vừa RAM cluster → bất khả thi về chi phí.
- Nếu để columnstore thuần: dashboard chạy tốt nhờ segment elimination theo
SORT KEY (ts), nhưng mỗi lần tra cứuWHERE txn_id = ?phải quét nhiều segment — chậm hàng trăm mili-giây, và cập nhật đối soát từng dòng rất đắt. - Với Universal Storage (
CLUSTERED COLUMNSTORE+KEY (txn_id) USING HASH): dashboard vẫn quét cột nhanh, còn point lookup theotxn_idrơi vào khoảng mili-giây nhờ hash index + subsegment seek, và cập nhật trạng thái từng giao dịch trở nên rẻ. Một bảng duy nhất phục vụ cả hai loại tải — không cần sao chép dữ liệu sang hệ OLTP riêng.
Ghi nhớ
- Rowstore = in-memory, hàng-hướng, skiplist lock-free → OLTP độ trễ thấp; giới hạn bởi RAM.
- Columnstore = trên đĩa, cột-hướng, segment + blob nén mỗi cột, sắp theo SORT KEY → OLAP quét/gộp nhanh, nén cao.
- SORT KEY hẹp min/max của segment → segment elimination hiệu quả; đây là tham số quan trọng nhất của bảng columnstore.
- Universal Storage = columnstore + hash index phụ + subsegment access + seekable → point lookup & update kiểu OLTP ngay trên columnstore.
- Cú pháp cốt lõi:
KEY (col) USING CLUSTERED COLUMNSTORE(đặt SORT KEY) vàKEY (col) USING HASH(secondary hash index). SHARD KEYquyết định partition;SORT KEYquyết định thứ tự trong columnstore của partition đó — hai thứ khác nhau.- Mặc định hiện đại: dùng
CLUSTERED COLUMNSTOREcho hầu hết bảng, chỉ dùng rowstore cho bảng nhỏ cực nóng.
Nguồn tham khảo
- SingleStore Documentation — "Columnstore" (row segments, SORT KEY, segment elimination): docs.singlestore.com
- SingleStore Documentation — "Rowstore" (in-memory, skiplist index): docs.singlestore.com
- SingleStore Documentation — "Universal Storage" (secondary hash index, subsegment access, seekable columnstore): docs.singlestore.com
- SingleStore Documentation — "CREATE TABLE" và mục "SORT KEY / SHARD KEY / USING CLUSTERED COLUMNSTORE": docs.singlestore.com
- SingleStore Documentation — "Choosing a Table Storage Type" (rowstore vs columnstore): docs.singlestore.com
- SingleStore Engineering Blog — các bài về Universal Storage và tiến hoá columnstore thành hệ lưu trữ hợp nhất: singlestore.com/blog
Bài viết liên quan
Điểm khác biệt lớn nhất của SingleStore: nó BIÊN DỊCH truy vấn ra mã máy (code generation) rồi chạy song song MPP trên leaf, thay vì diễn giải từng dòng. Bài dựng luồng SQL → tối ưu → sinh mã → plan biên dịch, giải thích plan cache tái dùng (biên dịch 1 lần), query pushdown xuống leaf và aggregator gộp; cách đọc EXPLAIN/PROFILE (thời gian, rows, bộ nhớ, network/reshuffle) và SHOW PLANCACHE để nhận diện reshuffle/broadcast, tối ưu truy vấn.
Kiến trúc shared-nothing của SingleStore: Master Aggregator giữ metadata và điều phối, Child Aggregator scale kết nối, Leaf node chứa dữ liệu chia thành partition. Bài mổ xẻ luồng một query (aggregator nhận → pushdown xuống leaf → gộp kết quả) và cơ chế High Availability master/replica, failover, redundancy level.
SingleStore (tiền thân MemSQL) là database quan hệ phân tán HTAP, tương thích giao thức MySQL, gộp OLTP và OLAP trong một hệ thống. Bài mở màn dựng mô hình tinh thần về HTAP, giải thích vấn đề nó giải quyết (tránh ETL sang warehouse riêng), chỉ rõ khi nào NÊN và KHÔNG NÊN dùng, định vị so với PostgreSQL, ClickHouse, BigQuery, TiDB/CockroachDB, và vẽ bản đồ toàn series 10 bài.
Khoá chính/ngoại/tổng hợp, ràng buộc (NOT NULL, UNIQUE, CHECK, FK) và cách mô hình hoá quan hệ 1:1, 1:n, n:n cho hệ khách hàng — tài khoản — giao dịch. Đi qua chuẩn hoá 1NF/2NF/3NF bằng ví dụ trước/sau cụ thể, rồi bàn khi nào nên cố tình phi chuẩn hoá để đọc nhanh — giúp thiết kế lược đồ đúng ngay từ đầu.
Cảm nhận của bạn
Bình luận
Chưa có bình luận. Hãy là người đầu tiên chia sẻ!