查询嵌套数组性能,Mongo vs ElasticSearch
Query nested array performance, Mongo vs ElasticSearch
我有一个 music app 可以根据标签 ID 查找音乐推荐。
涉及两个实体:
Song
- 歌曲记录包含其名称和这首歌所属的音乐标签 ID(流派)列表
MusicTag
- 音乐标签本身,包括id、名称等
数据当前存储在 MongoDB 中。
mongo中的Songs
个合集有数百万首歌曲,每首歌曲平均有7个tag id。
MusicTags
有大约 30K 条记录。
Songs
集合看起来像这样:
[
{
name: "Metallica - one",
tags: [
"6018703624d8a5e8efa1b76e", // Rock
"601861cc8cef62ba86765017", // Heavy metal
"5fda07ac8db0615c1c503a46" // Hard Rock
]
},
{
name: "Metallica - unforgiven",
tags: [
"6018703624d8a5e8efa1b76e", // Rock
"5fda07ac8db0615c1c503a46", // Metal
]
},
{
name: "Lady Gaga - Bad Romance",
tags: [
"5fc7b9f95e38e17282896b64", // Pop
"5fc729be5e38e17282844eff", // Dance
]
}
]
给定标签 "6018703624d8a5e8efa1b76e"
(Rock
),我想查询 Songs
集合并找到所有在其 [=22= 中具有 Rock
标签的歌曲]数组。
在 Mongo 这是我正在做的查询:
db.songs.find({ tags: { $in: [ObjectId("6018703624d8a5e8efa1b76e")] }});
它的性能非常糟糕(在 10 到 40 秒之间,并且随着集合的增长而变得最差),我尝试以各种方式索引 Mongo(table 包含更多搜索中涉及的数据,例如分数和持续时间,但目前不相关)但我的查询仍然花费太长时间,我无法解释(我阅读了很多官方和非官方的东西)但我有感觉以这种嵌套形式保存数据会使索引变得毫无价值,并且每次仍然以某种方式对 table 进行全面扫描 - 但我无法证明这一点(Mongo“解释”不是真的向我解释了一些事情 :) )
我正在考虑为其使用 ElasticSearch,同步所有歌曲数据,并查询它而不是 Mongo 将保留为数据 SSOT 和其他轻量级操作。
但是问题仍然悬而未决,我想确定:在 Elastic 中,我可以以那种形式保存数据(歌曲中的嵌套数组),或者我需要以不同的方式表示它(例如,将其扁平化,这样每条记录都会是 song_tag 索引等?
谢谢。
Elasticsearch 不提供 dedicated array type so what you'd typically do is define the mapping based on the type of the individual array items -- in your case a keyword
:
PUT songs
{
"mappings": {
"properties": {
"tags": {
"type": "keyword"
}
}
}
}
然后您将索引文档:
POST songs/_doc
{
"name": "Metallica - one",
"tags": [
"6018703624d8a5e8efa1b76e",
"601861cc8cef62ba86765017",
"5fda07ac8db0615c1c503a46"
]
}
并查询 tags
:
POST songs/_search
{
"query": {
"bool": {
"must": [
{ ... other queries },
{
"terms": {
"tags": [
"6018703624d8a5e8efa1b76e" // one or more
]
}
}
]
}
}
}
标签是唯一的关键字,但不是人类可读的,因此您需要将它们与实际流派的映射保存在某处。由于流派可能设置一次并且很少(如果有的话)更新,因此您也可以使用 nested
fields。但是您的标签将变成键值对数组:
POST songs/_doc
{
"name": "Metallica - one",
"tags": [
{
"tag": "6018703624d8a5e8efa1b76e",
"genre": "Rock"
}
...
]
}
映射会略有不同,查询也会略有不同,但现在您不需要翻译映射,而且您可以通过人类可读的值进行查询或聚合 -- tags.genre
.
我有一个 music app 可以根据标签 ID 查找音乐推荐。
涉及两个实体:
Song
- 歌曲记录包含其名称和这首歌所属的音乐标签 ID(流派)列表MusicTag
- 音乐标签本身,包括id、名称等
数据当前存储在 MongoDB 中。
mongo中的Songs
个合集有数百万首歌曲,每首歌曲平均有7个tag id。
MusicTags
有大约 30K 条记录。
Songs
集合看起来像这样:
[
{
name: "Metallica - one",
tags: [
"6018703624d8a5e8efa1b76e", // Rock
"601861cc8cef62ba86765017", // Heavy metal
"5fda07ac8db0615c1c503a46" // Hard Rock
]
},
{
name: "Metallica - unforgiven",
tags: [
"6018703624d8a5e8efa1b76e", // Rock
"5fda07ac8db0615c1c503a46", // Metal
]
},
{
name: "Lady Gaga - Bad Romance",
tags: [
"5fc7b9f95e38e17282896b64", // Pop
"5fc729be5e38e17282844eff", // Dance
]
}
]
给定标签 "6018703624d8a5e8efa1b76e"
(Rock
),我想查询 Songs
集合并找到所有在其 [=22= 中具有 Rock
标签的歌曲]数组。
在 Mongo 这是我正在做的查询:
db.songs.find({ tags: { $in: [ObjectId("6018703624d8a5e8efa1b76e")] }});
它的性能非常糟糕(在 10 到 40 秒之间,并且随着集合的增长而变得最差),我尝试以各种方式索引 Mongo(table 包含更多搜索中涉及的数据,例如分数和持续时间,但目前不相关)但我的查询仍然花费太长时间,我无法解释(我阅读了很多官方和非官方的东西)但我有感觉以这种嵌套形式保存数据会使索引变得毫无价值,并且每次仍然以某种方式对 table 进行全面扫描 - 但我无法证明这一点(Mongo“解释”不是真的向我解释了一些事情 :) )
我正在考虑为其使用 ElasticSearch,同步所有歌曲数据,并查询它而不是 Mongo 将保留为数据 SSOT 和其他轻量级操作。
但是问题仍然悬而未决,我想确定:在 Elastic 中,我可以以那种形式保存数据(歌曲中的嵌套数组),或者我需要以不同的方式表示它(例如,将其扁平化,这样每条记录都会是 song_tag 索引等?
谢谢。
Elasticsearch 不提供 dedicated array type so what you'd typically do is define the mapping based on the type of the individual array items -- in your case a keyword
:
PUT songs
{
"mappings": {
"properties": {
"tags": {
"type": "keyword"
}
}
}
}
然后您将索引文档:
POST songs/_doc
{
"name": "Metallica - one",
"tags": [
"6018703624d8a5e8efa1b76e",
"601861cc8cef62ba86765017",
"5fda07ac8db0615c1c503a46"
]
}
并查询 tags
:
POST songs/_search
{
"query": {
"bool": {
"must": [
{ ... other queries },
{
"terms": {
"tags": [
"6018703624d8a5e8efa1b76e" // one or more
]
}
}
]
}
}
}
标签是唯一的关键字,但不是人类可读的,因此您需要将它们与实际流派的映射保存在某处。由于流派可能设置一次并且很少(如果有的话)更新,因此您也可以使用 nested
fields。但是您的标签将变成键值对数组:
POST songs/_doc
{
"name": "Metallica - one",
"tags": [
{
"tag": "6018703624d8a5e8efa1b76e",
"genre": "Rock"
}
...
]
}
映射会略有不同,查询也会略有不同,但现在您不需要翻译映射,而且您可以通过人类可读的值进行查询或聚合 -- tags.genre
.