Trường chỉ mục mongodb

Khách mời của tuần này là anh "Quăng" (@xluffy), một nhân vật mà mình chả có gì để giới thiệu =))) anh hiện đang là devops ở đâu không rõ, ngoài ra anh còn là thành viên của nhóm

Các bạn có thể xem thêm bài viết gốc của tác giả tại blog @xluffy


Ai biết đánh index giúp tăng tốc truy vấn tìm kiếm, tìm kiếm trong cây chỉ mục (B-Tree) sẽ nhanh hơn quét trong toàn bộ bảng. Ai cũng biết nên đừng nói nhiều về chỉ số là gì nữa. v vậy nhé

Đây là tập dữ liệu người dùng MongoDB mà bạn có thể tải về chơi thử

1. Sơ đồ về chỉ mục

Chỉ mục trong Mongo cũng tương tự các cơ sở dữ liệu quan hệ, ví dụ ta có một chỉ mục được định nghĩa như sau

db.users.createIndex({
  user_status: 1
}, {
  background: true,
  name: 'idx_users_by_user_status'
});

Câu trên sẽ nói với việc xây dựng cơ sở dữ liệu chỉ mục dựa trên giá trị của trường

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0 của bộ sưu tập
db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
1

Ngoài ra nó còn có thêm một tùy chọn là

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
2 để xây dựng chỉ mục ở dạng backgroud, thông thường đánh chỉ mục sẽ chặn tất cả các thao tác khác và với các bộ sưu tập có kích thước lớn thì việc đánh chỉ mục sẽ mất tới vài giờ để hoàn thành và

Lưu ý là nên chạy truy vấn đánh chỉ mục trong

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
4 để tránh ngắt kết nối và để kiểm tra quá trình của chỉ mục mà ta có thể truy vấn

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"

Được rồi, tiếp tục với chỉ mục phía trên, nó sẽ có tác dụng với các truy vấn tìm kiếm với điều kiện là trường

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0, ví dụ như sau

________số 8

Không có gì phải bàn cãi thêm, truy vấn trên chỉ số sử dụng

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
6 và trả về kết quả với tốc độ của một vị thần gió

Giờ giả sử, ta có một truy vấn lấy của tất cả người dùng

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
7 NHƯNG trong một tháng của năm 2013 như sau

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
1

Tương tự như trên, lấy ra danh sách các

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
7 khá nhanh chóng do sử dụng chỉ mục như câu truy vấn trên cùng, tuy nhiên trong danh sách trả về
db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
7 ta cần lọc thêm một lần nữa để lấy ra những người dùng đã đăng ký trong tháng đó

Để giải quyết truy vấn trên, ta cần đánh chỉ mục trên 2 trường, gọi là chỉ mục phức như sau

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
6

Ok, trong có vẻ ngon rồi, truy vấn của chúng ta đã nhanh hơn rồi. Nhưng thử nghĩ xem, chúng ta có thể làm tốt hơn hay không nhỉ?

Giả sử bạn có 20 triệu người dùng đăng ký (hoặc bạn có thể tưởng tượng bạn có 1 tỷ người dùng đăng ký và bạn giỏi hơn Mark Zuckerberg. hơi_smiling_face. cũng được)

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0 có các giá trị sau

  • người dùng mới
  • đã xác nhận
  • bị cấm
  • đã xóa

Nếu không có chỉ mục trên

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0, bạn cần quét hết 20 triệu tài liệu này, đánh chỉ mục trên
db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0 con số giảm từ 20 triệu -> 5 triệu (1/4)

Giờ giả sử, start-úp face của bạn ra đời năm 2015, tính đến nay là 3 năm, giả sử đăng ký đồng đều giữa các ngày (thực tế thì đéo phải như vậy đâu). mồ hôi_smile. ). You index back as after

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0

Giờ với câu truy vấn trên, ta được một danh sách những người dùng đã đăng ký trong vòng 1 tháng, là hơn 500 nghìn người dùng (1 ngày có ~ 18 nghìn người dùng đăng ký), sau đó lọc theo điều kiện

db.users.find({
  "user_status": "new_user"
});
5 ta chỉ cần quét

Chốt lại, với chỉ mục ghép nên đánh trường chỉ mục có giá trị uniqe nhiều hơn trước. Ở trên thì đánh

db.users.find({
  "user_status": "new_user"
});
0 trước vì các giá trị trong lĩnh vực này hầu hết đều khác nhau (trong khi
db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0 chỉ có 4 giá trị), nên sẽ giảm vùng tìm kiếm của truy vấn nhiều hơn

2. Làm cách nào để cải thiện

Xong xuôi việc đánh index, bây giờ làm cách nào để ta có thể chắc chắn rằng cơ sở dữ liệu sẽ sử dụng index một cách hiệu quả? . Trong Mongo, RAM thì thường được sử dụng để giữ dữ liệu và chỉ mục truy vấn thường xuyên (WiredTiger internal cache) tránh phải đọc đĩa thường xuyên. Đề xuất là 50% bộ nhớ vật lý, ví dụ máy chủ có 32GB thì bộ nhớ đệm trong là 15GB

Nhưng dữ liệu thì lúc nào cũng lớn hơn RAM, bạn không thể đặt toàn bộ dữ liệu lên Wiredtiger cadhe được, với chỉ số cũng vậy, đánh chỉ số thì các truy vấn đọc dữ liệu trên điều kiện sẽ nhanh, nhưng quá nhiều chỉ số dư thừa thì

  • Không thể chứa tất cả các chỉ mục trên bộ nhớ
  • Làm chậm các truy vấn ghi dữ liệu như Chèn/Cập nhật/Xóa -> nhưng thực hiện

