Blog

2016.03.25 - 번역 - Reindex is coming! ...

drscg 2019. 1. 6. 17:51

_reindex and _update_by_query are coming to Elasticsearch 2.3.0 and 5.0.0-alpha1! Hurray!

_reindex 와 _update_by_query 가 Elasticsearch 2.3.0 과 5.0.0 에 제공된다.

_reindex reads documents from one index and writes them to another index. It can be used to copy documents from one index to another, enrich documents with fields, or recreate the index to change settings that are locked when the index is created.

_reindex 는 특정 index에서 document를 읽어 다른 index에 쓴다. 특정 index에서 다른 index로 document를 복사하거나, field를 가진 document를 풍부하게 하거나 index를 생성할 때 잠긴 설정을 변경하기 위하여 index를 다시 생성하는데 사용될 수 있다. 

_update_by_query reads documents from an index and writes them back to the same index. It can be used to update fields in many documents at once or to pick up mapping changes that can be made online.

_update_by_query 는 index에서 document를 읽어 동일한 index에 다시 쓴다. 한번에 많은 document의 field를 update하거나 online으로 만들어질 수 있는 mapping 변경을 처리하는데 사용될 수 있다.

_reindex copies documents

The _reindex API is really just a convenient way to copy documents from one index to another. Everything else that it can do is an outgrowth of that. If all you want to do is to copy all the documents from the src index into the dest index you invoke _reindex like this:

_reindex API는 특정 index에서 다른 index로 document를 복사하는 편리한 방법이다. reindex가 할 수 있는 다른 모든 것은 부산물이다. src index에서 dest index로 모든 document를 복사하려면, 다음과 같이 _reindex 를 호출하면 된다.

curl -XPOST localhost:9200/_reindex?pretty -d'{
  "source": {
    "index": "src"
  },
  "dest": {
    "index": "dest"
  }
}'

If you want to be a little more selective and, say, only copy docments tagged with bananas you invoke _reindexlike this:

좀 더 선택적으로 하려면, 예를 들어, bananas 로 tag된 document만을 복사하려면, 다음과 같이 _reindex 를 호출하면 된다.

curl -XPOST localhost:9200/_reindex?pretty -d'{
  "source": {
    "index": "src",
    "query": {
      "match": {
        "tags": "bananas"
      }
    }
  },
  "dest": {
    "index": "dest"
  }
}'

If you want to copy documents tagged with bananas but you want to add the chocolate tag to all copied documents you invoke _reindex like this:

bananas 로 tag된 document를 복사하되 모든 복사된 document에 chocolate tag를 추가하려면, 다음과 같이 _reindex 를 호출하면 된다.

curl -XPOST localhost:9200/_reindex?pretty -d'{
  "source": {
    "index": "src",
    "query": {
      "match": {
        "tags": "bananas"
      }
    }
  },
  "dest": {
    "index": "dest"
  },
  "script": {
    "inline": "ctx._source.tags += \"chocolate\""
  }
}'

That requires that you have dynamic scripts enabled but you can do the same thing with non- inline scripts.

따라서, dynamic script를 활성화해야 하지만 non-inline script로 동일한 작업을 할 수 있다.

Recreating an index to change settings that are locked at index creations is a bit more involved but still simpler than before _reindex:

index 생성시에 잠긴 설정을 변경하기 위해 index를 다시 생성하는 것은 좀 더 복잡하지만 _reindex 이전보다 훨신 더 간단하다.

# Say you have an old index that you made like this
curl -XPUT localhost:9200/test_1 -d'{
  "aliases": {
    "test": {}
  }
}'
for i in $(seq 1 1000); do
  curl -XPOST localhost:9200/test/test -d'{"tags": ["bananas"]}'
  echo
done
curl -XPOST localhost:9200/test/_refresh?pretty
# But you don't like having the default number of shards
# You can make a copy of it with the new number of shards
curl -XPUT localhost:9200/test_2 -d'{
  "settings": {
    "number_of_shards": 1
  }
}'
curl -XPOST 'localhost:9200/_reindex?pretty&refresh' -d'{
  "source": {
    "index": "test"
  },
  "dest": {
    "index": "test_2"
  }
}'
# Then just swing the alias to the new index
curl -XPOST localhost:9200/_aliases?pretty -d'{
  "actions": [
    { "remove": { "index": "test_1", "alias": "index" } },
    { "add": { "index": "test_2", "alias": "index" } }
  ]
}'
# Then when you are good and sure you are done with it you can
curl -XDELETE localhost:9200/test_1?pretty
Read Less

_update_by_query modifies documents

The simplest way to invoke update by query isn't particularly useful on its own:

query에 의해 update를 호출하는 가장 간단한 방법은 그 자체로는 특별히 유용하지 않다.

