Вращение таблицы — это процедура, при которой сервер 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+ ГБ.
При включённом бесшовном вращении, вращение работает следующим образом:
- Выделяется новая таблица для хранения в ОЗУ.
- Асинхронно загружаются в ОЗУ новые данные атрибутов и словаря таблицы.
- При успешном завершении старая таблица освобождается, а файлы обеих таблиц переименовываются.
- При неудаче новая таблица освобождается.
- В любой момент запросы обслуживаются либо из копии старой, либо из копии новой таблицы.
Бесшовное вращение требует большего пикового использования памяти во время вращения (потому что обе копии данных .spa/.spb/.spi/.spm — старые и новые — должны находиться в ОЗУ при предварительной загрузке новой копии). Однако среднее использование остаётся тем же.
Пример:
seamless_rotate = 1
≫ Обновление документов
Вы можете изменить существующие данные в таблице RT или PQ, либо обновляя, либо заменяя их.
UPDATE заменяет поколоночные значения атрибутов существующих документов новыми значениями. Полнотекстовые поля и поколоночные атрибуты не могут быть обновлены. Если необходимо изменить содержимое полнотекстового поля или поколоночных атрибутов, используйте REPLACE.
REPLACE работает аналогично INSERT, за исключением того, что если старый документ имеет тот же ID, что и новый документ, старый документ помечается как удалённый перед вставкой нового. Обратите внимание, что старый документ физически не удаляется из таблицы. Удаление происходит только при слиянии чанков в таблице, например, в результате OPTIMIZE.
Оба UPDATE и частичный REPLACE могут изменить значение поля, но работают они по-разному:
UPDATEможет изменить только поля, которые не являются поколоночными или полнотекстовыми. Этот процесс выполняется на месте и обычно быстрее, чемREPLACE.- Частичный
REPLACEможет изменить любое поле в документе, но требует, чтобы все поля в таблице были установлены как "хранимые" (хотя это и является настройкой по умолчанию). Это не требуется при использовании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 установлен.
Для замены из SELECT:
REPLACE INTO table
SELECT ... FROM source
REPLACE INTO table (column1, column2, column3)
SELECT ... FROM source
ПРИМЕЧАНИЕ: Этот синтаксис требует 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
- REPLACE ... SELECT
- 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;CREATE TABLE products_src (id int, title text, price float, category_id int);
CREATE TABLE products (id int, title text, price float, category_id int);
INSERT INTO products_src VALUES
(1, 'Notebook Stand', 45.00, 10),
(2, 'USB-C Hub', 79.90, 12),
(3, 'Wireless Mouse', 129.00, 10);
REPLACE INTO products_a (id, price)
SELECT id, price FROM products_src;
REPLACE INTO products_b
SELECT * FROM products_src;
REPLACE INTO products_c (id, title, category_id)
SELECT id, title, category_id
FROM products_src
WHERE price >= 100;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)Query OK, 3 rows 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 доступен для реального времени и перколационных таблиц. В обычных таблицах заменить данные нельзя.
Когда вы запускаете REPLACE, предыдущий документ не удаляется, а помечается как удалённый, поэтому размер таблицы растёт до тех пор, пока не произойдёт слияние чанков. Чтобы форсировать слияние чанков, используйте оператор OPTIMIZE.
Вы можете заменить несколько документов одновременно. Подробнее смотрите в разделе массовое добавление документов.
- 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
}