[030] ドキュメントを全件(11万件超)取得する by Ruby elasticsearch 7.14

|シリーズトップページ|
|:--|
|https://qiita.com/robozushi10/items/2c8b6951ee342b013974|

# 概要

Ruby の Elasticsearch Client を使って Elasticsearch 7.14 で REST API の操作する.
また、比較のために、Kibana DevTool でのクエリも合せて記しておく.

今回はインデックス「shakespeare」から「全件(11万件超)」を取得する.



# 検証環境

下記の要領で検証用データ「Shakespeare」が登録された
・Elasticsearch + Kibana (7.14)
を使用した

[[00] Ruby の elasticsearch client パッケージを使って Elasticsearch 7.14 を操作してみる ... 検証環境構築編](https://qiita.com/robozushi10/items/8bd936fe6fbab080149e)



# 参考にした情報

|URL|
|:--|
|https://zenn.dev/kaiba/articles/e8d93e0397404186124c|
|https://stackoverflow.com/questions/64708844/how-to-scroll-data-using-scroll-api-elasticsearch|
|[DevTool での scroll_id の使用方法](https://www.elastic.co/guide/en/elasticsearch/reference/current/scroll-api.html)|
|[elasticsearch-api における scroll\_id の指定方法](https://github.com/elastic/elasticsearch-ruby/blob/master/docs/examples.asciidoc)|
|[elasticsearch-api を使って scroll の情報を削除する](https://github.com/elastic/elasticsearch-ruby/blob/master/elasticsearch-api/lib/elasticsearch/api/actions/clear_scroll.rb)|
|[Qiita - 【Elasticsearch】ScrollAPIを使ってみた](https://qiita.com/mug-cup/items/5fbe8a4fd27a2f6492b1)|

　

# 実践

## Kibana DevTool の場合

### コード

次の(1)〜(4)を実行すること.

**(1) scroll_id を取得する.**

```
GET shakespeare/_search?scroll=2m
{
  "query": {"match_all": {}}
}
```

**(2) 上記 (1) で得た scroll_id の値を使って /_search/scroll に対して ( <font color="Red">GET</font> では無くて)  <font color="Blue">POST</font> を実行する.**

なおエンドポイントは<font color="Blue">「shakespeare」を含めずに、「**/_search/scroll**」とする</font> こと.

```
POST /_search/scroll
{
  "scroll": "5m",
  "scroll_id": "上記(1) で得た scroll_id の値"
}
```

**(3) 上記 (2)を 1回発行するごとに 10件ずつドキュメントを取り出せる**

ただし、11万件超を取り出すには 10分以内に上記 (2) のコマンドを 1.1万回以上を実施しないといけない.


**(4) スナップショットを消す**

10分経過すると自動的に消えるが、明示的に消す場合は次のようにする.

なおエンドポイントは<font color="Blue">「shakespeare」を含めずに、「**/_search/scroll**」とする</font> こと.


```
DELETE /_search/scroll
{
  "scroll_id" : "上記(1) で得た scroll_id の値"
}
```


### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/1ab1adfc-be0e-0600-eaf8-0b4a1f1debd7.png)




## Ruby の場合

### コード

重要な部分は「`if __FILE__ == $0 以降`」である.

class MySimpleClient は丸々コピーで良い. (が、192.168.10.115 のみ、適宜読み替えること)

```ruby
#!/usr/bin/env ruby
# -*- encoding: utf-8 -*-
require 'multi_json'
require 'faraday'
require 'elasticsearch/api'
require 'active_support/core_ext' #! note_0004
require 'active_support' #! note_0005
 
class MySimpleClient
# note_0001
  include Elasticsearch::API
  CONNECTION = ::Faraday::Connection.new url: 'http://192.168.10.115:29200'
  def perform_request(method, path, params, body, headers = nil)
    #! note_0003
    CONNECTION.run_request \
      method.downcase.to_sym,
      path_with_params(path, params),
      (body ? MultiJson.dump(body): nil),
      {'Content-Type' => 'application/json'}
  end
 
  private
 
  def path_with_params(path, params)
    return path if params.blank?
 
    case params
    when String
      "#{path}?#{params}"
    when Hash
      "#{path}?#{params.to_query}"
    else
      raise ArgumentError, "Cannot parse params: '#{params}'"
    end
  end
end
 
if __FILE__ == $0
 
  client = MySimpleClient.new
 
  q = {
    "query": {
      "match_all": { 
      }
    }
  }
  res = client.search index: 'shakespeare', scroll: '10m', body: q
  h = JSON.parse(res)
  pp h
  while h['hits']['hits'].size.positive?
    q = {
      "scroll_id": h['_scroll_id']
    }
    res = client.scroll scroll: '5m', body: q
    h = JSON.parse(res)
    pp h
  end
 
  # elasticsearch 上のスナップショットを消す
  res = client.clear_scroll scroll: '5m', body: q
end
 
# note_0001: https://rubydoc.info/gems/elasticsearch-api
# note_0002: https://www.elastic.co/guide/en/elasticsearch/reference/7.14/scroll-api.html
#            https://www.elastic.co/guide/en/elasticsearch/reference/7.14/clear-scroll-api.html#clear-scroll-api-scroll-id-param
# note_0003: MySimpleClient.new のインスタンスである client から呼び出されるので実装が必要である
# note_0004: 'active_support' を 'active_support/core_ext' に変更する.
#            APIドキュメントにある 'active_support' 指定だと次のエラーが発生してしまうためである.
#            tutorial.rb:26:in `path_with_params': undefined method `blank?' for {}:Hash (NoMethodError)
# note_0005: require 'active_support' が存在しないと次のエラーが発生してしまう.
#            /usr/local/bundle/gems/activesupport-6.0.4/lib/active_support/core_ext/object/json.rb:42:
#              in `to_json': uninitialized constant ActiveSupport::JSON (NameError)
```

### 結果

10件ずつのペースで合計 111397件 🛑 のドキュメントを GET できた.

なお、当方の環境では 111397件の取得に 1分42秒を費した.

```
{"_scroll_id"=>
  "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFnN5bnF6RHI1UUUyMnltTFA2Z09IemcAAAAAAAJEsBZpNkY2YTRJbVEtUzlUdkVNUVJkUXJn",
 "took"=>0,
 "timed_out"=>false,
 "_shards"=>{"total"=>1, "successful"=>1, "skipped"=>0, "failed"=>0},
 "hits"=>
  {"total"=>{"value"=>111397, "relation"=>"eq"},
   "max_score"=>1.0,
   "hits"=>
    [{"_index"=>"shakespeare",
      "_type"=>"_doc",
      "_id"=>"0",
略
{"_scroll_id"=>
  "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFnN5bnF6RHI1UUUyMnltTFA2Z09IemcAAAAAAAJEsBZpNkY2YTRJbVEtUzlUdkVNUVJkUXJn",
 "took"=>1,
 "timed_out"=>false,
 "terminated_early"=>true,
 "_shards"=>{"total"=>1, "successful"=>1, "skipped"=>0, "failed"=>0},
 "hits"=>
  {"total"=>{"value"=>111397 🛑} , "relation"=>"eq"},
   "max_score"=>1.0,
略
```