使用桶模式分组数据
桶模式将长时间序列数据分离成独立的对象。将大型数据序列分解成更小的组可以提高查询访问模式并简化应用逻辑。当您有与中心实体(如单个用户进行的股票交易)相关的类似对象时,桶非常有用。
您可以使用桶模式进行分页,通过根据应用每页显示的元素来分组数据。此方法使用MongoDB灵活的数据模型,根据应用所需的数据来存储数据。
提示
时间序列集合 自动应用桶模式,适用于涉及桶式时间序列数据的大多数应用。
关于此任务
考虑以下跟踪股票交易的架构。初始架构没有使用桶模式,每个交易存储在一个单独的文档中。
db.trades.insertMany( [ { "ticker" : "MDB", "customerId": 123, "type" : "buy", "quantity" : 419, "date" : ISODate("2023-10-26T15:47:03.434Z") }, { "ticker" : "MDB", "customerId": 123, "type" : "sell", "quantity" : 29, "date" : ISODate("2023-10-30T09:32:57.765Z") }, { "ticker" : "GOOG", "customerId": 456, "type" : "buy", "quantity" : 50, "date" : ISODate("2023-10-31T11:16:02.120Z") } ] )
应用一次显示单个客户的股票交易,每页显示10个交易。为了简化应用逻辑,使用桶模式按10个一组对交易进行分组:customerId
步骤
按customerld分组数据
将模式重组,每个customerId
有一个单独的文档
{ "customerId": 123, "history": [ { "type": "buy", "ticker": "MDB", "qty": 419, "date": ISODate("2023-10-26T15:47:03.434Z") }, { "type": "sell", "ticker": "MDB", "qty": 29, "date": ISODate("2023-10-30T09:32:57.765Z") } ] }, { "customerId": 456, "history": [ { "type" : "buy", "ticker" : "GOOG", "quantity" : 50, "date" : ISODate("2023-10-31T11:16:02.120Z") } ] }
使用桶模式
具有相同
customerId
值的文档被压缩成一个单独的文档,其中customerId
是一个顶级字段。该客户的交易被分组到一个嵌套数组字段中,称为
history
。
为每个桶添加一个标识符和计数
1 db.trades.drop() 2 3 db.trades.insertMany( 4 [ 5 { 6 "_id": "123_1698349623", 7 "customerId": 123, 8 "count": 2, 9 "history": [ 10 { 11 "type": "buy", 12 "ticker": "MDB", 13 "qty": 419, 14 "date": ISODate("2023-10-26T15:47:03.434Z") 15 }, 16 { 17 "type": "sell", 18 "ticker": "MDB", 19 "qty": 29, 20 "date": ISODate("2023-10-30T09:32:57.765Z") 21 } 22 ] 23 }, 24 { 25 "_id": "456_1698765362", 26 "customerId": 456, 27 "count": 1, 28 "history": [ 29 { 30 "type" : "buy", 31 "ticker" : "GOOG", 32 "quantity" : 50, 33 "date" : ISODate("2023-10-31T11:16:02.120Z") 34 } 35 ] 36 }, 37 ] 38 )
_id
字段值是customerId
和history
字段中第一条交易的时间戳(自Unix纪元以来)的拼接。
count
字段指示该文档的history
数组中有多少个元素。count
字段用于实现分页逻辑。
下一步
在您将架构更新为使用桶模式后,更新您的应用程序逻辑以读取和写入数据。请参阅以下章节
使用桶模式查询数据
在更新后的架构中,每个文档包含应用程序中单个页面的数据。您可以使用 _id
和 count
字段来确定如何返回和更新数据。
要查询适当页面的数据,请使用正则表达式查询返回指定 customerId
的数据,并使用 skip
返回到正确页面的数据。在 _id
上的正则表达式查询使用 默认 _id 索引,这可以执行高效的查询,无需额外的索引。
以下查询返回客户 123
的第一页交易数据
db.trades.find( { "_id": /^123_/ } ).sort( { _id: 1 } ).limit(1)
要返回后续页面的数据,请指定一个比您要显示数据的页面少一的 skip
值。例如,要显示第 10 页的数据,请运行以下查询
db.trades.find( { "_id": /^123_/ } ).sort( { _id: 1 } ).skip(9).limit(1)
注意
前面的查询没有返回结果,因为示例数据只包含第一页的文档。
使用桶模式插入数据
现在架构使用桶模式,更新您的应用程序逻辑以将新交易插入正确的桶中。使用更新命令将交易插入具有适当 customerId
和桶的桶中。
以下命令插入客户 customerId: 123
的新交易
db.trades.updateOne( { "_id": /^123_/, "count": { $lt: 10 } }, { "$push": { "history": { "type": "buy", "ticker": "MSFT", "qty": 42, "date": ISODate("2023-11-02T11:43:10") } }, "$inc": { "count": 1 }, "$setOnInsert": { "_id": "123_1698939791", "customerId": 123 } }, { upsert: true } )
应用程序每页显示10笔交易。更新过滤器搜索具有customerId: 123
的文档,其中count
小于10,这意味着该桶不包含一整页的数据。
如果存在匹配
"_id": /^123_/
的文档且其count
小于10,更新命令将新交易推入匹配文档的history
数组。如果没有匹配的文档,更新命令将插入一个包含新交易的新文档(因为
upsert
为true
)。新文档的_id
字段是customerId
和交易自Unix纪元以来的时间(以秒为单位)的串联。
更新命令的逻辑通过确保没有history
数组包含超过10个文档来避免无界数组。
运行更新操作后,trades
集合具有以下文档
[ { _id: '123_1698349623', customerId: 123, count: 3, history: [ { type: 'buy', ticker: 'MDB', qty: 419, date: ISODate("2023-10-26T15:47:03.434Z") }, { type: 'sell', ticker: 'MDB', qty: 29, date: ISODate("2023-10-30T09:32:57.765Z") }, { type: 'buy', ticker: 'MSFT', qty: 42, date: ISODate("2023-11-02T11:43:10.000Z") } ] }, { _id: '456_1698765362', customerId: 456, count: 1, history: [ { type: 'buy', ticker: 'GOOG', quantity: 50, date: ISODate("2023-10-31T11:16:02.120Z") } ] } ]
结果
实现桶模式后,您不需要在应用程序中集成分页逻辑来返回结果。数据的存储方式与应用程序中使用的方式相匹配。