Hiệu suất truy vấn MongoDB cho hơn 5 triệu bản ghi


78

Gần đây chúng tôi đã đạt được hơn 2 triệu bản ghi cho một trong những bộ sưu tập chính của chúng tôi và bây giờ chúng tôi bắt đầu gặp vấn đề về hiệu suất lớn trên bộ sưu tập đó.

Các tài liệu trong bộ sưu tập có khoảng 8 trường mà bạn có thể lọc bằng cách sử dụng giao diện người dùng và kết quả phải được sắp xếp theo trường dấu thời gian mà bản ghi đã được xử lý.

Tôi đã thêm một số chỉ mục kết hợp với các trường được lọc và bộ đếm thời gian, ví dụ:

db.events.ensureIndex({somefield: 1, timestamp:-1})

Tôi cũng đã thêm một số chỉ mục để sử dụng một số bộ lọc cùng một lúc để hy vọng đạt được hiệu suất tốt hơn. Nhưng một số bộ lọc vẫn mất nhiều thời gian để thực hiện.

Tôi đã đảm bảo rằng sử dụng giải thích rằng các truy vấn sử dụng các chỉ mục tôi đã tạo nhưng hiệu suất vẫn không đủ tốt.

Tôi đã tự hỏi liệu sharding có phải là cách để đi ngay bây giờ hay không .. nhưng chúng tôi sẽ sớm bắt đầu có khoảng 1 triệu bản ghi mới mỗi ngày trong bộ sưu tập đó .. vì vậy tôi không chắc liệu nó có mở rộng quy mô tốt hay không ..

EDIT: ví dụ cho một truy vấn:

> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: ['nickey@acme.com']}}).sort({timestamp: -1}).limit(25).explain()
{
        "cursor" : "BtreeCursor user.userName_1_timestamp_-1",
        "isMultiKey" : false,
        "n" : 0,
        "nscannedObjects" : 30060,
        "nscanned" : 30060,
        "nscannedObjectsAllPlans" : 120241,
        "nscannedAllPlans" : 120241,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 1,
        "nChunkSkips" : 0,
        "millis" : 26495,
        "indexBounds" : {
                "user.userName" : [
                        [
                                "nickey@acme.com",
                                "nickey@acme.com"
                        ]
                ],
                "timestamp" : [
                        [
                                {
                                        "$maxElement" : 1
                                },
                                {
                                        "$minElement" : 1
                                }
                        ]
                ]
        },
        "server" : "yarin:27017"
}

xin lưu ý rằng deviceType chỉ có 2 giá trị trong bộ sưu tập của tôi.


Bạn đang sử dụng limitđối số?
Joe

một trong những tốt đẹp! Tôi có thể sẽ gặp trường hợp tương tự trong tương lai và các câu trả lời cũng có thể hữu ích cho tôi. Cơ sở dữ liệu của bạn có bộ sưu tập đó lớn đến mức nào? Phải mất bao lâu trước khi truy vấn các cặp 8 KV đó trước khi bạn đạt được 2 triệu và bây giờ mất bao lâu? (chỉ tò mò)
anvarik

Joe, tất nhiên là tôi đang sử dụng giới hạn, hiện tại tôi đang giới hạn kết quả của mình ở 25 tài liệu. Tôi thậm chí không muốn nói về số lần bỏ qua vì tôi sẽ thay thế chúng bằng các truy vấn phạm vi trong tương lai gần.
Yarin Miran

2
Thật tuyệt vời, khi bộ sưu tập có khoảng 1-2 triệu bản ghi, tôi đã bắt đầu nhận thấy một số vấn đề về hiệu suất (thời gian truy vấn 5-50 giây). Sau đó, tôi đã thêm các chỉ mục và tôi đã nhận được hiệu suất hợp lý cho truy vấn <1000ms giờ đây các truy vấn mất từ ​​20 mili giây đến 60 giây nhưng tất cả phụ thuộc vào phân phối giá trị của các trường được lọc và mức độ 'hữu ích' của các chỉ mục.
Yarin Miran

Truy vấn nào chậm? Một truy vấn đơn giản không có bộ lọc đã chậm chưa? Hay chỉ các truy vấn được lọc theo một trường chậm? Hay bởi hai lĩnh vực?
Joe

Câu trả lời:


71

