Website logo

Robert Chang

技術部落格

Elasticsearch - 使用 Script 來更新欄位

上一篇文章 中,我們使用了 POST /users/_update/1 的方式來更新 _id 為 1 的 user 的值。

但這樣的操作方式,再結合了商業邏輯之後,就會需要先 GET 去拿到值接著在判斷是不是要增加或減少。

但 Elasticsearch ( 以下簡稱 ES ) 其實可以讓我們寫 script,也就是把一些判斷式放到 payload 裡面。

一樣使用昨天的例子,我們來針對使用者的年紀進行增加。

screen shot

ctx 代表的是 context

上面的方式寫起來就很像程式語言 -- 就是針對目前的值減 1 的意思,反之 ++ 則是加 1。

我們可以想像這個是整個 document,而後面的 ._source 其實就很像是取值。

複習一下,整個 document 物件的長相:

{
  "_index": "users",
  "_id": "1",
  "_version": 6,
  "_seq_no": 6,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "name": "John",
    "age": 42,
    "gender": "male",
    "habits": [
      "baseball"
    ]
  }
}

也可以直接使用 = 的方式來更新值:

screen shot

更甚者,還可以支援 params 的寫法,這樣就可以透過應用程式處理完商業邏輯後,拋到 ES 進行更新。

screen shot

返回的結果

這邊有一個很重要的概念,這種 request 以及 response 的模式,很重要的是回傳的結果。

而在 _update 的 endpoint 中,可以看到 result 這個 key 都是回傳 updated 代表的是成功更新,那失敗呢?

{
  "_index": "users",
  "_id": "1",
  "_version": 9,
  "result": "updated", // 更新成功
  "_shards": {
    "total": 3,
    "successful": 3,
    "failed": 0
  },
  "_seq_no": 9,
  "_primary_term": 1
}

上一篇文章 中,有一個點我忘記提到了,如果不使用 script 的方式去更新值的話,當 ES 發現該值沒有任何變化,會回傳 noop 的結果。

假設 user 1 的 age = 20,接著我們嘗試用 doc 的方式直接更新成 20,會回傳 noop 代表什麼都沒發生:

screen shot

而當今天使用 script 的方式去更新,不論值有變與否,都會回傳 updated,這或許不會是個問題,但有時候結合商業邏輯之後,我們會想要知道更多,那該怎麼做?

可以在 script 裡面利用 ctx.op 的方式來控制回傳的 result

op 代表的 operation,執行的動作

假設今天使用者的年紀等於 20 歲,就不做更新,並且回傳 noop 該怎麼做呢?其餘一律改成 20 歲:

screen shot

神奇的魔法!ES 的 script 內是可以寫 if else 的判斷式的!

而我們也成功地看到回傳的結果是 noop,這樣就可以讓我們在使用這個 endpoint 的同時,知道這個操作的結果是到底真的更新,還是沒有,方便應用程式做後續的處理。

而關於 ctx.op,甚至還可以使用 delete,當某個條件式達成時,刪除掉這個 document。

隨便寫一下,大概像是這樣:

{
  "script": {
    "source": """
      if (ctx._source.age == 20) {
        ctx.op = 'delete';
      }
      
      ctx._source.age = 20;
    """
  }
}

所以當 ES 發現這個使用者是 20 歲,就會刪掉這個 document,我知道我的例子很爛。

但真實的使用場景可以變成,當商品的庫存為 0 的時候,刪除掉 document,因為我們不希望使用者在搜尋到這項商品之類的。

總之,ES 的 _update API 搭配 script 的方式真的非常實用。

上一篇文章Elasticsearch - Index & Document 的 CRUD

下一篇文章Elasticsearch - Upsert & Routing