Elasticsearch nested query : nested에 should와 must를 함께 쓰는 경우 inner_hit 안맞는 이슈

brody·2020년 5월 19일
1

엘라스틱서치

목록 보기
3/5

이번에 개발하다가 이상한 점을 발견했다.

기존 should로만 구성된 nested query에 필수 조건이 붙어 must를 추가로 넣었는데, 쿼리는 잘 되는 듯 보였으나, inner_hit이 제대로 나오지 않는 현상이다. 쿼리의 결과는 제대로 나오고 있어서 제대로 되고있다고 착각했다.

아래 예시 데이터로 설명하겠다.

  • mapping

    blogs라는 인덱스에 post doc이 있고, 그 안에는 nested로 해당 blog가 누구에게 공유되었는지(sharedTo)와 언제까지 공유가 유효한지(expiration)을 가지고 있다. authority는 해당 post에대한 권한을 의미한다.

PUT blogs
{
  "mappings": {
    "_doc": {
      "properties": {
        "post": {
          "properties": {
            "postId": {
              "type": "keyword"
            },
            "contents": {
              "type": "text"
            }
          }
        },
        "sharedPeople": {
            "type" : "nested",
          "properties": {
            "expiration": {
              "type": "date"
            },
            "authority": {
              "type": "keyword"
            },
            "sharedTo":{
              "type":"keyword"
            }
          }
        }
      }
    }
  }
}
  • doc 2개 추가

    1번 doc은 A,B,C에게 공유되었고

    2번 doc은 B,C에게 공유되었다.

    PUT blogs/_doc/1
    {
      "post": {
        "postId": 1,
        "contents": "hello"
      },
      "sharedPeople": [
        {
          "exiration": "2021-05-20",
          "authority": "1",
          "sharedTo": "A"
        },
            {
          "exiration": "2021-05-20",
          "authority": "2",
          "sharedTo": "B"
        }
        ,
        {
          "exiration": "2021-05-20",
          "authority": "3",
          "sharedTo": "C"
        }
      ]
    }
    
    PUT blogs/_doc/2
    {
      "post": {
        "postId": 2,
        "contents": "world"
      },
      "sharedPeople": [
            {
          "exiration": "2021-05-20",
          "authority": "2",
          "sharedTo": "B"
        }
        ,
        {
          "exiration": "2021-05-20",
          "authority": "3",
          "sharedTo": "C"
        }
      ]
    }
    
  • A가 공유받은 문서를 가져오는 쿼리

    여러번 공유되었을 수도 있기 때문에, 쿼리에서는 공유 권한(authority)순으로 정렬해서 가장 큰 값을 inner_hit으로 받아온다.

    GET blogs/_search
    {
      "query": {
        "bool": {
          "filter": {
            "nested": {
              "path": "sharedPeople",
              "query": {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "terms": {
                              "sharedPeople.sharedTo": [
                                "A"
                              ]
                            }
                          }
                        ]
                      }
                    }
                  ]
                }
              },
              "inner_hits": {
                "name": "sharedInfo",
                "from": 0,
                "size": 1,
                "sort": [
                  {
                    "sharedPeople.authority": {
                      "order": "desc"
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
      "hits" : {
        "total" : 1,
        "max_score" : 0.0,
        "hits" : [
          {
            "_index" : "blogs",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.0,
            "_source" : {
              "post" : {
                "postId" : 1,
                "contents" : "hello"
              },
              "sharedPeople" : [
                {
                  "exiration" : "2021-05-20",
                  "authority" : "1",
                  "sharedTo" : "A"
                },
                {
                  "exiration" : "2021-05-20",
                  "authority" : "2",
                  "sharedTo" : "B"
                },
                {
                  "exiration" : "2021-05-20",
                  "authority" : "3",
                  "sharedTo" : "C"
                }
              ]
            },
            "inner_hits" : {
              "sharedInfo" : {
                "hits" : {
                  "total" : 1,
                  "max_score" : null,
                  "hits" : [
                    {
                      "_index" : "blogs",
                      "_type" : "_doc",
                      "_id" : "1",
                      "_nested" : {
                        "field" : "sharedPeople",
                        "offset" : 0
                      },
                      "_score" : null,
                      "_source" : {
                        "exiration" : "2021-05-20",
                        "authority" : "1",
                        "sharedTo" : "A"
                      },
                      "sort" : [
                        "1"
                      ]
                    }
                  ]
                }
              }
            }
          }
        ]
      }
  • 여기에 공유기간을 추가조건으로 must로 넣었을때

    GET blogs/_search
    {
      "query": {
        "bool": {
          "filter": {
            "nested": {
              "path": "sharedPeople",
              "query": {
                "bool": {
                  "must": [
                    {
                      "range": {
                        "sharedPeople.exiration": {
                          "from": "2020-05-20",
                          "to": null,
                          "include_lower": false,
                          "include_upper": true
                        }
                      }
                    }
                  ],
                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "terms": {
                              "sharedPeople.sharedTo": [
                                "A"
                              ]
                            }
                          }
                        ]
                      }
                    }
                  ]
                }
              },
              "inner_hits": {
                "name": "sharedInfo",
                "from": 0,
                "size": 1,
                "sort": [
                  {
                    "sharedPeople.authority": {
                      "order": "desc"
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
      "hits" : {
        "total" : 1,
        "max_score" : 0.0,
        "hits" : [
          {
            "_index" : "blogs",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.0,
            "_source" : {
              "post" : {
                "postId" : 1,
                "contents" : "hello"
              },
              "sharedPeople" : [
                {
                  "exiration" : "2021-05-20",
                  "authority" : "1",
                  "sharedTo" : "A"
                },
                {
                  "exiration" : "2021-05-20",
                  "authority" : "2",
                  "sharedTo" : "B"
                },
                {
                  "exiration" : "2021-05-20",
                  "authority" : "3",
                  "sharedTo" : "C"
                }
              ]
            },
            "inner_hits" : {
              "sharedInfo" : {
                "hits" : {
                  "total" : 3,
                  "max_score" : null,
                  "hits" : [
                    {
                      "_index" : "blogs",
                      "_type" : "_doc",
                      "_id" : "1",
                      "_nested" : {
                        "field" : "sharedPeople",
                        "offset" : 2
                      },
                      "_score" : null,
                      "_source" : {
                        "exiration" : "2021-05-20",
                        "authority" : "3",
                        "sharedTo" : "C"
                      },
                      "sort" : [
                        "3"
                      ]
                    }
                  ]
                }
              }
            }
          }
        ]
      }

    위와같이 결과중에 inner_hit에 C의 권한이 나온다.

    inner_hit에서 쿼리 중 아래의 should권한이 무시된 것이다.

                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "terms": {
                              "sharedPeople.sharedTo": [
                                "A"
                              ]
                            }
                          }
                        ]
                      }
                    }
                  ]
  • 문제는 무시되었다면 쿼리의 결과에 2번문서, 즉 A에게 공유되지 않은 문서도 나왔어야하는데, 실제 쿼리의 결과에서는 1번만 나오지만 inner_hit에서 A의 권한이 아닌 C의권한이 나오고있다.
  • ES의 filter context에서 should안의 minimum_should_match는 기본으로 1인데, (version 7.0이하에서) 이것이 inner_hit에서는 적용되지 않고 있었다. 실제로 6.8에서 위 쿼리를 날리면, 앞으로 없어진다는 아래와같은 경고가 나온다

#! Deprecation: Should clauses in the filter context will no longer automatically set the minimum should match to 1 in the next major version. You should group them in a [filter] clause or explicitly set [minimum_should_match] to 1 to restore this behavior in the next major version.

  • 아래와같이 minimun_should_match를 명시해보자

    GET blogs/_search
    {
      "query": {
        "bool": {
          "filter": {
            "nested": {
              "path": "sharedPeople",
              "query": {
                "bool": {
                  "must": [
                    {
                      "range": {
                        "sharedPeople.exiration": {
                          "from": "2020-05-20",
                          "to": null,
                          "include_lower": false,
                          "include_upper": true
                        }
                      }
                    }
                  ],
                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "terms": {
                              "sharedPeople.sharedTo": [
                                "A"
                              ]
                            }
                          }
                        ]
                      }
                    }
                    
                  ],
                  "minimum_should_match" : 1
                }
              },
              "inner_hits": {
                "name": "sharedInfo",
                "from": 0,
                "size": 1,
                "sort": [
                  {
                    "sharedPeople.authority": {
                      "order": "desc"
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
      "hits" : {
        "total" : 1,
        "max_score" : 0.0,
        "hits" : [
          {
            "_index" : "blogs",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.0,
            "_source" : {
              "post" : {
                "postId" : 1,
                "contents" : "hello"
              },
              "sharedPeople" : [
                {
                  "exiration" : "2021-05-20",
                  "authority" : "1",
                  "sharedTo" : "A"
                },
                {
                  "exiration" : "2021-05-20",
                  "authority" : "2",
                  "sharedTo" : "B"
                },
                {
                  "exiration" : "2021-05-20",
                  "authority" : "3",
                  "sharedTo" : "C"
                }
              ]
            },
            "inner_hits" : {
              "sharedInfo" : {
                "hits" : {
                  "total" : 1,
                  "max_score" : null,
                  "hits" : [
                    {
                      "_index" : "blogs",
                      "_type" : "_doc",
                      "_id" : "1",
                      "_nested" : {
                        "field" : "sharedPeople",
                        "offset" : 0
                      },
                      "_score" : null,
                      "_source" : {
                        "exiration" : "2021-05-20",
                        "authority" : "1",
                        "sharedTo" : "A"
                      },
                      "sort" : [
                        "1"
                      ]
                    }
                  ]
                }
              }
            }
          }
        ]
      }

    제대로 A의 권한이 나오고 있다.

  • 우리는 6.8을 쓰고있는데, 여기서도 inner_hit에서의 minimum_should_match를 없애버리는 실수를 한걸까? 앞으로 7.0대부터 minimum_should_match가 없어지니 항상 명시하는것을 기본으로 해야겠다.
profile
일하며 하는 기록

0개의 댓글