В этом уроке мы обсудим различные способы обновления существующих документов. Внутренне обновление всегда является удалением и повторным индексом. Вы можете обновить весь документ (заменить исходный документ) или обновить одно поле или добавить новое поле или обновить поле с помощью сценариев, например, увеличить счетчик.
Обновление с использованием всего документа
Когда вы индексируете документ с существующим идентификатором, он заменит текущий документ новым документом. Как показано ниже, мы можем обновить документа используя индетификатор:
PUT example4/person/1 { "id": 1, "name": "name update 1", "age": 55, "gender": "M", "email": "[email protected]", "last_modified_date": "2017-02-15" }
Ответ:
{ "_index": "example4", "_type": "person", "_id": "1", "_version": 2, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": false }
Из ответа видно что result равен updated, _version (версия) равна 2 и что документ не был сейчас создан (created: false).
Частичные обновления
В предыдущем разделе мы обсудили, как обновить весь документ. В этом разделе мы обсудим, как обновить только одно или два поля в документе. Elasticsearch предоставляет API обновления, чтобы частично обновить существующий документ. API-интерфейс обновления сначала извлекает старый документ, а затем использует _source документа для применения изменений, удаляет старый документ и индексирует документ в качестве нового. Обновляемые поля указываются в теле запроса в поле doc.
Чтобы работали частичные обновления _source должно быть включено, по умолчанию так и есть, но лучше проверить. Что такое _source смотрите раздел Хранение оригинального документа
Предположим, мы хотим обновить только имя человека и не беспокоиться о каком-либо другом поле в документе. Пример:
POST example4/person/1/_update { "doc": { "name": "name udpate 2" } }
Ответ следующий:
{ "_index": "example4", "_type": "person", "_id": "1", "_version": 3, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 } }
Как видно в ответе версия увеличилась и теперь равна 3.
Сценарий обновления
Сценарии обновления будут полезны, если вы хотите обновить документ на основе условий. Без данной возможности вам необходимо сначала получить документ, проверить условия в документе, применить изменения и переиндексировать документ. Частичные обновления извлекают документ из осколка, рекурсивно применяют изменения и повторно индексируют документы, избегая сетевых обходов.
Сценарий обновления также можно использовать для увеличения счетчика. Приложению не нужно беспокоиться о текущем значении. Мы можем увеличить поле возраста в документе лица, используя скрипт, как показано ниже:
POST example4/person/1/_update { "script": "ctx._source.age+=1" }
Elasticsearch поддерживает множество языков сценариев для выполнения встроенных скриптов, язык сценариев по умолчанию — Painless. Предположим, мы хотим классифицировать документ, который мы проиндексировали ранее, по взрослым и подросткам. Мы можем использовать встроенный скрипт, как показано ниже, чтобы проверить возраст человека и добавить новое поле person_type. Следующая команда обновит документ на основе сценария:
POST example4/person/1/_update { "script": { "inline": "if (ctx._source.age > params.age) { ctx._source.person_type = 'adult' } else { ctx._source.person_type = 'teenager' }", "params": { "age": 19 } } }
Теперь давайте вернем документ id 2. Ответ выглядит следующим образом:
{ "id": 2, "name": "name update 1", "age": "55", "gender": "M", "email": "[email protected]", "last_modified_date": "2017-02-18", "person_type": "adult" }
Из ответа можно увидеть, что в документе появилось новое поле person_type = adult (взрослый).
Upsert
При частичном обновлении документа, если документ еще не существует, обновление завершится неудачно. Если вы хотите создать новый документ, если документ не существует, вы можете установить doc_as_upsert флаг в true. Установка upsert в true приведет к созданию нового документа с полями в поле doc. Приведем пример:
POST example4/person/3/_update { "doc": { "name": "user3" }, "doc_as_upsert": true }
В предыдущем примере, поскольку документ id 3 не существует, создается новый документ. Теперь давайте посмотрим на него. Ответ следующий:
{ "_index": "example4", "_type": "person", "_id": "3", "_version": 1, "found": true, "_source": { "name": "user3" } }
Из ответа видно, что новый документ содержит только поле name.
NOOP
В предыдущем разделе мы обновили документ id 2, где имени присвоили значение «name update 2»:
POST example4/person/2/_update { "doc": { "name": "name update 2" } }
Если вы попытаетесь снова запустить обновление, операция будет проигнорирована, так как изменений нет. Ответ будет содержать result (результат) равный noop, как показано здесь:
{ "_index": "example4", "_type": "person", "_id": "2", "_version": 1, "result": "noop", "_shards": { "total": 0, "successful": 0, "failed": 0 } }
Вы можете отключить это поведение, установив detect_noop в false:
POST example4/person/2/_update { "doc" : { "name" : "name update 2" }, "detect_noop" : "false" }
С detect_noop установленным значением false, произойдет обновление документа не взирая что данные уже есть в документе. Ответ следующий:
{ "_index": "example4", "_type": "person", "_id": "2", "_version": 2, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 } }
Что происходит при обновлении документа?
В предыдущем разделе мы обсудили, что происходит, когда вы индексируете документ. В этом разделе мы обсудим, как обрабатываются обновления документов и почему обновление является дорогостоящей операцией.
Как обсуждалось ранее, когда документ индексируется, он сохраняется в сегменте. По дизайну сегмент, после создания, не может быть изменен. Будучи неизменным, вы получаете несколько преимуществ. Например, как только файл сегмента считывается в кеш файловой системы, он может жить там вечно, поскольку он не изменяется, и Lucene не нужно беспокоиться о блокировке файла для любых изменений. Но если сегмент не может быть изменен, как мы можем обновить существующий документ? Чтобы выполнить обновление, сначала существующий документ будет мягко удален, а обновленный документ будет проиндексирован как новый документ.
Слияние сегментов
По умолчанию процесс обновления создает новый сегмент каждую секунду. Это приведет к созданию множества сегментов. Поскольку поиск по осколку должен проходить через все сегменты в осколке, наличие большого количества сегментов замедлит работу поиска.
Сегменты также нуждаются в большом количестве ресурсов, таких как обработчики файлов, процессор, дисковое пространство и память. Чтобы уменьшить количество сегментов, Lucene объединяет сегменты аналогичного размера в более крупный сегмент. При слиянии сегментов документы, помеченные как удаленные, не копируются в объединенный сегмент. Пока сегменты объединяться, документ физически не удаляется с диска:

В то время как сегменты объединяются в более крупный сегмент, поисковые запросы по-прежнему обслуживаются из изначальных сегментов. После завершения слияния новые сегменты могут обслуживать запросы поиска, а старые сегменты удаляются с диска. Чем больше обновлений, тем чаще объединяются сегменты. Слияние активно использует ресурсы процессора, памяти и ввода-вывода, из-за чего данная операция очень дорогая.