Nhóm chuỗi thời gian MongoDB

Cách sử dụng các bộ chứa để làm cho dữ liệu chuỗi thời gian của bạn hoạt động nhanh hơn và thông minh hơn trong bài viết Building with Patterns của tuần này trên blog MongoDB. https. //t. co/X4rWBMxJSj

— MongoDB (@MongoDB) ngày 31 tháng 1 năm 2019

Một chuỗi thời gian được thực hiện bằng các phép đo kín đáo trong các khoảng thời gian. Mẫu chuỗi thời gian là mẫu tối ưu hóa ghi được thực hiện để đảm bảo thông lượng hiệu suất ghi tối đa cho ứng dụng phân tích điển hình lưu trữ dữ liệu theo đơn vị thời gian riêng biệt. Ví dụ có thể bao gồm đếm số lượt xem trang trong một giây hoặc nhiệt độ mỗi phút. Đối với lược đồ này, chúng ta sẽ thảo luận về chuỗi thời gian trong ngữ cảnh của

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
6

Quan sát lược đồ

  • Lược đồ chuỗi thời gian dựa trên các bản cập nhật tại chỗ, hiệu quả, phù hợp với cách thức hoạt động của công cụ lưu trữ
    var col = db.getSisterDB("timeseries").pageViews;
    var secondInMinute = 2;
    var updateStatment = {$inc: {}};
    updateStatment["$inc"]["seconds." + secondInMinute] = 1;
    
    col.update({
      page: "/index.htm",
      timestamp: ISODate("2014-01-01T10:01:00Z")
    }, updateStatment, true)
    
    7. Tuy nhiên, điều này không hiệu quả khi sử dụng công cụ lưu trữ
    var col = db.getSisterDB("timeseries").pageViews;
    var secondInMinute = 2;
    var updateStatment = {$inc: {}};
    updateStatment["$inc"]["seconds." + secondInMinute] = 1;
    
    col.update({
      page: "/index.htm",
      timestamp: ISODate("2014-01-01T10:01:00Z")
    }, updateStatment, true)
    
    0 do thiếu hỗ trợ cập nhật tại chỗ

Lược đồ

Thuộc tính lược đồ Mô tả được tối ưu hóa cho hiệu suất ghi Preallocation Lợi ích từ Preallocation trên MMAP

Để tối đa hóa thông lượng ghi của chúng tôi trong một chuỗi thời gian, chúng tôi đang đưa ra các giả định rằng chúng tôi quan tâm đến các nhóm thời gian kín đáo. Điều đó có nghĩa là, bản thân một lần xem trang riêng lẻ không hấp dẫn đối với ứng dụng. Chỉ số lượt xem trang, trong một giây, phút, giờ, ngày cụ thể hoặc trong một phạm vi ngày và giờ mới được quan tâm. Điều này có nghĩa là đơn vị thời gian nhỏ nhất mà chúng tôi muốn cho ví dụ này, là một phút

Tính đến điều đó, hãy lập mô hình một

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
1 để giữ tất cả các lượt xem trang của chúng ta trong một phút cụ thể

{
  "page": "/index.htm",
  "timestamp": ISODate("2014-01-01T10:01:00Z"),
  "totalViews": 0,
  "seconds": {
    "0": 0
  }
}

ví dụ 1. Một chuỗi thời gian xô

Chia nhỏ cánh đồng

Schema AttributesDescriptionpageTrang web chúng tôi đang đo lường dấu thời gian Số phút thực tế mà bộ chứa là fortotalViewsTổng số lần xem trang trong phút này giâySố lần xem trang cho một giây cụ thể trong phút

Tài liệu

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
1 không chỉ thể hiện toàn bộ số lượt xem trang trong một phút cụ thể mà còn chứa phân tích về số lượt xem trang mỗi giây trong phút đó

hoạt động

Cập nhật số lần xem trang trong một nhóm

Hãy mô phỏng những gì xảy ra trong một ứng dụng đang đếm lượt xem trang cho một trang cụ thể. Chúng tôi sẽ mô phỏng cập nhật một nhóm, cho một lần xem trang cụ thể trong giây thứ 2 của nhóm

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
3

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)

ví dụ 2. Cập nhật một thùng

Phần đầu tiên của

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
4 thiết lập giá trị
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
5 để tăng trường trong trường
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
6 có tên là
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
7, tương ứng với giây đã trôi qua thứ cấp trong khoảng thời gian nhóm của chúng tôi

Nếu trường không tồn tại MongoDB, sẽ đặt nó thành một. Nếu không, nó sẽ tăng giá trị hiện tại với một. Lưu ý tham số cuối cùng của câu lệnh cập nhật. Điều này yêu cầu MongoDB thực hiện một

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
8 hướng dẫn MongoDB tạo một tài liệu mới nếu không có tài liệu nào phù hợp với bộ chọn
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
9