Đây là mò kim đáy bể. Chúng tôi cần một số đầu ra explain()cho những truy vấn không hoạt động tốt. Thật không may, ngay cả điều đó sẽ chỉ khắc phục sự cố cho truy vấn cụ thể đó, vì vậy đây là chiến lược về cách tiếp cận điều này:

  1. Đảm bảo không phải do không đủ RAM và phân trang quá nhiều
  2. Bật trình biên dịch DB (sử dụng db.setProfilingLevel(1, timeout)đâu timeoutlà ngưỡng cho số mili giây mà truy vấn hoặc lệnh thực hiện, mọi thứ chậm hơn sẽ được ghi lại)
  3. Kiểm tra các truy vấn chậm trong db.system.profilevà chạy các truy vấn theo cách thủ công bằngexplain()
  4. Cố gắng xác định các hoạt động chậm trong explain() đầu ra, chẳng hạn như scanAndOrderhoặc lớn nscanned, v.v.
  5. Lý do về tính chọn lọc của truy vấn và liệu có thể cải thiện truy vấn bằng cách sử dụng chỉ mục nào cả . Nếu không, hãy xem xét việc không cho phép cài đặt bộ lọc cho người dùng cuối hoặc cung cấp cho họ hộp thoại cảnh báo rằng hoạt động có thể chậm.

Một vấn đề chính là bạn dường như đang cho phép người dùng của mình kết hợp các bộ lọc theo ý muốn. Nếu không có chỉ mục giao nhau, điều đó sẽ làm tăng đáng kể số lượng chỉ mục cần thiết.

Ngoài ra, việc ném chỉ mục một cách mù quáng vào mọi truy vấn có thể là một chiến lược rất tồi. Điều quan trọng là phải cấu trúc các truy vấn và đảm bảo các trường được lập chỉ mục có đủ tính chọn lọc .

Giả sử bạn có truy vấn cho tất cả người dùng có status"hoạt động" và một số tiêu chí khác. Nhưng trong số 5 triệu người dùng, 3 triệu đang hoạt động và 2 triệu thì không, vì vậy hơn 5 triệu mục nhập chỉ có hai giá trị khác nhau. Một chỉ mục như vậy thường không hữu ích. Tốt hơn nên tìm kiếm các tiêu chí khác trước, sau đó quét kết quả. Trung bình, khi trả lại 100 tài liệu, bạn sẽ phải quét 167 tài liệu, điều này sẽ không ảnh hưởng quá nhiều đến hiệu suất. Nhưng nó không đơn giản như vậy. Nếu tiêu chí chính là joined_atngày tháng của người dùng và khả năng người dùng ngừng sử dụng theo thời gian là cao, bạn có thể phải quét hàng nghìn tài liệu trước khi tìm thấy hàng trăm tài liệu phù hợp.

Vì vậy, việc tối ưu hóa phụ thuộc rất nhiều vào dữ liệu (không chỉ cấu trúc của nó mà còn cả bản thân dữ liệu ), các mối tương quan bên trong của nó và các mẫu truy vấn của bạn .

Mọi thứ trở nên tồi tệ hơn khi dữ liệu quá lớn so với RAM, bởi vì khi đó, có một chỉ mục là rất tốt, nhưng việc quét (hoặc thậm chí chỉ đơn giản là trả lại) kết quả có thể yêu cầu tìm nạp nhiều dữ liệu từ đĩa một cách ngẫu nhiên, mất rất nhiều thời gian.

Cách tốt nhất để kiểm soát điều này là hạn chế số lượng các loại truy vấn khác nhau, không cho phép truy vấn thông tin có tính chọn lọc thấp và cố gắng ngăn truy cập ngẫu nhiên vào dữ liệu cũ.

Nếu vẫn thất bại và nếu bạn thực sự cần sự linh hoạt trong các bộ lọc, có thể đáng giá để xem xét một Cơ sở dữ liệu tìm kiếm riêng hỗ trợ các giao điểm chỉ mục, tìm nạp các id mongo từ đó và sau đó lấy kết quả từ mongo bằng cách sử dụng $in. Nhưng điều đó đầy rẫy những nguy cơ riêng của nó.

-- BIÊN TẬP --

Giải thích bạn đã đăng là một ví dụ tuyệt đẹp về sự cố khi quét các trường có độ chọn lọc thấp. Rõ ràng, có rất nhiều tài liệu cho "nickey@acme.com". Giờ đây, việc tìm kiếm những tài liệu đó và sắp xếp chúng giảm dần theo dấu thời gian khá nhanh chóng, bởi vì nó được hỗ trợ bởi các chỉ mục có tính chọn lọc cao. Thật không may, vì chỉ có hai loại thiết bị, mongo cần quét 30060 tài liệu để tìm loại đầu tiên phù hợp với 'di động'.

