元祖若手のプログラミング奮闘記

元祖若手の奮闘記。主にメモ

Elasticsearch7にしたらtoo_many_buckets_exceptionになってしまった

結論

一回でまとめて取得していたものを数回に分けて取得するようにした。

対象のインデックス

GET logs/_mapping

{
  "logs" : {
    "mappings" : {
      "properties" : {
        "action" : {
          "type" : "text"
        },
        "user_id" : {
          "type" : "integer"
        },
        "company_id" : {
          "type" : "integer"
        },
        "request" : {
          "type" : "nested",
          "properties" : {
            "id" : {
              "type" : "integer"
            },
            "type" : {
              "type" : "text",
              "fielddata" : true
            }
          }
        },
        "timestamp" : {
          "type" : "date"
        }
      }
    }
  }
}

too_many_buckets_exceptionとは

Elasticsearchの用語に合わせて説明すると
指定した集約(aggregation)方法で分類(Bucket)された数が
1万を超えたために出たエラーである

SQLに例えて説明すると
GROUP BY で出力されたレコードの数が
1万を超えた場合である。

実際のElasticsearch で叩いてた内容

まずこんな検索Queryを作ったことを許してほしい

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "company_id": 1
          }
        },
        {
          "range": {
            "timestamp": {
              "gte": "now-1y",
              "lt": "now"
            }
          }
        }
      ]
    }
  },
  "aggs":{
    "by_user_id":{
      "terms":{
        "field":"user_id",
        "size":1000000,
        "missing":0
      },
      "aggs":{
        "by_month":{
          "date_histogram":{
            "field":"timestamp",
            "interval":"month",
            "format":"yyyyMM"
          },
          "aggs":{
            "by_request":{
              "nested":{
                "path":"request"
              },
              "aggs":{
                "by_request_type":{
                  "terms":{
                    "field":"request.type"
                  },
                  "aggs":{
                    "by_request_id":{
                      "terms":{
                        "field":"request.id"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

SQLで表現すると

SELECT
  user_id,
  DATE_FORMAT(timestamp,'%Y-%m') AS month,
  request_id,
  request_type
FROM
  logs
WHERE
  company_id = 1 AND
  timestamp BETWEEN DATE_SUB(NOW(),INTERVAL 1 YEAR) AND NOW()
GROUP BY
  user_id, DATE_FORMAT(timestamp,'%Y-%m'), request_id, request_type

確かに一年間分となるとそうなるよなと思いつつ
このエラーに対する設定は変えられるのだが、
現時点ではElasticsearch 公式では設定ではなくクエリ自体を修正するように勧めている。

公式通り検索範囲を狭めた

一年で1回ではなく 一ヶ月で12回に分けるようにした。

GET logs/_search

{
   "query":{
      "bool":{
         "must":[
            {
               "match":{
                  "company_id": 1
               }
            },
            {
               "range":{
                  "timestamp":{
                     "gte":"2020-01-01T00:00:00.000+09:00",
                     "lte":"2020-01-31T23:59:59.999+09:00"
                  }
               }
            }
         ]
      }
   },
   "aggs":{
      "by_user_id":{
         "terms":{
            "field":"user_id",
            "size":1000000,
            "missing":0
         },
         "aggs":{
            "by_request":{
               "nested":{
                  "path":"request"
               },
               "aggs":{
                  "by_request_type":{
                     "terms":{
                        "field":"request.type"
                     },
                     "aggs":{
                        "by_request_id":{
                           "terms":{
                              "field":"request.id"
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }
}

あとはサーバーサイド側で整えて今回は乗り越えた。

やはり基礎は学んでおかないと。。。

今回はこの本を読んで基本を抑えました。

Kindle unlimitedの対象となっていたので読んでみましたが
例えや図が多く、わかりやすい内容になっているので急いで学ばなくてはいけない人にもおすすめです