Truy xuất một Bucket cụ thể

Nếu chúng tôi muốn truy xuất một nhóm đo thời gian cụ thể trong một phút cụ thể, chúng tôi có thể truy xuất nó rất dễ dàng bằng cách sử dụng trường

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
20 như được hiển thị bên dưới

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
2

ví dụ 3. Truy xuất một nhóm cụ thể

Thao tác này sẽ truy xuất nhóm mà

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
20 khớp với nhóm thời gian
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
3

Nhóm đo lường phân bổ trước

Để cải thiện hiệu suất khi ghi, chúng ta có thể phân bổ trước các nhóm để tránh phải di chuyển tài liệu trong bộ nhớ và trên đĩa. Mỗi tài liệu nhóm có kích thước cuối cùng cố định đã biết. Nếu chúng tôi sử dụng một mẫu để tạo các thùng trống, chúng tôi có thể tận dụng các bản cập nhật tại chỗ, giảm thiểu dung lượng đĩa IO cần thiết để thu thập

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
23

Hãy xem cách chúng ta có thể phân bổ trước các nhóm cho cả giờ đo lường. Hàm ví dụ bên dưới

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
24, lấy một bộ sưu tập, tên trang web và dấu thời gian biểu thị một giờ cụ thể

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
8

Ví dụ 4. Phân bổ trước các nhóm cho một giờ cụ thể

Hãy sử dụng phương pháp phân bổ trước này để chạy thử bằng cách phân bổ trước các thùng trị giá một giờ

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
9

Ví dụ 5. Gọi phương thức preAllocate cho một giờ cụ thể

Hãy xác minh rằng việc phân bổ trước các nhóm diễn ra chính xác bằng cách đếm số mục nhập nhóm được tạo cho một giờ cụ thể

{
  "page": "/index.htm",
  "timestamp": ISODate("2014-01-01T10:01:00Z"),
  "totalViews": 0,
  "seconds": {
    "0": 0
  }
}
0

Ví dụ 6. Truy xuất số lượng tài liệu được tạo bởi phương thức preAllocateHour

Như chúng tôi mong đợi, số lượng được trả về là

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
25 mục nhập

chỉ mục

Vì chúng tôi sẽ truy xuất các nhóm dấu thời gian theo tên trang và dấu thời gian của chúng, nên các chỉ mục cần thiết duy nhất cho tính hiệu quả là trên các trường

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
26 và
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
20

{
  "page": "/index.htm",
  "timestamp": ISODate("2014-01-01T10:01:00Z"),
  "totalViews": 0,
  "seconds": {
    "0": 0
  }
}
4

Ví dụ 7. Tạo chỉ mục dấu thời gian

Điều này sẽ đảm bảo mọi truy vấn phạm vi trên trường

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
20 sẽ có thể tận dụng chỉ mục để có hiệu suất truy vấn tốt hơn

mở rộng quy mô

Đọc phụ

Các lần đọc thứ cấp có thể hữu ích khi báo cáo về dữ liệu, vì bất kỳ báo cáo chạy dài nào về chúng sẽ gây ra tác động tối thiểu đến thông lượng ghi

sharding

Việc chọn một khóa phân đoạn cho một chuỗi thời gian sẽ ảnh hưởng đến cách ghi và đọc dữ liệu

Trong trường hợp ví dụ phân tích trang web, chúng tôi muốn tóm tắt dữ liệu theo trang. Nếu việc ghi cho một trang web cụ thể được trải ra giữa tất cả các phân đoạn, chúng tôi yêu cầu các truy vấn phân tán/thu thập để tóm tắt chính xác dữ liệu trong một khoảng thời gian cụ thể

Chúng tôi muốn một khóa phân đoạn sẽ nhóm tất cả các phép đo cho một trang web cụ thể trên một phân đoạn cụ thể nhưng sẽ trải rộng các trang trên tất cả các phân đoạn để tối đa hóa thông lượng ghi

Chúng tôi có thể đạt được điều này bằng cách tạo một khóa phân đoạn phức hợp chứa tên trang web cũng như ngày tháng.

{
  "page": "/index.htm",
  "timestamp": ISODate("2014-01-01T10:01:00Z"),
  "totalViews": 0,
  "seconds": {
    "0": 0
  }
}
6

Ví dụ 8. Chìa khóa phân mảnh hợp chất

Lợi ích của việc sử dụng khóa phân đoạn này là chúng tôi vẫn có thể tổng hợp tất cả các giá trị cho một trang cụ thể trên một phân đoạn mà không cần thực hiện truy vấn thu thập phân tán

Hiệu suất