curl -XPOST localhost:9200/test/_update_by_query?pretty

That will just increment the document version number on each document in the test index and fail if you modify a document while it is running. A more interesting example is adding the chocolate tag to all documents with the bananas tag:

이렇게 하면, test index의 각 document에서 document version이 증가하고, query가 실행하는 동안 document를 변경하면 실패한다. 보다 흥미로운 예는 bananas tag를 가진 모든 document에 chocolate tag를 추가하는 것이다.

curl -XPOST 'localhost:9200/test/_update_by_query?pretty&refresh' -d'{
  "query": {
    "bool": {
      "must": [ {"match": {"tags": "bananas"}} ],
      "must_not": [ {"match": {"tags": "chocolate"}} ]
    }
  },
  "script": {
    "inline": "ctx._source.tags += \"chocolate\""
  }
}'

Like the last version this will fail if any documents are changed while it is running, but it is written in such a way that you can just retry it and it'll pick up from where it left off. If you've already modified whatever application is making the concurrent updates to add the chocolate tag whenever it sees bananas then you can safely ignore version conflicts in the _update_by_query. You can tell it to do so by setting conflicts=proceed. It will just count the version conflicts and continue performing updates. Now the command looks like this:

마지막 버전과 마찬가지로, query가 실행되는 동안 document를 변경하면 실패하지만, query를 다시 시도하는 방식으로 작성되어, 중단된 부분부터 처리한다. bananas 를 찾을 때마다 chocolate tag를 추가하는 동시에 update하는 application을 이미 수정했다면, _update_by_query 에서 version 충돌을 안전하게 무시할 수 있다. conflicts=proceed 를 설정하여 이렇게 할 수 있다. version 충돌을 count만 하고, update를 계속 한다. command는 다음과 같다.

curl -XPOST 'localhost:9200/test/_update_by_query?pretty&refresh&conflicts=proceed' -d'{
  "query": {
    "bool": {
      "must": [ {"match": {"tags": "bananas"}} ],
      "must_not": [ {"match": {"tags": "chocolate"}} ]
    }
  },
  "script": {
    "inline": "ctx._source.tags += \"chocolate\""
  }
}'

Finally, you can use _update_by_query to suck up mapping changes that only take effect when the document is modified like adding a new field to an existing field. For example:

마지막으로, 기존의 field에 새로운 field를 추가하는 것처럼, document가 변경되는 경우에만 효과가 있도록 mapping 변경이 적용되도록, _update_by_query 를 사용할 수 있다. 

# Say I made an index with tags not_analyzed because, you know, they are tags after all
curl -XPUT localhost:9200/test_3?pretty -d'{
  "mappings": {
    "test": {
      "properties": {
        "tags": {
          "type": "string",
          "index": "not_analyzed"
        }
      }
    }
  }
}'
for i in $(seq 1 1000); do
  curl -XPOST localhost:9200/test_3/test -d'{"tags": ["bananas"]}'
  echo
done
curl -XPOST localhost:9200/test_3/_refresh?pretty
# But now I want to search on tags using the standard analyzer so I can search for banana and find bananas
curl -XPUT localhost:9200/test_3/_mapping/test?pretty -d'{
  "properties": {
    "tags": {
      "type": "string",
      "index": "not_analyzed",
      "fields": {
        "analyzed": {
          "type": "string",
          "analyzer": "standard"
        }
      }
    }
  }
}'
# This doesn't take effect immediately
curl 'localhost:9200/test_3/_search?pretty' -d'{
  "query": {
    "match": {
      "tags.analyzed": "bananas"
    }
  }
}'
# :(
# But we can _update_by_query to pick up the new mapping on all documents
curl -XPOST 'localhost:9200/test_3/_update_by_query?pretty&conflicts=proceed&refresh'
# And now the new mapping has been applied to the whole index!
curl 'localhost:9200/test_3/_search?pretty' -d'{
  "query": {
    "match": {
      "tags.analyzed": "bananas"
    }
  }
}'
Read Less

Getting the status

_reindex and _update_by_query can touch millions of documents so they can take a long time. You can fetch their status with:

_re와 up는 수백만개의 document를 조작할 수 있어, 시간이 많이 걸린다. 그들의 상태를 가져올 수 있다.

curl localhost:9200/_tasks?pretty&detailed&actions=*reindex,*byquery

That will contain a field that looks like:

다음과 같은 field를 포함한다.

