Вращение таблицы — это процедура, при которой сервер searchd ищет новые версии определённых таблиц в конфигурации. Вращение поддерживается только в режиме Plain.
Могут быть два случая:
- для plain-таблиц, которые уже загружены
- таблицы, добавленные в конфигурацию, но ещё не загруженные
В первом случае индексатор не может поставить новую версию таблицы онлайн, так как запущенная копия заблокирована и загружена searchd. В этом случае indexer нужно вызвать с параметром --rotate. Если используется rotate, indexer создаёт новые файлы таблицы с .new. в их именах и посылает сигнал HUP серверу searchd, информируя его о новой версии. searchd выполнит проверку и заменит старую версию таблицы на новую. В некоторых случаях может потребоваться создать новую версию таблицы, но не выполнять вращение сразу. Например, может потребоваться сначала проверить работоспособность новых версий таблиц. В этом случае indexer может принять параметр --nohup, который запретит отправку сигнала HUP серверу.
Новые таблицы могут быть загружены через вращение; однако обычная обработка сигнала HUP заключается в проверке новых таблиц только если конфигурация изменилась с момента запуска сервера. Если таблица уже была определена в конфигурации, таблицу следует сначала создать, запустив indexer без вращения, и выполнить оператор RELOAD TABLES.
Также существуют два специализированных оператора, которые можно использовать для выполнения вращения таблиц:
RELOAD TABLE tbl [ FROM '/path/to/table_files' [ OPTION switchover=1 ] ];
Команда RELOAD TABLE позволяет выполнять вращение таблицы через SQL.
Эта команда работает в трёх режимах. В первом режиме, без указания пути, сервер Manticore проверяет наличие новых файлов таблицы в каталоге, указанном в параметре path. Новые файлы таблицы должны иметь имена вида tbl.new.sp?.
Если указать путь, сервер ищет файлы таблицы в этом каталоге, перемещает их в каталог таблицы, указанный в path, переименовывает их с tbl.sp? в tbl.new.sp? и выполняет вращение.
Третий режим, активируемый опцией OPTION switchover=1, переключает индекс на новый путь. Здесь демон пытается загрузить таблицу непосредственно из нового пути без перемещения файлов. Если загрузка успешна, этот новый индекс заменяет старый.
Также демон записывает уникальный файл-ссылку (tbl.link) в каталог, указанный в path, обеспечивая постоянное перенаправление.
Если вы возвращаете перенаправленный индекс к пути, указанному в конфигурации, демон обнаружит это и удалит файл-ссылку.
После перенаправления демон получает таблицу из нового связанного пути. При вращении он ищет новые версии таблиц по новому перенаправленному пути. Учтите, что демон проверяет конфигурацию на общие ошибки, такие как дублирование путей для разных таблиц. Однако он не выявит, если несколько таблиц указывают на один и тот же путь через перенаправление. В нормальных условиях таблицы блокируются файлом .spl, но отключение блокировки может вызвать проблемы. Если возникает ошибка (например, путь недоступен по какой-либо причине), вам следует вручную исправить (или просто удалить) файл-ссылку.
indextool следует за файлом-ссылкой, но другие инструменты (indexer, index_converter и т. п.) не распознают файл-ссылку и всегда используют путь, определённый в конфигурационном файле, игнорируя перенаправление. Таким образом, вы можете проверить индекс с помощью indextool, и он будет читать из нового расположения. Однако более сложные операции, такие как слияние, не будут учитывать файл-ссылку.
mysql> RELOAD TABLE plain_table;
mysql> RELOAD TABLE plain_table FROM '/home/mighty/new_table_files';
mysql> RELOAD TABLE plain_table FROM '/home/mighty/new/place/for/table/table_files' OPTION switchover=1;
RELOAD TABLES;
Эта команда работает аналогично системному сигналу HUP, вызывая вращение таблиц. Тем не менее, она не полностью повторяет типичный сигнал HUP (который может быть вызван командой kill -HUP или indexer --rotate). Эта команда активно ищет таблицы, требующие вращения, и способна перечитывать конфигурацию. Предположим, вы запускаете Manticore в режиме plain с конфигурационным файлом, указывающим на несуществующую plain-таблицу. Если затем попытаться выполнить indexer --rotate для этой таблицы, сервер не распознает новую таблицу, пока вы не выполните RELOAD TABLES или не перезапустите сервер.
В зависимости от значения параметра seamless_rotate новые запросы могут быть временно приостановлены, и клиенты получат временные ошибки.
mysql> RELOAD TABLES;
Query OK, 0 rows affected (0.01 sec)
Вращение предполагает, что старая версия таблицы отбрасывается, а новая версия загружается и заменяет существующую. Во время этой замены сервер должен также обслуживать входящие запросы к таблице, которая обновляется. Чтобы избежать задержек запросов, сервер по умолчанию реализует плавное вращение таблицы, как описано ниже.
Таблицы могут содержать данные, которые нужно предварительно загрузить в ОЗУ. В настоящее время файлы .spa, .spb, .spi и .spm полностью предварительно загружаются (они содержат данные атрибутов, данные блоб-атрибутов, таблицу ключевых слов и карту удалённых строк соответственно). Без плавного вращения вращение таблицы старается использовать как можно меньше ОЗУ и работает следующим образом:
- Новые запросы временно отклоняются (с кодом ошибки "retry").
searchdждёт завершения всех текущих запросов.- Старая таблица освобождается, и её файлы переименовываются.
- Файлы новой таблицы переименовываются, и выделяется необходимая ОЗУ.
- Данные атрибутов и словаря новой таблицы предварительно загружаются в ОЗУ.
searchdвозобновляет обслуживание запросов из новой таблицы.
Однако если данных атрибутов или словаря много, этап предварительной загрузки может занять заметное время — до нескольких минут при загрузке файлов размером 1-5+ ГБ.
С включённым бесшовным поворотом, вращение работает следующим образом:
- Выделяется новое RAM-хранилище для таблицы.
- Асинхронно загружаются в RAM новые данные атрибутов и словаря таблицы.
- В случае успеха, старая таблица освобождается, и файлы обеих таблиц переименовываются.
- В случае неудачи, новая таблица освобождается.
- В любой момент запросы обслуживаются либо из старой, либо из новой копии таблицы.
Бесшовный поворот требует большего пикового использования памяти во время вращения (потому что обе копии данных .spa/.spb/.spi/.spm — старая и новая — должны находиться в RAM во время предварительной загрузки новой копии). Однако среднее использование остаётся прежним.
Пример:
seamless_rotate = 1
≫ Обновление документов
You can modify existing data in an RT or PQ table by either updating or replacing it.
UPDATE заменяет построчно значения атрибутов существующих документов новыми значениями. Полнотекстовые поля и колоннарные атрибуты нельзя обновлять. Если вам нужно изменить содержимое полнотекстового поля или колоннарных атрибутов, используйте REPLACE.
REPLACE работает аналогично INSERT, за исключением того, что если у старого документа тот же ID, что и у нового, старый документ помечается как удалённый перед вставкой нового документа. Обратите внимание, что старый документ физически не удаляется из таблицы. Удаление может произойти только при слиянии чанков в таблице, например, в результате OPTIMIZE.
Both UPDATE and a partial REPLACE can change the value of a field, but they operate differently:
UPDATEможет изменять только поля, которые не являются колоннарными или полнотекстовыми. Этот процесс выполняется на месте, что обычно быстрее, чемREPLACE.- Частичный
REPLACEможет изменить любое поле в документе, но требует, чтобы все поля в таблице были установлены как "stored" (хотя это настройка по умолчанию). Это не требуется при использованииUPDATE.
REPLACE работает аналогично INSERT, но перед вставкой нового документа помечает предыдущий документ с тем же ID как удалённый.
Если вам нужны обновления на месте, пожалуйста, смотрите этот раздел.
Синтаксис SQL оператора REPLACE следующий:
Для замены всего документа:
REPLACE INTO table [(column1, column2, ...)]
VALUES (value1, value2, ...)
[, (...)]
Столбцы, явно не включённые в SQL-запрос, устанавливаются в значения по умолчанию, такие как 0 или пустая строка, в зависимости от их типа данных.
Для замены только выбранных полей:
REPLACE INTO table
SET field1=value1[, ..., fieldN=valueN]
WHERE id = <id>
Обратите внимание, что в этом режиме фильтровать можно только по id.
ПРИМЕЧАНИЕ: Частичная замена требует Manticore Buddy. Если не работает, убедитесь, что Buddy установлен.
Подробнее о UPDATE и частичной замене REPLACE читайте здесь.
Смотрите примеры для более подробной информации.
-
/replace:POST /replace { "table": "<table name>", "id": <document id>, "doc": { "<field1>": <value1>, ... "<fieldN>": <valueN> } }/index— это псевдоним эндпоинта и работает так же. -
Эндпоинт в стиле Elasticsearch
<table>/_doc/<id>:PUT/POST /<table name>/_doc/<id> { "<field1>": <value1>, ... "<fieldN>": <valueN> }ПРИМЕЧАНИЕ: Замена в стиле Elasticsearch требует Manticore Buddy. Если не работает, убедитесь, что Buddy установлен.
-
Частичная замена:
POST /<{table | cluster:table}>/_update/<id> { "<field1>": <value1>, ... "<fieldN>": <valueN> }<table name>может быть просто именем таблицы или в форматеcluster:table. Это позволяет выполнять обновления по конкретному кластеру при необходимости.ПРИМЕЧАНИЕ: Частичная замена требует Manticore Buddy. Если не работает, убедитесь, что Buddy установлен.
Смотрите примеры для более подробной информации.
- SQL
- REPLACE SET
- JSON
- Elasticsearch-like
- Elasticsearch-like partial
- Elasticsearch-like partial in cluster
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- TypeScript
- Go
REPLACE INTO products VALUES(1, "document one", 10);REPLACE INTO products SET description='HUAWEI Matebook 15', price=10 WHERE id = 55;POST /replace
-H "Content-Type: application/x-ndjson" -d '
{
"table":"products",
"id":1,
"doc":
{
"title":"product one",
"price":10
}
}
'ПРИМЕЧАНИЕ: Замена в стиле Elasticsearch требует Manticore Buddy. Если не работает, убедитесь, что Buddy установлен.
PUT /products/_doc/2
{
"title": "product two",
"price": 20
}
POST /products/_doc/3
{
"title": "product three",
"price": 10
}ПРИМЕЧАНИЕ: Частичная замена требует Manticore Buddy. Если не работает, убедитесь, что Buddy установлен.
POST /products/_update/55
{
"doc": {
"description": "HUAWEI Matebook 15",
"price": 10
}
}POST /cluster_name:products/_update/55
{
"doc": {
"description": "HUAWEI Matebook 15",
"price": 10
}
}$index->replaceDocument([
'title' => 'document one',
'price' => 10
],1);indexApi.replace({"table" : "products", "id" : 1, "doc" : {"title" : "document one","price":10}})await indexApi.replace({"table" : "products", "id" : 1, "doc" : {"title" : "document one","price":10}})res = await indexApi.replace({"table" : "products", "id" : 1, "doc" : {"title" : "document one","price":10}});docRequest = new InsertDocumentRequest();
HashMap<String,Object> doc = new HashMap<String,Object>(){{
put("title","document one");
put("price",10);
}};
docRequest.index("products").id(1L).setDoc(doc);
sqlresult = indexApi.replace(docRequest);Dictionary<string, Object> doc = new Dictionary<string, Object>();
doc.Add("title", "document one");
doc.Add("price", 10);
InsertDocumentRequest docRequest = new InsertDocumentRequest(index: "products", id: 1, doc: doc);
var sqlresult = indexApi.replace(docRequest);let mut doc = HashMap::new();
doc.insert("title".to_string(), serde_json::json!("document one"));
doc.insert("price".to_string(), serde_json::json!(10));
let insert_req = InsertDocumentRequest::new("products".to_string(), serde_json::json!(doc));
let insert_res = index_api.replace(insert_req).await;res = await indexApi.replace({
index: 'test',
id: 1,
doc: { content: 'Text 11', name: 'Doc 11', cat: 3 },
});replaceDoc := map[string]interface{} {"content": "Text 11", "name": "Doc 11", "cat": 3}
replaceRequest := manticoreclient.NewInsertDocumentRequest("test", replaceDoc)
replaceRequest.SetId(1)
res, _, _ := apiClient.IndexAPI.Replace(context.Background()).InsertDocumentRequest(*replaceRequest).Execute()Query OK, 1 row affected (0.00 sec)Query OK, 1 row affected (0.00 sec){
"table":"products",
"_id":1,
"created":false,
"result":"updated",
"status":200
}{
"_id":2,
"table":"products",
"_primary_term":1,
"_seq_no":0,
"_shards":{
"failed":0,
"successful":1,
"total":1
},
"_type":"_doc",
"_version":1,
"result":"updated"
}
{
"_id":3,
"table":"products",
"_primary_term":1,
"_seq_no":0,
"_shards":{
"failed":0,
"successful":1,
"total":1
},
"_type":"_doc",
"_version":1,
"result":"updated"
}{
"table":"products",
"updated":1
}{
"table":"products",
"updated":1
}Array(
[_index] => products
[_id] => 1
[created] => false
[result] => updated
[status] => 200
){'created': False,
'found': None,
'id': 1,
'table': 'products',
'result': 'updated'}{'created': False,
'found': None,
'id': 1,
'table': 'products',
'result': 'updated'}{"table":"products","_id":1,"result":"updated"}class SuccessResponse {
index: products
id: 1
created: false
result: updated
found: null
}class SuccessResponse {
index: products
id: 1
created: false
result: updated
found: null
}class SuccessResponse {
index: products
id: 1
created: false
result: updated
found: null
}{
"table":"test",
"_id":1,
"created":false
"result":"updated"
"status":200
}{
"table":"test",
"_id":1,
"created":false
"result":"updated"
"status":200
}REPLACE доступен для real-time и percolate таблиц. Нельзя заменять данные в plain таблице.
При выполнении REPLACE предыдущий документ не удаляется, а помечается как удалённый, поэтому размер таблицы растёт до тех пор, пока не произойдёт слияние чанков. Чтобы принудительно выполнить слияние чанков, используйте оператор OPTIMIZE.
Вы можете заменить несколько документов одновременно. Подробнее смотрите в разделе bulk adding documents.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- TypeScript
- Go
REPLACE INTO products(id,title,tag) VALUES (1, 'doc one', 10), (2,' doc two', 20);POST /bulk
-H "Content-Type: application/x-ndjson" -d '
{ "replace" : { "table" : "products", "id":1, "doc": { "title": "doc one", "tag" : 10 } } }
{ "replace" : { "table" : "products", "id":2, "doc": { "title": "doc two", "tag" : 20 } } }
'$index->replaceDocuments([
[
'id' => 1,
'title' => 'document one',
'tag' => 10
],
[
'id' => 2,
'title' => 'document one',
'tag' => 20
]
);indexApi = manticoresearch.IndexApi(client)
docs = [ \
{"replace": {"table" : "products", "id" : 1, "doc" : {"title" : "document one"}}}, \
{"replace": {"table" : "products", "id" : 2, "doc" : {"title" : "document two"}}} ]
api_resp = indexApi.bulk('\n'.join(map(json.dumps,docs)))indexApi = manticoresearch.IndexApi(client)
docs = [ \
{"replace": {"table" : "products", "id" : 1, "doc" : {"title" : "document one"}}}, \
{"replace": {"table" : "products", "id" : 2, "doc" : {"title" : "document two"}}} ]
api_resp = await indexApi.bulk('\n'.join(map(json.dumps,docs)))docs = [
{"replace": {"table" : "products", "id" : 1, "doc" : {"title" : "document one"}}},
{"replace": {"table" : "products", "id" : 2, "doc" : {"title" : "document two"}}} ];
res = await indexApi.bulk(docs.map(e=>JSON.stringify(e)).join('\n'));body = "{\"replace\": {\"index\" : \"products\", \"id\" : 1, \"doc\" : {\"title\" : \"document one\"}}}" +"\n"+
"{\"replace\": {\"index\" : \"products\", \"id\" : 2, \"doc\" : {\"title\" : \"document two\"}}}"+"\n" ;
indexApi.bulk(body);string body = "{\"replace\": {\"index\" : \"products\", \"id\" : 1, \"doc\" : {\"title\" : \"document one\"}}}" +"\n"+
"{\"replace\": {\"index\" : \"products\", \"id\" : 2, \"doc\" : {\"title\" : \"document two\"}}}"+"\n" ;
indexApi.Bulk(body);string body = r#"{"replace": {"index" : "products", "id" : 1, "doc" : {"title" : "document one"}}}
{"replace": {"index" : "products", "id" : 2, "doc" : {"title" : "document two"}}}
"#;
index_api.bulk(body).await;replaceDocs = [
{
replace: {
index: 'test',
id: 1,
doc: { content: 'Text 11', cat: 1, name: 'Doc 11' },
},
},
{
replace: {
index: 'test',
id: 2,
doc: { content: 'Text 22', cat: 9, name: 'Doc 22' },
},
},
];
res = await indexApi.bulk(
replaceDocs.map((e) => JSON.stringify(e)).join("\n")
);body := "{\"replace\": {\"index\": \"test\", \"id\": 1, \"doc\": {\"content\": \"Text 11\", \"name\": \"Doc 11\", \"cat\": 1 }}}" + "\n" +
"{\"replace\": {\"index\": \"test\", \"id\": 2, \"doc\": {\"content\": \"Text 22\", \"name\": \"Doc 22\", \"cat\": 9 }}}" +"\n";
res, _, _ := apiClient.IndexAPI.Bulk(context.Background()).Body(body).Execute()Query OK, 2 rows affected (0.00 sec){
"items":
[
{
"replace":
{
"table":"products",
"_id":1,
"created":false,
"result":"updated",
"status":200
}
},
{
"replace":
{
"table":"products",
"_id":2,
"created":false,
"result":"updated",
"status":200
}
}
],
"errors":false
}Array(
[items] =>
Array(
Array(
[_index] => products
[_id] => 2
[created] => false
[result] => updated
[status] => 200
)
Array(
[_index] => products
[_id] => 2
[created] => false
[result] => updated
[status] => 200
)
)
[errors => false
){'error': None,
'items': [{u'replace': {u'_id': 1,
u'table': u'products',
u'created': False,
u'result': u'updated',
u'status': 200}},
{u'replace': {u'_id': 2,
u'table': u'products',
u'created': False,
u'result': u'updated',
u'status': 200}}]}{'error': None,
'items': [{u'replace': {u'_id': 1,
u'table': u'products',
u'created': False,
u'result': u'updated',
u'status': 200}},
{u'replace': {u'_id': 2,
u'table': u'products',
u'created': False,
u'result': u'updated',
u'status': 200}}]}{"items":[{"replace":{"table":"products","_id":1,"created":false,"result":"updated","status":200}},{"replace":{"table":"products","_id":2,"created":false,"result":"updated","status":200}}],"errors":false}class BulkResponse {
items: [{replace={_index=products, _id=1, created=false, result=updated, status=200}}, {replace={_index=products, _id=2, created=false, result=updated, status=200}}]
error: null
additionalProperties: {errors=false}
}class BulkResponse {
items: [{replace={_index=products, _id=1, created=false, result=updated, status=200}}, {replace={_index=products, _id=2, created=false, result=updated, status=200}}]
error: null
additionalProperties: {errors=false}
}class BulkResponse {
items: [{replace={_index=products, _id=1, created=false, result=updated, status=200}}, {replace={_index=products, _id=2, created=false, result=updated, status=200}}]
error: null
additionalProperties: {errors=false}
}{
"items":
[
{
"replace":
{
"table":"test",
"_id":1,
"created":false,
"result":"updated",
"status":200
}
},
{
"replace":
{
"table":"test",
"_id":2,
"created":false,
"result":"updated",
"status":200
}
}
],
"errors":false
}{
"items":
[
{
"replace":
{
"table":"test",
"_id":1,
"created":false,
"result":"updated",
"status":200
}
},
{
"replace":
{
"table":"test",
"_id":2,
"created":false,
"result":"updated",
"status":200
}
}
],
"errors":false
}