您的位置:首页 > 移动开发 > Objective-C

elasticsearch之modeling your data(not flat)--nested objects

2014-11-13 10:06 281 查看
我们常会有这样的应用场景:把跟某一个实体相关的实体存储在一个document中。比如我们会存储博客与之相关的评论。

PUT /my_index/blogpost/1
{
  "title": "Nest eggs",
  "body":  "Making your money work...",
  "tags":  [ "cash", "shares" ],
  "comments": [ 
    {
      "name":    "John Smith",
      "comment": "Great article",
      "age":     28,
      "stars":   4,
      "date":    "2014-09-01"
    },
    {
      "name":    "Alice White",
      "comment": "More like this please",
      "age":     31,
      "stars":   5,
      "date":    "2014-10-22"
    }
  ]
}

如果我们依赖动态mapping,comments字段将会作为一个object创建。

因为所有关于一个博客的所有评论都存储在一个document中,所以查询过程中就可以很快速的检索。但是问题来了,以下查询仍然会成立:

GET /_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "Alice" }},
        { "match": { "age":  28      }} 
      ]
    }
  }
}

这显然不是我们想要的结果。为什么会出现这种情形?

原因在于:我们结构化的json数据在es中被扁平化存储了,如下:

{
  "title":            [ eggs, nest ],
  "body":             [ making, money, work, your ],
  "tags":             [ cash, shares ],
  "comments.name":    [ alice, john, smith, white ],
  "comments.comment": [ article, great, like, more, please, this ],
  "comments.age":     [ 28, 31 ],
  "comments.stars":   [ 4, 5 ],
  "comments.date":    [ 2014-09-01, 2014-10-22 ]
}

这种方式下,同一个实体之间的关系丢失了,比如“Alice”跟31.

object类型对于存储单一实体是有效的,但是在数组情况下,就无效了,会丧失实体内的关系。

这就是es提供nested objects的目的,来解决以上问题。通过mapping将comments字段设置为nested,每一个nested object就会在索引中作为单一的document存储,如下:

{ 
  "comments.name":    [ john, smith ],
  "comments.comment": [ article, great ],
  "comments.age":     [ 28 ],
  "comments.stars":   [ 4 ],
  "comments.date":    [ 2014-09-01 ]
}
{ 
  "comments.name":    [ alice, white ],
  "comments.comment": [ like, more, please, this ],
  "comments.age":     [ 31 ],
  "comments.stars":   [ 5 ],
  "comments.date":    [ 2014-10-22 ]
}
{ 
  "title":            [ eggs, nest ],
  "body":             [ making, money, work, your ],
  "tags":             [ cash, shares ]
}

通过把没一个nested object单独存储(nested 跟 root 处在同一个level上),其fields保持了之间的关系。因此以上查询过程中出现的问题就解决了。不仅如此,由于采用了这样的存储方式,nested object跟root之间的连接查询也变得非常高效,就像是在同一个document中一样。

这种nested document是隐藏存储的,我们不能直接访问。更新、添加或者删除一个nested object,必须reindex整个document。查询结果也不仅仅是返回nested object,而是返回整个document。

1:nested object mapping

设置非常简单:

PUT /my_index
{
  "mappings": {
    "blogpost": {
      "properties": {
        "comments": {
          "type": "nested", 
          "properties": {
            "name":    { "type": "string"  },
            "comment": { "type": "string"  },
            "age":     { "type": "short"   },
            "stars":   { "type": "short"   },
            "date":    { "type": "date"    }
          }
        }
      }
    }
  }
}

2:query a nested object

因为nested objet 在索引中是作为隐藏文档的形式存在的,所以我们不能直接query,而应该利用nested query 或者 nested filter去访问:

GET /my_index/blogpost/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "eggs" }}, 
        {
          "nested": {
            "path": "comments", 
            "query": {
              "bool": {
                "must": [ 
                  { "match": { "comments.name": "john" }},
                  { "match": { "comments.age":  28     }}
                ]
        }}}}
      ]
}}}

当然,一个nested query会去匹配多个nested object,每一个match都会产生一个score值,但是这些score值需要统一成一个score跟root级别的query一起使用。默认是采用average的方式,es也提供了score_mode:avg,max,min,node。