"BHgHr0cETkOehwqZ2N_-aQ:28295" : {
  "node" : "BHgHr0cETkOehwqZ2N_-aQ",
  "id" : 28295,
  "type" : "transport",
  "action" : "indices:data/write/reindex",
  "start_time_in_millis" : 1458767149108,
  "running_time_in_nanos" : 5475314,
  "status" : {
    "total" : 6154,
    "updated" : 3500,
    "created" : 0,
    "deleted" : 0,
    "batches" : 36,
    "version_conflicts" : 0,
    "noops" : 0,
    "retries": 0,
    "throttled_millis": 0
  }
}

You can read the docs for more, but the gist is that _reindex plans to do total operations and has already done updated + created + deleted + noops of them. So you can estimate how complete the request is by dividing those numbers.

자세한 부분은 docs 에서 읽을 수 있는데, 중요한 부분은 _reindex는 total 연산을 예상하고 있고, 그 중에 updated + created + deleted + noops 를 이미 완료했다.  따라서, 이 숫자를 나누면, request가 얼마나 완료되었는지 추정할 수 있다.

Cancelling

_reindex was so long in coming because Elasticsearch lacked a way to cancel running tasks. For short running tasks like _search and indexing that is fine. But, like I wrote above, _reindex and _update_by_query can touch millions of documents are take a long time. The tasks themselves are ok with that, but you may not be. Say you realize ten minutes into a three hour long _update_by_query that you made a mistake in the script. There isn't a way to rollback the changes that the reindex already made but you can cancel it so it won't make any more such changes:

Elasticsearch에서 실행중인 _reindex task를 cancel할 방법이 없어, 출시에 오래 걸렸다. _search 나 index 처럼 짧은 시간동안 실행되는 task는 괜찮다. 그러나 위에서 언급한 것처럼, _reindex 와 _update_by_query 은 수백만의 document를 처리할 수 있어 시간이 오래 걸린다. task 자체는 ok다. 하지만 아닐 수도 있다. 3시간 짜리 _update_by_query 에서 script에 실수가 있다는 것을 10분 후에 알게되었다고 가정해 보자. reindex가 이미 변경한 사항을 rollback할 방법은 없지만, cancel하면 더 이상 변경 사항이 발생하지 않는다.

curl -XPOST localhost:9200/_task/{taskId}/_cancel

And where do you get the taskId? It is the name of the object returned by the task listing API in the last section of this blog post. The one in the example return is BHgHr0cETkOehwqZ2N_-aQ:28295.

그렇다면 taskid는 어디에 얻을 수 있을까? 위에서 이야기한 task list API에서 반환되는 object의 name이다. 위의 예에서는 BHgHr0cETkOehwqZ2N_-aQ:28295 이다

In Elasticsearch task cancelation is opt in. It kind of has to be that way in any Java application. Anyway, tasks that can be canceled like _reindex and _update_by_query periodically check to see if they have been canceled and then shut themselves down. This means that you might see the task if you immediately list its status after it has been canceled. It will go away on its own and you can't cancel it any harder without stopping the node it is running on.

Elasticsearch에서 task cancel은 사전 예약이다. 모든 java application이 그런 방식이어야 한다. 어쨌든, _reindex 나 _update_by_query 처럼 cancel될 수 있는 task는 주기적으로 cancel되었는지 확인하고, 스스로 종료한다. 즉, task를 cancel하고 바로 상태를 나열해 보면, task가 나타날 수 있다. 스스로 종료되므로, 실행중인 node를 중단하지 않고서는 더 이상 cancel할 수 없다.

Remember that Elasticsearch is a search engine

Every update has to mark the document as deleted and index the entire new document. The deleted documents have to then be merged out of the index. _reindex and _update_by_query don't save anything in that process. They work just as though you performed a scroll query and indexed all the results. Running a zillion _reindexs or_update_by_querys is unlikely to be the most efficient use of computer resources to accomplish some task. You will almost always be better off making changes to the application that adds data to Elasticsearch rather than updating the data after the fact. _reindex and _update_by_query are most useful for turning the data that you already have in Elasticsearch into the data that you want to be in Elasticsearch.

update는 document를 삭제된 것으로 표시하고 새로 전체 document를 index하는 것이다. 삭제된 document는 index에서 merge되어야 한다. _reindex 나 _update_by_query 는 해당 process에서 아무것도 저장하지 않는다. scroll query를 실행하고 모든 결과를 index하는 것처럼 동작한다. 엄청난 수의 _reindex 나 _update_by_query 를 실행하는 것이 특정 task를 수행하는데 있어 가장 효율적이 computer resource 사용이 될 것 같지는 않다. 사실, 나중에 data를 update하는 것 보다는 Elasticsearch에 data를 추가하는 application을 변경하는 것이 더 낫다. _reindex 나 _update_by_query 는 Elasticsearch가 이미 가지고 있는 data를 Elasticsearch에 존재해야 하는 data로 변환하는데 가장 유용하다.

원문 : Reindex is coming!