nếu phần ghi chiếm tới 80-95% thì việc đánh đổi cũng đáng kể

Bạn có thể kiểm tra kích thước tổng thể của chỉ mục trên cơ sở dữ liệu như sau

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
4

Cụ thể hơn (tài liệu này ăn cắp trên sản xuất)

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
5

=> size of all index on csdl là 86GB, có nghĩa là index không thể vừa hết bộ nhớ, nên khi nào cần sử dụng tới index, nếu index đó không có sẵn trên bộ nhớ thì cần đọc index đó từ đĩa lên

Nói chung, xác định bao nhiêu bộ nhớ là cần thiết không dễ dàng, bạn có thể trả lời một vài câu hỏi sau để xác định và điều chỉnh bộ nhớ cho hợp lý

  • The big data of you are bao nhiêu?
  • Độ lớn của chỉ mục là bao nhiêu?
  • Độ phát triển của dữ liệu (trong 1 tháng, 1 năm?)
  • Độ lớn của tập dữ liệu thường xuyên truy cập nhất (gọi là working set)?

Vì vậy chiến lược để giữ chỉ mục có kích thước nhỏ là như thế nào?

2. 1 Xóa các chỉ mục không sử dụng

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
6

Ở ví dụ trên, ta có 3 index, ở giá trị

db.users.find({
  "user_status": "new_user"
});
8, ta thấy 2 index đầu tiên được sử dụng rất nhiều lần, trong khi đó index thứ 3 không hề được sử dụng. Con số bao nhiêu là hợp lý để loại bỏ một chỉ mục tùy thuộc vào số lượng truy vấn và nghiệp vụ của chính bạn, bạn có thể tự cân nhắc, đo lường và loại bỏ nếu không cần thiết

Cần lưu ý,

db.users.find({
  "user_status": "new_user"
});
9 có thể bị đặt lại, con số hiện tại có thể hiện số lần được sử dụng kể từ
db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
10 -> thời gian mongod được khởi động lại

2. 2 Loại bỏ các dư thừa chỉ mục

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0

At ví dụ trên, cả 2 chỉ mục đều được sử dụng. Tuy nhiên, chỉ mục đầu tiên có thể làm cho chỉ mục thứ 2 trở nên dư thừa, bởi vì truy vấn chỉ trên điều kiện

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0 có thể sử dụng chỉ mục đầu tiên mà không có vấn đề gì

2. 3 Sử dụng chỉ mục thưa thớt

Đây là kiểu đánh index trên điều kiện, ví dụ ta có 20 triệu người dùng, nhưng nếu ta chỉ truy vấn thông thường

db.currentOp(
  { "msg": /Index Build/ }
);

"msg" : "Index Build (background) Index Build (background): 3305028/574961784 0%"
0 là
db.users.find({
  "user_status": "new_user"
});
5 thì ta có thể đánh index riêng với tập
db.users.find({
  "user_status": "new_user"
});
5 thôi -> giả sử lượng
db.users.find({
  "user_status": "new_user"
});
5 là 40% trên tổng

2. 4 Giảm kích thước của bộ sưu tập

Cách duy nhất để giảm kích thước của bộ sưu tập đó là. xóa dữ liệu, thực tế có những nghiệp vụ lưu trữ nhật ký, sự kiện không cần giữ quá 1-2 năm để tra cứu, thì ta có thể xóa bớt dữ liệu không còn cần thiết (hoặc chuyển nó sang một cơ sở dữ liệu khác với tần suất . Khi kích thước của bộ sưu tập giảm, kích thước của chỉ mục cũng sẽ giảm theo và dữ liệu/chỉ mục có thể vừa với RAM

2. 4 Keep index menu

Chỉ số phức hợp rất lợi hại trong trường hợp truy vấn dữ liệu trên nhiều điều kiện khác nhau, tuy nhiên nó cũng tốn chi phí bảo trì và dữ liệu càng lớn thì kích thước chỉ số sẽ càng tăng nhanh. Tương tự như truy vấn, cố gắng giữ chỉ mục theo cách đơn giản nhất có thể

3. Thưởng

Đáng ra đéo có phần này, cơ mà sợ anh lại thằng chuyên bài trừ Mongo mà nay lại viết về Mongo, thấy sai thế éo nào nên viết thêm tí

Thật ra những lý thuyết về chỉ mục và quản lý chỉ mục đều có thể áp dụng cho các cơ sở dữ liệu quan hệ như MySQL hay PostgreSQL. Ví dụ PostgreSQL cũng có chỉ mục trên điều kiện đó là chỉ mục từng phần tương tự như chỉ mục thưa thớt (MySQL thì không biết là không có)

Bản chất, nếu để ý sẽ thấy các hệ thống CSDL có rất nhiều đặc điểm giống nhau (cả M$ SQL), lý thuyết này có thể áp dụng cho các CSDL khác, và ngược lại. Cốt lõi của vấn đề là tư duy về việc đo lường, cách thức đo lường và đánh giá hiệu quả của mỗi hành động tác động lên hệ thống

Lần sau sẽ viết thêm về một số thứ liên quan đến số liệu trong cơ sở dữ liệu quan hệ, hỗ trợ cho việc tối ưu hệ thống tương tự như trên MongoDB