GET /my_index/blogpost/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "eggs" }},
        {
          "nested": {
            "path":       "comments",
            "score_mode": "max", 
            "query": {
              "bool": {
                "must": [
                  { "match": { "comments.name": "john" }},
                  { "match": { "comments.age":  28     }}
                ]
        }}}}
      ]
}}}

3:sorting by nested fields

即使value值存在于一个单独的nested document中,针对某一个nested field进行排序也是可以的。

假设我们想要得到在十月份收到评论的博客,按照这些博客评论中最小的star值进行排序(我们先看下这个查询:十月份收到评论的博客中,每一个博客都有多条评论,因此排序根据这些评论中的最小值进行也就不足为奇了,但是我们的检索结果只是想得到这些博客而已,而不是评论。)

GET /_search
{
  "query": {
    "nested": { 


      "path": "comments",
      "filter": {
        "range": {
          "comments.date": {
            "gte": "2014-10-01",
            "lt":  "2014-11-01"
          }
        }
      }
    }
  },
  "sort": {
    "comments.stars": { 


      "order": "asc",   


      "mode":  "min",   


      "nested_filter": { 


        "range": {
          "comments.date": {
            "gte": "2014-10-01",
            "lt":  "2014-11-01"
          }
        }
      }
    }
  }
}

query部分不难理解,sort中为啥还要嵌套一个跟query一样的查询呢???

首先sort是针对query的结果进行排序的,但是query的返回结果是博客信息(query筛选了一部分博客信息,这些博客信息存在10月份的评论,但并不意味着这一篇博客不存在其他月份的评论,而这些信息都是跟随query信息一起返回的,而如果一篇博客有10篇评论而都不在10月份,则这个结果不会返回),如果不执行一次筛选的话,排序的依据就可能不仅仅是10月份的的博客信息了。

4:nested aggregations

如同我们可以用nested query去访问nested object一样,我们也可以用nested aggregation对nested object中的fields进行操作。

GET /my_index/blogpost/_search?search_type=count
{
  "aggs": {
    "comments": { 
      "nested": {
        "path": "comments"
      },
      "aggs": {
        "by_month": {
          "date_histogram": { 
            "field":    "comments.date",
            "interval": "month",
            "format":   "yyyy-MM"
          },
          "aggs": {
            "avg_stars": {
              "avg": { 
                "field": "comments.stars"
              }
            }
          }
        }
      }
    }
  }
}

上面的aggregation先按照comments.date字段进行bucket,然后每一个bucket根据comment.stars字段进行avg。

nested aggregation只能访问nested document中的field,不能访问root cocument 或者 不同的nested document。然而使用reverse_nested aggregation可以跳出nested scope,去访问parent level的数据.

比如:我们想要得到发表评论的人感兴趣的tags,以评论者的年龄区分。这里comment.age是一个nested field,而tags是一个root document。

GET /my_index/blogpost/_search?search_type=count
{
  "aggs": {
    "comments": {
      "nested": { 
        "path": "comments"
      },
      "aggs": {
        "age_group": {
          "histogram": { 
            "field":    "comments.age",
            "interval": 10
          },
          "aggs": {
            "blogposts": {
              "reverse_nested": {}, 
              "aggs": {
                "tags": {
                  "terms": { 
                    "field": "tags"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
以上:histogtam agg用comment.age进行bucket,然后reverse_nested aggs到了root document,然后term aggs对上边的没一个bucket中的数据根据tags进行buckent统计。
总结:什么时候使用nested objects呢?

Nested objects适用与一个主实体(blog),关联几个有限数目的不太重要的实体(comment)的情况。利于根据次要实体的内容查找符合条件的主实体,并且nested query和filter提供了快速的query-time join性能。

nested model 的不足之处在于:

添加,更新,删除一个nested document,整个document必须reindexed。nested object越多,代价越高。

查询请求返回完整的document,并不仅仅是match到的nested objects(目前es这块有打算去做,但是现在还是不支持)。

有些应用场景下你可能需要在main document和associated entity做完全的分离,这种分离由parent-child relationship提供。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: