查询嵌套数组性能,Mongo vs ElasticSearch

Query nested array performance, Mongo vs ElasticSearch

我有一个 music app 可以根据标签 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.