Khám phá đơn giản về hiệu suất trên một máy duy nhất với MongoDb 3. 0 cho thấy sự khác biệt giữa

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
7 và
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
0 đối với mô phỏng hẹp sử dụng khung mô phỏng lược đồ
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
81

Kịch bản

https. //github. com/christkv/mongodb-schema-simulator/blob/master/examples/scripts/single_or_replset/timeseries/exercise_time_series. js

MongoDb chạy cục bộ trên MacBook Pro Retina 2015 với ssd và 16 gb ram. Mô phỏng chạy với các tham số sau đối với một phiên bản

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
82 duy nhất trong
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
83

Tham sốGiá trịquy trình4nhómKích thước mỗi quy trình50loạituyến tínhĐộ phân giải tính bằng mili giây1000Số lần chạy25Số người dùng cập nhật chuỗi thời gian mỗi lần lặp1000Chiến lược thực thilát thời gian

MMAP

Công cụ

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
7 được chạy bằng cài đặt mặc định trên
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
85

Nhóm chuỗi thời gian MongoDB

kết quả kịch bản chuỗi thời gian

StatisticsValueRuntime30. 253 giâyTrung bình1. 06 mili giây Độ lệch chuẩn1. 588 mili giây75 phần trăm1. 246 mili giây95 phần trăm1. 502 mili giây99 phần trăm1. 815 mili giâyMinimum0. 448 mili giâyMaximum57. 48 mili giây

Lưu ý rằng

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
86 người dùng mỗi giây ảnh hưởng khá nhiều đến mức tối thiểu và tối đa cũng như thời gian truy vấn trung bình

Có DâyHổ

Công cụ

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
0 được chạy bằng cài đặt mặc định trên
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
85

Nhóm chuỗi thời gian MongoDB

kết quả kịch bản siêu dữ liệu

StatisticsValueRuntime30. 08 giâyMean1. 108 mili giây Độ lệch chuẩn0. 401 mili giây75 phần trăm1. 341 mili giây95 phần trăm1. 871 mili giây99 phần trăm2. 477 mili giâyMinimum0. 513 mili giâyMaximum5. 481 mili giây

Như mong đợi, không có nhiều sự khác biệt giữa công cụ lưu trữ

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
7 và
var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
0 khi đó là khối lượng công việc chỉ đọc

ghi chú

Tài liệu được phân bổ trước giúp MongoDB giảm thiểu việc di chuyển tài liệu trong bộ nhớ, giảm IO của đĩa và phân mảnh thấp hơn trên đĩa và trong bộ nhớ. Điều này đặc biệt đúng đối với công cụ lưu trữ

var col = db.getSisterDB("timeseries").pageViews;
var secondInMinute = 2;
var updateStatment = {$inc: {}};
updateStatment["$inc"]["seconds." + secondInMinute] = 1;

col.update({
  page: "/index.htm",
  timestamp: ISODate("2014-01-01T10:01:00Z")
}, updateStatment, true)
7

Làm cách nào để lưu trữ thời gian trong MongoDB?

MongoDB lưu trữ thời gian ở dạng UTC theo mặc định và sẽ chuyển đổi mọi biểu diễn giờ địa phương thành dạng này. Các ứng dụng phải vận hành hoặc báo cáo về một số giá trị thời gian địa phương chưa sửa đổi có thể lưu trữ múi giờ cùng với dấu thời gian UTC và tính toán thời gian địa phương ban đầu trong logic ứng dụng của chúng.

Bộ sưu tập chuỗi thời gian trong MongoDB là gì?

MongoDB coi các bộ sưu tập chuỗi thời gian là các chế độ xem không cụ thể hóa có thể ghi được hỗ trợ bởi một bộ sưu tập nội bộ . Khi bạn chèn dữ liệu, bộ sưu tập nội bộ sẽ tự động sắp xếp dữ liệu chuỗi thời gian thành định dạng lưu trữ được tối ưu hóa. Khi bạn truy vấn các bộ sưu tập chuỗi thời gian, bạn thao tác trên một tài liệu cho mỗi phép đo.

MongoDB có tốt cho dữ liệu chuỗi thời gian không?

MongoDB là cơ sở dữ liệu có mục đích chung dựa trên tài liệu với thiết kế lược đồ linh hoạt và ngôn ngữ truy vấn phong phú. Kể từ MongoDB 5. 0, MongoDB vốn hỗ trợ dữ liệu chuỗi thời gian .

Xô trong MongoDB là gì?

Định nghĩa. $bucket. Phân loại tài liệu đến thành các nhóm, được gọi là nhóm, dựa trên một biểu thức và ranh giới nhóm đã chỉ định, đồng thời xuất một tài liệu cho mỗi nhóm . Mỗi tài liệu đầu ra chứa một trường _id có giá trị chỉ định giới hạn dưới bao gồm của nhóm.