Tôi cho rằng đây là một loại theo dõi web nào đó và cách sử dụng của người dùng khiến truy vấn chậm (nếu anh ta chuyển đổi thiết bị di động và web hàng ngày, truy vấn sẽ nhanh).

Việc làm cho truy vấn cụ thể này nhanh hơn có thể được thực hiện bằng cách sử dụng chỉ mục kết hợp có chứa loại thiết bị, ví dụ: sử dụng

a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})

hoặc là

b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})

Thật không may, điều đó có nghĩa là các truy vấn như find({"username" : "foo"}).sort({"timestamp" : -1}); không thể sử dụng cùng một chỉ mục nữa , vì vậy, như đã mô tả, số lượng chỉ mục sẽ tăng lên rất nhanh.

Tôi e rằng không có giải pháp nào tốt cho việc này bằng cách sử dụng mongodb vào lúc này.


Cảm ơn vi đa trả lơi! Một vấn đề khác mà chúng tôi gặp phải là trên mongo của chúng tôi có một số cơ sở dữ liệu khách hàng trong đó mỗi cơ sở dữ liệu có một bộ sưu tập khổng lồ. Chúng tôi e rằng việc lập chỉ mục tất cả các bộ sưu tập này sẽ ảnh hưởng nghiêm trọng đến hiệu suất vì chúng tôi sẽ cần có dung lượng RAM lớn để hỗ trợ các truy vấn đồng thời từ những người dùng khác nhau. Bạn có đề xuất cho một cơ sở dữ liệu tìm kiếm tốt cho mục đích này không?
Yarin Miran

Tôi đoán điều đó phụ thuộc vào các tính năng tìm kiếm bạn cần. Về cơ bản, bất kỳ db nào hỗ trợ giao điểm chỉ mục đều nên làm. Nếu bạn cần tìm kiếm toàn văn, tìm kiếm theo từng khía cạnh hoặc thậm chí cắt và xúc xắc, mọi thứ sẽ trở nên phức tạp và có cả một vũ trụ công cụ, từ SolR, Tìm kiếm đàn hồi cho đến các khối OLAP. Khi đang ở đó, bạn cũng có thể bỏ phiếu cho giao điểm chỉ mục trong MongoDB Jira: jira.mongodb.org/browse/SERVER-3071
mnemosyn

Tôi nghĩ chúng ta sẽ sử dụng ElasticSearch cho bảng cụ thể này. Bạn nghĩ gì về nó ?
Yarin Miran,

2
Câu trả lời chính xác. Tôi muốn biết điều gì đã thay đổi trong 4,5 năm qua về vấn đề này.
Daniel Hilgarth

2

Mongo chỉ sử dụng 1 chỉ mục cho mỗi truy vấn. Vì vậy, nếu bạn muốn lọc trên 2 trường, mongo sẽ sử dụng chỉ mục với một trong các trường, nhưng vẫn cần quét toàn bộ tập con.

Điều này có nghĩa là về cơ bản bạn sẽ cần một chỉ mục cho mọi loại truy vấn để đạt được hiệu suất tốt nhất.

Tùy thuộc vào dữ liệu của bạn, có thể là một ý tưởng không tồi nếu có một truy vấn cho mỗi trường và xử lý kết quả trong ứng dụng của bạn. Bằng cách này, bạn sẽ chỉ cần chỉ mục trên mọi trường, nhưng có thể quá nhiều dữ liệu để xử lý.


-1

Nếu bạn đang sử dụng $ in, mongodb không bao giờ sử dụng INDEX. Thay đổi truy vấn của bạn, bằng cách xóa $ in này. Nó nên sử dụng chỉ mục và nó sẽ cho hiệu suất tốt hơn những gì bạn đã nhận trước đó.

http://docs.mongodb.org/manual/core/query-optimization/


15
FYI, $ in không sử dụng chỉ mục, $ nin không sử dụng chỉ mục. Vấn đề trong $ in từ những gì chúng tôi gặp phải là mongo thực hiện truy vấn cho mỗi giá trị trong $ in. Mặc dù sử dụng một chỉ mục cho mỗi truy vấn nhưng nó cực kỳ chậm ..
Yarin Miran
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.