Частичное обновление документов в Solr
Обзор
Solr предоставляет три основных метода для частичного обновления данных в индексе. Данная статья описывает использование каждого метода с примерами, а именно:
-
Атомарные обновления (atomic updates). Позволяют изменять одно или несколько полей документа путем переиндексирования всего документа.
-
Обновления типа in-place (in-place updates). Подвид атомарных обновлений, позволяющие модифицировать числовые поля без переиндексирования всего документа.
-
Метод оптимистичного параллелизма (optimistic concurrency). Позволяет выполнять обновления по условию, используя версию документа.
В этой статье примеры операций обновления предназначены для изменения следующего тестового документа:
{
"id": 1,
"post_name": "Sample blog post...",
"categories": ["leisure", "hobby"],
"post_rank": 75.0,
"post_date": "2024-01-02",
"post_text": "Lorem ipsum dolor sit amet ...",
"description": "A sample post for testing purposes"
}
Данный документ хранится в индексе Solr в коллекции test_collection
.
Информация о добавлении документа в индекс доступна в разделе Обзор работы с индексами в Solr.
Атомарные обновления
Данный метод позволяет изменять отдельные поля документа. Этот подход следует применять, когда скорость и частота изменений в индексе критична для клиентских приложений.
Solr поддерживает несколько модификаторов, которые используются для обновления отдельных полей документа. Эти модификаторы представлены ниже:
-
set
— устанавливает новое или перезаписывает существующее значение поля. Удаляет значение, если указанnull
или пустой список. Принимает одно значение или список. -
add
— добавляет указанное значение(я) в поле типаmultiValued
. Принимает одно значение или список. -
add-distinct
— добавляет указанное значение(я) в поле типаmultiValued
, если указанное значение не присутствует в индексе. Принимает одно значение или список. -
remove
— удаляет все совпадения указанного значения из поля типаmultiValued
. Принимает одно значение или список. -
removeregex
— удаляет все совпадения, соответствующие регулярному выражению, из поля типаmultiValued
. Принимает одно значение или список. -
inc
— увеличивает числовое поле на заданное значение. Принимает одно числовое значение.
Чтобы обновить документ, укажите соответствующий модификатор в качестве значения обновляемого поля и загрузите документ в Solr. Следующий пример обновляет сразу несколько полей тестового документа:
{
"id": 1,
"post_name": {"set": "Updated post name ..."},
"categories": {"add": ["science"]},
"post_rank": {"inc": 5},
"post_date": "2024-01-02",
"post_text": "Lorem ipsum dolor sit amet ...",
"description": {"removeregex": ".*testing.*"}
}
$ curl -X POST 'http://ka-adh-1.ru-central1.internal:8983/solr/test_collection/update?commit=true' -H 'Content-Type: application/json' --data-binary '[{
"id": 1,
"post_name": {"set": "Updated post name ..."},
"categories": {"add": ["science"]},
"post_rank": {"inc": 5},
"post_date": "2024-01-02",
"post_text": "Lorem ipsum dolor sit amet ...",
"description": {"removeregex": ".*testing.*"}
}]'
Когда Solr получает такой документ, он распознает выражения-модификаторы и обновляет соответствующие поля документа. После обновления документ имеет следующий вид:
{
"responseHeader":{
"zkConnected":true,
"status":0,
"QTime":0,
"params":{
"q":"*:*",
"indent":"true",
"q.op":"OR",
"_":"1726172218281"}},
"response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[
{
"id":"1",
"post_name":["Updated post name ..."],
"categories":["leisure",
"hobby",
"science"],
"post_rank":[80.0],
"post_date":["2024-01-02T00:00:00Z"],
"post_text":["Lorem ipsum dolor sit amet ..."],
"_version_":1810022770405277696}]
}}
Обновление вложенных документов
Solr позволяет изменять, добавлять и удалять вложенные документы с помощью операций атомарного обновления. Процесс очень похож на обновление обычных (невложенных) документов, а основные особенности, которые следует учитывать, заключаются в следующем:
-
Каждая операция по обновлению вложенного объекта должна быть направлена в нужный шард Solr — тот, в котором хранится родительский документ. Поскольку Solr определяет целевой шард на основе ID документа, важно использовать ID корневого (root) документа, а не ID обновляемого вложенного документа. Для выполнения этого требования можно либо явно указать Solr-роутер (через параметр
_route_
), либо использовать compositeId router (используется по умолчанию) для направления операций обновления в нужный шард. Больше информации о правилах маршрутизации при обновлении вложенных документов доступно в документации Solr. -
При обновлении вложенных объектов необходимо проинформировать Solr, какой документ является родительским для обновляемого объекта. Это можно сделать, указав поле
_root_
в запросе на обновление. -
По умолчанию Solr пытается применить обновления ко всему дереву вложенных документов вместо того, чтобы изменять отдельные объекты. Это может приводить к дополнительной нагрузке.
Предположим, что имеется следующий документ с вложенными объектами-комментариями, который необходимо обновить:
{
"id": "post2",
"name_s": "Another demo post...",
"text_t": "Foo Bar Buzz ....",
"content_type": "post",
"comments":
[
{
"id": "post2!comment3",
"author_s": "Luke",
"text_t": "I like this post!",
"rated_i": 4,
"content_type": "comment"
}
]
}
Например, чтобы увеличить числовое поле rated_i
, используя атомарные обновления, необходимо загрузить следующий документ в Solr:
{
"id": "post2!comment3", (1)
"_root_": "post2", (2)
"rated_i": { "inc": 1 }
}
1 | ID вложенного документа, который необходимо обновить.
Обратите внимание, что ID является составным (состоит из частей, разделенных символом ! ).
Использование составного ID позволяет стандартному compositeId router направить операцию обновления в соответствующий шард Solr. |
2 | Поле _root_ является обязательным.
Оно информирует Solr о том, какой документ является родительским по отношению к обновляемому объекту.
Если поле не указано, Solr отклонит операцию обновления. |
Результат обновления:
{
"responseHeader":{
"zkConnected":true,
"status":0,
"QTime":0,
"params":{
"q":"*:*",
"indent":"true",
"q.op":"OR",
"_":"1726172218281"}},
"response":{"numFound":2,"start":0,"numFoundExact":true,"docs":[
{
"id":"post2!comment3",
"author_s":"Luke",
"text_t":"I like this post!",
"rated_i":5,
"content_type":["comment"],
"_version_":1810022942789074944},
{
"id":"post2",
"name_s":"Another demo post...",
"text_t":"Foo Bar Buzz ....",
"content_type":["post"],
"_version_":1810022942789074944}]
}}
Загрузка следующего документа в Solr добавляет еще один вложенный объект к родительскому документу ("id": "post2"
):
{
"id": "post2",
"comments": { "add": { "id": "post2!comment4",
"author_s": "Rick Sanchez",
"text_t": "Another added comment here..",
"rated_i": 5,
"content_type": "comment"
} }
}
Результат:
{
"responseHeader":{
"zkConnected":true,
"status":0,
"QTime":0,
"params":{
"q":"*:*",
"indent":"true",
"q.op":"OR",
"_":"1726172218281"}},
"response":{"numFound":3,"start":0,"numFoundExact":true,"docs":[
{
"id":"post2!comment3",
"author_s":"Luke",
"text_t":"I like this post!",
"rated_i":5,
"content_type":["comment"],
"_version_":1810023009958756352},
{
"id":"post2!comment4",
"author_s":"Rick Sanchez",
"text_t":"Another added comment here..",
"rated_i":5,
"content_type":["comment"],
"_version_":1810023009958756352},
{
"id":"post2",
"name_s":"Another demo post...",
"text_t":"Foo Bar Buzz ....",
"content_type":["post"],
"_version_":1810023009958756352}]
}}
Отправка следующего документа в Solr удаляет вложенный документ по ID:
{
"id": "post2",
"comments": {
"remove": {
"id": "post2!comment4"
}
}
}
Результат:
{
"responseHeader":{
"zkConnected":true,
"status":0,
"QTime":0,
"params":{
"q":"*:*",
"indent":"true",
"q.op":"OR",
"_":"1726172218281"}},
"response":{"numFound":2,"start":0,"numFoundExact":true,"docs":[
{
"id":"post2!comment3",
"author_s":"Luke",
"text_t":"I like this post!",
"rated_i":5,
"content_type":["comment"],
"_version_":1810023111907606528},
{
"id":"post2",
"name_s":"Another demo post...",
"text_t":"Foo Bar Buzz ....",
"content_type":["post"],
"_version_":1810023111907606528}]
}}
Обновления типа in-place
Обновления типа in-place (in-place updates) являются подмножеством атомарных обновлений. Однако при обычном атомарном обновлении под капотом происходит переиндексация всего документа. При обновлениях типа in-place изменяются только необходимые поля, а остальная часть документа остается нетронутой. Таким образом, эффективность in-place обновлений не зависит от количества полей в обновляемом документе. Кроме внутренних особенностей имплементации, влияющих на производительность, между атомарными и in-place обновлениями нет особых функциональных различий.
Операция обновления может быть выполнена как обновление типа in-place, если обновляемые поля отвечают следующим требованиям:
-
Обновляемые поля являются числовыми и имеют следующие свойства:
indexed="false"
,stored="false"
,multiValued="false"
,docValues="true"
. -
В обновляемом документе присутствует поле
_version_
, которое имеет свойства:indexed="false"
,stored="false"
,multiValued="false"
,docValues="true"
. -
CopyField destination обновляемого поля (если таковые используются) является числовым и имеет следующие свойства:
indexed="false"
,stored="false"
,multiValued="false"
,docValues="true"
.
Для обновлений типа in-place Solr поддерживает следующие модификаторы:
-
set
— устанавливает новое или перезаписывает существующее значение поля. -
inc
— увеличивает числовое поле на определенное значение.
Чтобы обновить числовое поле документа с помощью метода in-place, соответствующее поле должно быть определено в схеме Solr. Например:
<field name="post_rank" type="float" indexed="false" stored="false" docValues="true"/>
Чтобы обновить тестовый документ, используя подход in-place, загрузите в Solr следующий документ:
{
"id": 1,
"post_rank": {
"set": 100
}
}
В этом случае документ будет изменен с помощью стратегии обновления in-place. Результаты операции обновления:
{
"responseHeader":{
"zkConnected":true,
"status":0,
"QTime":0,
"params":{
"q":"*:*",
"indent":"true",
"q.op":"OR",
"_":"1726172218281"}},
"response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[
{
"id":"1",
"post_name":["Updated post name ..."],
"categories":["leisure",
"hobby",
"science"],
"post_rank":[100.0],
"post_date":["2024-01-02T00:00:00Z"],
"post_text":["Lorem ipsum dolor sit amet ..."],
"_version_":1810023165979525120}]
}}
Метод оптимистичного параллелизма
Обновление документов с помощью метода оптимистичного параллелизма (optimistic concurrency) — это функция Solr, которая гарантирует, что один и тот же документ не будет изменен несколькими клиентскими приложениями параллельно.
Эта функция активно использует поле _version_
и ожидает, что данное поле присутствует в каждом документе в индексе Solr.
По умолчанию схема Solr включает поле _version_
, так что поле автоматически добавляется в каждый новый документ.
При обновлении документа Solr сравнивает значение _version_
из индекса со значением, предоставленным в запросе, тем самым гарантируя, что документ не был изменен другими клиентами.
Чтобы обновить документ с помощью метода оптимистичного параллелизма, клиент должен передать актуальное значение _version_
вместе с документом.
Поле _version_
— это обычное поле Solr, которое можно запрашивать так же, как и любое другое поле, например:
$ curl -X GET 'http://ka-adh-1.ru-central1.internal:8983/solr/test_collection/query?q=*:*&fl=id,post_name,_version_&omitHeader=true' -H 'Content-Type: application/json'
Ответ Solr-сервера:
{
"response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[
{
"id":"1",
"post_name":["Sample blog post..."],
"_version_":1809999244663193600}]
}}
Вы можете передать версию документа в Solr двумя способами:
-
Используя параметр запроса
_version_=<version>
в URL. Например:$ curl -X POST 'http://ka-adh-1.ru-central1.internal:8983/solr/test_collection/update?_version_=123' -H 'Content-Type: application/json' --data-binary '[{"id": 1, "updated_field": "updated value"}]'
-
Указав значение
_version_
в обновленном документе. Например:{ "id": 1, "_version_": 123, "post_rank": {"set": 80} }
Этот способ удобен, когда документы отправляются в Solr пакетом и для каждого документа нужно указать разные значения
_version_
.
Если Solr получает некорректную версию документа, он отклоняет запрос на обновление, возвращает HTTP-ответ 409 и следующую ошибку:
"error":{
"metadata":[
"error-class","org.apache.solr.common.SolrException",
"root-error-class","org.apache.solr.common.SolrException"],
"msg":"version conflict for 1 expected=12345 actual=1809999244663193600",
"code":409}
Если версия документа указана корректно, Solr обновляет необходимые поля и присваивает новую версию для обновленного документа. Следующий запрос обновляет тестовый документ, передавая в качестве параметра запроса актуальную версию документа.
$ curl -X POST 'http://ka-adh-1.ru-central1.internal:8983/solr/test_collection/update?_version_=1810003184734699520&versions=true&omitHeader=true' -H 'Content-Type: application/json' --data-binary '[{"id":1,"post_rank": {"set": 80}}]'
Ответ сервера:
{
"adds":[
"1",1810019385852559360]
}
РЕКОМЕНДАЦИЯ
Параметр запроса versions=true добавляет версию документа в каждый ответ Solr-сервера.
Это может быть полезно, чтобы избежать лишних GET-запросов для получения информации о версии документа.
|