SingleStore 3 — Universal Storage (rowstore & columnstore)

15 thg 7, 2026 4 lượt xem
#sql
#singlestore
#htap
#columnstore
#rowstore

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 — rowstorecolumnstore — 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 KEY là 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/UPSERT mộ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íRowstoreColumnstore (thuần)Universal Storage (columnstore + hash index)
Vị tríIn-memory (RAM)Trên đĩa (+ cache)Trên đĩa (+ cache)
HướngHàngCộtCột
Điểm mạnhPoint read/write, độ trễ cực thấpScan/aggregate khối lớn, nén caoCả hai — HTAP thật sự
Dung lượngGiới hạn bởi RAM (đắt)Rất lớn, rẻRất lớn, rẻ
Point lookupRất nhanh (skiplist/hash)Chậm (quét segment)Nhanh (secondary hash + seek)
Update/delete lẻRẻĐắtRẻ (subsegment)
Hợp choSession, hàng đợi, bảng nóng nhỏKho phân tích chỉ-đọc/appendBả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 COLUMNSTORE cho hầu hết bảng, kể cả bảng có point lookup — thêm KEY ... USING HASH khi 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ứu WHERE 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 theo txn_id rơ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 KEY quyết định partition; SORT KEY quyết định thứ tự trong columnstore của partition đó — hai thứ khác nhau.
  • Mặc định hiện đại: dùng CLUSTERED COLUMNSTORE cho 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.

15 thg 7, 2026 5

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.

15 thg 7, 2026 5

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.

15 thg 7, 2026 5

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.

13 thg 7, 2026 5

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!