ElasticSearch - 批量操作 bulk

古古

2018/07/30


  • ES 的 bulk 語法允許在一個請求中進行多個操作(create、index、update、delete),也就是可以在一次請求裡做很多事情

    • 也由於這個關係,因此 bulk 的請求體和其他請求的格式會有點不同
  • bulk 的請求模板

    • 分成 action 和 metadata 兩部份
    • action : 必須是以下 4 種選項之一
      • index(最常用) : 如果文檔不存在就創建他,如果文檔存在就更新他
      • create : 如果文檔不存在就創建他,但如果文檔存在就返回錯誤
        • 使用時一定要在 metadata 設置 _id 值,他才能去判斷這個文檔是否存在
      • update : 更新一個文檔,如果文檔不存在就返回錯誤
        • 使用時也要給 _id 值,且後面文檔的格式和其他人不一樣
      • delete : 刪除一個文檔,如果要刪除的文檔 id 不存在,就返回錯誤
        • 使用時也必須在 metadata 中設置文檔 _id,且後面不能帶一個 doc,因為沒意義,他是用 _id 去刪除文檔的
    • metadata : 設置這個文檔的 metadata,像是 _id_index
      POST mytest/_bulk
      { action : { metadata } }
      { doc }
      { action : { metadata } }
      { doc }
      ....
      
  • 具體實例

    • bulk請求

      POST mytest/_bulk
      
      //創建一筆數據
      { "create" : { "_id": 1 } }
      { "color": "create black" }
      
      //創建一筆數據,因為id=1的文檔已經存在,所以會創建失敗
      { "create" : { "_id": 1 } }
      { "color": "create black2" }
      
      //索引一筆數據
      { "index" : { "_id": 2 } }
      { "color": "index red" }
      
      //索引一筆數據,但是index可以創建也可以更新,所以執行成功
      { "index" : { "_id": 2 } }
      { "color": "index red2" }
      
      //索引一筆數據,不一定要設置id(index又能創建又能更新又不用設id,超好用)
      { "index": {} }
      { "color": "index blue" }
      
      //刪除一筆文檔,注意delete後面不接一個doc
      { "delete" : { "_id": "2" } }
      
      //找不到此id的文檔,刪除失敗
      { "delete" : { "_id": "2" } }
      
      //更新一筆文檔,注意doc格式不太一樣
      { "update" : { "_id": 1 } }
      { "doc": { "color": "update green"} }
      
      //更新一筆文檔,但因為此id的文檔不存在,所以更新失敗
      { "update" : { "_id": 100 } }
      { "doc": { "color": "update green2"} }
      
  • bulk 的返回結果

    • 因為在 bulk 中,每個 action 的執行結果都是獨立的,所以有幾個 action,就會有幾個返回結果,返回結果如下

      • 最上面會有一個 errors,表示這一次 bulk 請求中,是否有 action 出錯了
      • 因此寫代碼時可以先檢查 errors 這個值,如果是 false,表示這次 bulk 請求全部通過,就不用再一一去檢查是否有 action 出錯,但如果是 true,則必須去 items 一個一個檢查到底是哪個 action 出錯了
    • items 是一個 array,裡面則放著每個 action 對應的結果,上面的請求執行了 9 個 action,所以返回結果的 items 就會有 9 個

      • 返回結果會依照 action 的順序排好,因此 items 的第一個結果就是請求時第一個 action 的執行結果
        {
            "took": 145,
            "errors": true,
            "items": [
                {
                    "create": {
                        "_index": "mytest",
                        "_type": "_doc",
                        "_id": "1",
                        "result": "created",
                        "status": 201
                    }
                },
                {
                    "create": {
                        "_index": "mytest",
                        "_type": "_doc",
                        "_id": "1",
                        "status": 409,
                        "error": {
                            "type": "version_conflict_engine_exception",
                            "reason": "[1]: version conflict, document already exists (current version [1])",
                            "index_uuid": "PfdgdTyiRgaCIM6uKRQAPQ",
                            "shard": "0",
                            "index": "mytest"
                        }
                    }
                },
                {
                    "index": {
                        "_index": "mytest",
                        "_type": "_doc",
                        "_id": "2",
                        "result": "created",
                        "status": 201
                    }
                },
                {
                    "index": {
                        "_index": "mytest",
                        "_type": "_doc",
                        "_id": "2",
                        "result": "updated",
                        "status": 200
                    }
                }
                ... 5 RESULTS REMOVED ...
            ]
        }
        
  • 使用 bulk 要注意的地方

    • 如果使用 127.0.0.1/_bulk,那麼就是在整個 ES 的範圍中插入數據,因此在 metadata 中要指定插入的 index,優點是可以一次插入多筆數據到不同的索引
      • 而如果使用 127.0.0.1/mytest/_bulk,就不用在 metadata 再次指定要插入的 index,可以想像成是 _bulk API幫我們自動填好了 metadata 的 _index,很方便
    • 還有因為 bulk 和其他請求的格式不同,或是說基本上他已經不是正常的 json 格式了,所以在使用 bulk 時,HTTP header 要使用 application/x-ndjson
      • 而且每一行的結尾,都要使用 \n,如果是一般在 postman 寫請求不會有問題,但是如果是使用 curl 來發送請求,就要使用 --data-binary,才會使每一句的結尾都是 \n
  • 多大是太大了?

    • 由於整個 bulk 批量請求都需要由接收到請求的節點加載到內存中,因此該請求越大,其他請求所能獲得的內存就越少
    • 批量請求的大小有一個最佳值,大於這個值,性能將不再提升,甚至會下降,但是最佳值不是一個固定的值,它完全取決於硬件、文檔的大小和複雜度、索引和搜索的負載的整體情況
    • 幸運的是,很容易找到這個最佳點 :可以通過批量索引文檔,並不斷增加批量大小進行嘗試,當性能開始下降時,那就表示你的批量大小太大了
      • 一個好的辦法是開始時將 1000 到 5000 個文檔作爲一個批次慢慢增長,試圖找到最佳點
      • 但是如果你的文檔非常大,那麼就必須減少批量的文檔個數,密切關注你的批量請求的物理大小往往非常有用,一千個 1KB 的文檔是完全不同於一千個 1MB 文檔所佔的物理大小,一個好的批量大小在開始處理後所佔用的物理大小約爲 5-15 MB