Appearance
1. Secondary Index là gì ?
Secondary Index sẽ cho phép chúng ta truy vấn dữ liệu không phải là một thành phần trong khóa chính (primary key)
Tuy nhiên do kiến chúc phân tán của db Casandra nên các truy vấn secondary index không được tối ưu khi truy vấn trên tệp dữ liệu lớn, truy vấn thường xuyên .
Hiểu đơn giản Secondary là INDEX trong mysql. Tuy nhiên vì cơ chế phân tán của Cassandra, nên nếu chỉ dùng Index mà không dùng Partition Key, Clustering Key thì ảnh hưởng hiệu suất rất nhiều.
Nên sử dụng khi:
- Truy vấn không xảy ra quá thường xuyên hoặc không tạo ra tải ghi/đọc quá nặng.
- Cardinality cao: Dữ liệu trong Secondary Index đa dạng dữ liệu, chứa ít dữ liệu lặp lại.
- Ví dụ: Cột email sẽ là một cardinality cao vì nó sẽ có rất nhiều data khác nhau. Còn age hoặc sex thì chỉ có trong khoảng từ 1 đến 200 cho age hoặc sex thì có nam và nữa và giới tính thứ 3.
Cần cân nhắc:
Với khối lượng dữ liệu lớn hoặc cột có giá trị ít đa dạng, việc sử dụng secondary index có thể gây ra tình trạng hiệu năng giảm sút. Trong những trường hợp này, việc thiết kế lại mô hình dữ liệu theo hướng denormalization hay sử dụng materialized views sẽ hiệu quả hơn.
1.1 Ví dụ về Secondary Index trong Cassandra:
Cho ví dụ với table như sau
sql
CREATE TABLE users
(
user_id UUID PRIMARY KEY,
name TEXT,
email TEXT,
age INT
);
Trong ví dụ này, user_id là khóa chính của bảng. Nếu bạn muốn tìm kiếm người dùng theo email mà không phải qua user_id, bạn có thể tạo một Secondary Index trên cột email:
sql
CREATE INDEX email_index ON users (email);
Sau khi tạo Secondary Index, bạn có thể thực hiện các truy vấn như sau:
sql
SELECT *
FROM users
WHERE email = '[email protected]';
Cassandra sẽ sử dụng Secondary Index để nhanh chóng tìm kiếm người dùng có email đó, thay vì phải quét toàn bộ bảng.
Vì thực tế địa chỉ email rất ít khi lặp lại, nên việc sử dụng Secondary Index trên cột email sẽ mang lại hiệu suất tốt.
Lưu ý:
Secondary Index có thể làm giảm hiệu suất khi dữ liệu lớn hoặc khi có nhiều ghi/chỉnh sửa trên các cột có chỉ mục. Vì vậy, bạn cần đánh giá kỹ lưỡng khi sử dụng Secondary Index trong các ứng dụng có yêu cầu về hiệu suất cao.
1.2 Cách Secondary Index hoạt động, tìm kiếm dữ liệu và lý do làm giảm hiệu suất
1.2.1. Cách Secondary Index hoạt động trong Cassandra
Khi bạn tạo một Secondary Index trên một cột, Cassandra sẽ tạo một bảng ẩn chứa ánh xạ từ giá trị của cột đó → Partition Key gốc của bảng chính. Theo cơ chế index.
Ví dụ với bảng user
sql
CREATE TABLE users
(
user_id int PRIMARY KEY,
name TEXT,
email TEXT,
age INT
);
Thực tế ID trong Cassandra thường là UUID, vì là demo nên tôi đã int
Nếu tạo một Secondary Index trên cột email:
sql
CREATE INDEX email_index ON users (email);
Cassandra sẽ tạo một bảng ẩn chứa index ẩn có dạng:
user_id(Partition key) | |
---|---|
[email protected] | 1 |
[email protected] | 2 |
[email protected] | 3 |
Khi truy vấn: SELECT * FROM users WHERE email = '[email protected]';
- Cassandra tìm giá trị
[email protected]
trong bảng index. - Tìm thấy
[email protected]
có partition key user_id = 1. - Truy vấn vào bảng
users
bằnguser_id = 1
để lấy dữ liệu đầy đủ.
1.2.2 Vì sao Secondary Index trong Cassandra làm giảm hiệu suất?
Trong cassandra khi sử dụng Secondary Index sẽ gây ra vấn đề scatter-gather" (phân tán-thu thập dữ liệu).
Secondary Index trong Cassandra được triển khai local của Node. Điều này là nghĩa là mỗi node chỉ lưu trữ Index cho dữ liệu mà nó lưu trữ. Khi bạn tạo một Secondary Index, Cassandra sẽ tạo một bảng ẩn (hidden table) trên mỗi node. Bảng này ánh xạ giá trị của cột được đánh chỉ mục tới Primary Key của (các) hàng chứa giá trị đó trên chính node đó.
Những lý do chính khiến hiệu suất giảm bao gồm:
- Query phân tán(Scatter-Gather)):
- Khi thực hiện một query sử dụng Secondary Index mà không chỉ định partition key, query đó phải được gửi đến tất cả các Node trong cluster để tìm kiếm trong Index local trong các Node.
- Sau đó, node điều phối(coordinator node) sẽ thực hiện thu thập kết quả ở tất cả các node và trả về cho client. Quá trình này làm tăng đáng kế độ trễ.
- Nếu giá trị tìm kiếm bằng
Secondary Index
bị trùng lặp nhiều, việc này sẽ làm tăng số lượng dữ liệu cần quét và trả về trên mỗi node sau khi đã tìm kiếm xong trong Index local, dẫn đến hiệu suất giảm.
- Tăng Tải Ghi (Write Amplification)
- Mỗi khi ghi(Insert) ,Cập nhật(Update )hoặc xóa(Delete) dữ liệu trong bảng chính, Cassandra cũng phải cập nhật bảng Index tương ứng. Điều này làm tăng tải ghi trên hệ thống, đặc biệt là khi có nhiều ghi/chỉnh sửa trên các cột có Index.
- Điều này làm tăng gấp đôi (hoặc hơn) số lượng thao tác ghi cho mỗi lần thay đổi dữ liệu.
- Tăng Tải Đọc (Read Amplification):
- Ngay khi truy vấn được gửi đến một node, việc đọc từ Secondary Index yêu cầu hai bước: đầu tiên là tìm kiếm trong bảng chỉ mục để lấy Primary Key, sau đó dùng Primary Key đó để đọc dữ liệu từ bảng chính.
- Nếu một giá trị chỉ mục xuất hiện ở nhiều Record hoặc trên nhiều node, điều này có thể dẫn đến nhiều lần đọc riêng lẻ và giảm hiệu suất.
- Vấn Đề với Cardinality:
- High Cardinality (Độ đa dạng cao): Nếu Column sử dụng để đánh Secondary Index có nhiều giá trị duy nhất(Ví dụ: Email, user_id, ...) bảng Index ẩn có thể sẽ rất lớn, làm tăng dung lượng lưu trữ.
- Mặc dù vẫn gửi request đến tất cả các node, nhưng việc tìm kiếm trong Index sẽ nhanh hơn so với việc quét toàn bộ bảng chính. Bởi vì nếu tại bước đầu tiên không tìm thấy giá trị trong Index thì sẽ không thực hiện lấy dữ liệu từ bảng chính trong node đó.
- Low Cardinality (Độ đa dạng thấp): Nếu column có ít giá trị duy nhất (ví dụ: giới tính, trạng thái), bảng Index sẽ chứa nhiều bản ghi trùng lặp. Điều này dẫn đến việc quét hàng triệu bản ghi trên bảng chính để tìm kiếm các bản ghi phù hợp với Index, làm giảm hiệu suất.
- High Cardinality (Độ đa dạng cao): Nếu Column sử dụng để đánh Secondary Index có nhiều giá trị duy nhất(Ví dụ: Email, user_id, ...) bảng Index ẩn có thể sẽ rất lớn, làm tăng dung lượng lưu trữ.
Minh họa chi tiết về vấn đề hiệu suất .
Giả sử chúng ta có một bảng users để lưu thông tin người dùng, và chúng ta muốn tìm kiếm người dùng theo thành phố.
Chi tiết
1. Tạo Bảng:
sql
CREATE KEYSPACE my_keyspace WITH replication = {
'class': 'SimpleStrategy', 'replication_factor': 3
};
USE my_keyspace;
CREATE TABLE users (
user_id uuid PRIMARY KEY,
name text,
email text,
city text
);
Trong bảng này, user_id
là Partition Key
. Chúng ta chỉ có thể truy vấn hiệu quả theo user_id
. Nếu muốn tìm theo city
, chúng ta cần Secondary Index
.
2. Tạo Secondary Index:
sql
CREATE INDEX idx_city ON users (city);
Cassandra
sẽ tự động tạo một bảng index ẩn trên mỗi node. Bảng này sẽ có cấu trúc tương tự như: idx_city (city_value, user_id).
3. Thêm Dữ Liệu:
sql
INSERT INTO users (user_id, name, email, city) VALUES (uuid(), 'Thành', '[email protected]', 'Hanoi');
INSERT INTO users (user_id, name, email, city) VALUES (uuid(), 'Tuấn', '[email protected]', 'HCM');
INSERT INTO users (user_id, name, email, city) VALUES (uuid(), 'Nam', '[email protected]', 'Hanoi');
- Khi ghi
Thành
, Node chịu trách nhiệm sẽ ghi hàng vàousers
và ghi (Hanoi
,Thành_user_id
) vàoidx_city
. - Khi ghi
Tuấn
, Node chịu trách nhiệm sẽ ghi hàng vàousers
và ghi (HCM
,Tuấn_user_id
) vàoidx_city
. - Khi ghi
Nam
, Node chịu trách nhiệm sẽ ghi hàng vàousers
và ghi (Hanoi
,Nam_user_id
) vàoidx_city
.
Lưu ý rằng Thành
và Nam
có thể nằm trên các node khác nhau, vì partition key token khác nhau, và mỗi node đó sẽ có mục nhập Hanoi
trong index local của nó.
4. Tìm Kiếm Dữ Liệu:
Bây giờ, chúng ta muốn tìm tất cả người dùng ở Hanoi
:
sql
SELECT * FROM users WHERE city = 'Hanoi';
Đây là cách Cassandra xử lý (giả sử có 3 nút N1
, N2
, N3
và Thành
ở N1, Nam
ở N3):
- Node điều phối nhận the query(Có thể là 1 trong các node Cassandra, giả sử là
N1
). - Bởi vì
city
là một Secondary Index, vì vậyN1
sẽ gửi query đến cảN2
vàN3
để tìm kiếm trong index local của chúng.(Tìm kiếm dữ liệu trên cả 2 Node) N1
: TìmHanoi
trongidx_city
local, thấyThành_user_id
. Đọc Record củaThành
từ users.N2
: TìmHanoi
trongidx_city
local, không thấy. Gửi về kết quả rỗng.N3
: TìmHanoi
trongidx_city
local, thấyNam_user_id
. Đọc Record củaNam
từ users và gửi về.N1
thu thập kết quả từN2
vàN3
, trả về cho client.(Thành
,Nam
)
Như bạn thấy, dù chỉ có hai kết quả, truy vấn đã phải hỏi
cả ba Node, gây ra hiện tượng scatter-gather
và làm giảm hiệu suất so với việc truy vấn theo user_id
.
1.3 Khi nào nên dùng Secondary Index?
- Khi thực sự cần truy vấn theo column không phải là partition key và chấp nhận độ trễ cao hơn.
- Khi truy vấn kết hợp với Partition Key, giúp giới hạn số lượng nút cần quét.
- Khi column có độ đa dạng (cardinality) không quá cao cũng không quá thấp (khó xác định).
Khi nào không nên sử dụng Secondary Index?
- Không nên sử dụng Secondary Index khi có thể sử dụng Partition Key để truy vấn.
- Không nên sử dụng Secondary Index nếu không kèm theo Partition Key, vì sẽ làm giảm hiệu suất. 2.1 Nếu có kèm theo Partition Key, thì hiệu suất sẽ tốt hơn. Dữ liệu chỉ cần tìm trên 1 node.
- Không nên sử dụng Secondary Index trên các cột có độ đa dạng (cardinality) quá thấp, vì sẽ làm tăng số lượng bản ghi cần quét.