С Manticore транзакции записи (такие как INSERT, REPLACE, DELETE, TRUNCATE, UPDATE, COMMIT) могут реплицироваться на другие узлы кластера до полного применения транзакции на текущем узле. В настоящее время репликация поддерживается для таблиц percolate, rt и distributed в Linux и macOS.
Нативные бинарные файлы Windows для Manticore не поддерживают репликацию. Мы рекомендуем устанавливать Manticore через WSL (Подсистема Windows для Linux).
На macOS репликация имеет ограниченную поддержку и рекомендуется только для целей разработки.
Репликация Manticore базируется на библиотеке Galera и обладает несколькими впечатляющими возможностями:
- Истинный multi-master: чтение и запись на любой узел в любое время.
- Почти синхронная репликация — отсутствие задержек ведомых и потери данных после сбоя узла.
- Горячий резерв: отсутствие времени простоя при переключении (так как переключения нет).
- Плотно связанные узлы: все узлы содержат одно и то же состояние, расхождений данных между узлами не допускается.
- Автоматическое добавление узлов: нет необходимости вручную создавать резервные копии базы и восстанавливать их на новом узле.
- Простота использования и развёртывания.
- Обнаружение и автоматическое удаление ненадёжных узлов.
- Репликация на основе сертификации.
Для настройки репликации в Manticore Search:
- Опция data_dir должна быть задана в разделе "searchd" конфигурационного файла. Репликация не поддерживается в plain-режиме.
- Директива listen должна содержать IP-адрес, доступный для других узлов, либо должен быть указан node_address с доступным IP-адресом.
- По желанию, можно задать уникальные значения для server_id на каждом узле кластера. Если значение не задано, узел попытается использовать MAC-адрес или случайное число для генерации
server_id.
Если включены аутентификация и авторизация, для операций с кластером требуется разрешение replication. Выдайте его пользователю, который должен владеть операциями репликации для кластера:
GRANT replication ON 'posts' TO 'repl_user';
CREATE CLUSTER и JOIN CLUSTER могут указывать такого пользователя через '<user>' AS user. Пользователь должен существовать с совпадающими сохраненными данными аутентификации на узлах, участвующих в операции. Просто создать одно и то же имя пользователя и пароль независимо на каждом узле недостаточно, потому что сохраненные данные аутентификации могут отличаться. Если вы изменили данные аутентификации вне демона, выполните RELOAD AUTH на затронутых узлах перед использованием операции с кластером.
Позднее ALTER CLUSTER ... ADD, ALTER CLUSTER ... DROP, ALTER CLUSTER ... UPDATE nodes и DELETE CLUSTER используют сохраненного пользовательского кластера. Когда аутентификация включена, успешный JOIN CLUSTER заменяет все локальные данные аутентификации на присоединяющемся узле данными аутентификации кластера-источника.
Если директива replication listen не задана, Manticore использует первые два свободных порта из диапазона в 200 портов после порта прослушивания протокола по умолчанию для каждого созданного кластера. Для ручного задания портов репликации необходимо определить диапазон портов в директиве listen (типа replication), при этом пары адрес/диапазон портов не должны пересекаться между разными узлами на одном сервере. Как правило, диапазон портов должен содержать как минимум два порта на кластер. Когда вы определяете слушатель репликации с диапазоном портов (например, listen = 192.168.0.1:9320-9328:replication), Manticore не начинает прослушивать эти порты сразу. Вместо этого система будет выбирать случайные свободные порты из указанного диапазона только при начале использования репликации.
Репликационный кластер — это группа узлов, в которой реплицируются транзакции записи. Репликация настраивается на уровне таблицы, то есть одна таблица может принадлежать только одному кластеру. Нет ограничений на количество таблиц в кластере. Все операции INSERT, REPLACE, DELETE, TRUNCATE для любой percolate или real-time таблицы, входящей в кластер, реплицируются на все другие узлы этого кластера. В репликацию также могут быть включены distributed таблицы. Репликация является multi-master, поэтому записи на любой узел или несколько узлов одновременно работают корректно.
Для создания кластера обычно используется команда create cluster с CREATE CLUSTER <имя кластера>, а для присоединения к кластеру — команда join cluster с JOIN CLUSTER <имя кластера> at 'host:port'. Однако в некоторых редких случаях может понадобиться тонкая настройка поведения CREATE/JOIN CLUSTER. Доступные опции:
Эта опция задаёт имя кластера. Оно должно быть уникальным среди всех кластеров в системе.
Примечание: Максимальная длина имени хоста для команды JOIN составляет 253 символа. Если предел превышен, searchd выдаст ошибку.
Параметр path задаёт каталог данных для репликации кэша write-set и других файлов провайдера кластера. Он не влияет на место хранения реплицируемых таблиц. Входящие реплицируемые таблицы сохраняются в обычном каталоге таблицы внутри data_dir. Это значение должно быть уникальным среди всех кластеров в системе и задаваться как относительный путь к каталогу data_dir. По умолчанию оно равно значению data_dir.
Критическое изменение: В более старых версиях входящие файлы реплицируемых таблиц тоже сохранялись по пути кластера. Если вы использовали собственный path кластера, обновляйтесь осторожно, потому что реплицируемые таблицы, полученные старыми версиями, может потребоваться переместить или повторно синхронизировать в обычную структуру data_dir/<table>.
Опция nodes — это список адресов и портов всех узлов в кластере, разделённых запятыми. Этот список должен быть получен через API узла и может включать адрес текущего узла. Он используется для присоединения узла к кластеру и для повторного присоединения после перезапуска.
Опция options позволяет передавать дополнительные параметры непосредственно плагину репликации Galera, как описано в Galera Documentation Parameters
При работе с репликационным кластером все операторы записи, такие как INSERT, REPLACE, DELETE, TRUNCATE, UPDATE, которые изменяют содержимое таблицы кластера, должны использовать выражение cluster_name:table_name вместо имени таблицы. Это гарантирует, что изменения будут распространены на все реплики в кластере. Если использовать неправильное выражение, будет выдана ошибка.
В JSON-интерфейсе свойство cluster должно быть установлено вместе с именем table для всех операций записи в таблицу кластера. Если свойство cluster не установлено, это приведет к ошибке.
Автоматический ID для таблицы в кластере будет действителен, если server_id настроен правильно.
INSERT INTO posts:weekly_table VALUES ( 'iphone case' )
TRUNCATE TABLE click_query:weekly_table
UPDATE INTO posts:rt_tags SET tags=(101, 302, 304) WHERE MATCH ('use') AND id IN (1,101,201)
DELETE FROM clicks:rt WHERE MATCH ('dumy') AND gid>206
POST /insert -d '
{
"cluster":"posts",
"table":"weekly_table",
"doc":
{
"title" : "iphone case",
"price" : 19.85
}
}'
POST /delete -d '
{
"cluster":"posts",
"table": "weekly_table",
"id":1
}'
$table->addDocuments([
1, ['title' => 'iphone case', 'price' => 19.85]
]);
$table->deleteDocument(1);
indexApi.insert({"cluster":"posts","table":"weekly_table","doc":{"title":"iphone case","price":19.85}})
indexApi.delete({"cluster":"posts","table":"weekly_table","id":1})
await indexApi.insert({"cluster":"posts","table":"weekly_table","doc":{"title":"iphone case","price":19.85}})
await indexApi.delete({"cluster":"posts","table":"weekly_table","id":1})
res = await indexApi.insert({"cluster":"posts","table":"weekly_table","doc":{"title":"iphone case","price":19.85}});
res = await indexApi.delete({"cluster":"posts","table":"weekly_table","id":1});
InsertDocumentRequest newdoc = new InsertDocumentRequest();
HashMap<String,Object> doc = new HashMap<String,Object>(){{
put("title","Crossbody Bag with Tassel");
put("price",19.85);
}};
newdoc.table("weekly_table").cluster("posts").id(1L).setDoc(doc);
sqlresult = indexApi.insert(newdoc);
DeleteDocumentRequest deleteRequest = new DeleteDocumentRequest();
deleteRequest.table("weekly_table").cluster("posts").setId(1L);
indexApi.delete(deleteRequest);
Dictionary<string, Object> doc = new Dictionary<string, Object>();
doc.Add("title", "Crossbody Bag with Tassel");
doc.Add("price", 19.85);
InsertDocumentRequest newdoc = new InsertDocumentRequest(table: "weekly_table", cluster:posts, id: 1, doc: doc);
var sqlresult = indexApi.Insert(newdoc);
DeleteDocumentRequest deleteDocumentRequest = new DeleteDocumentRequest(table: "weekly_table", cluster: "posts", id: 1);
indexApi.Delete(deleteDocumentRequest);
let mut doc = HashMap::new();
doc.insert("title".to_string(), serde_json::json!("Crossbody Bag with Tassel"));
doc.insert("price".to_string(), serde_json::json!(19.85));
let insert_req = InsertDocumentRequest {
table: serde_json::json!("weekly_table"),
doc: serde_json::json!(doc),
cluster: serde_json::json!("posts"),
id: serde_json::json!(1),
};
let insert_res = index_api.insert(insert_req).await;
let delete_req = DeleteDocumentRequest {
table: serde_json::json!("weekly_table"),
cluster: serde_json::json!("posts"),
id: serde_json::json!(1),
};
index_api.delete(delete_req).await;
Операции чтения, такие как SELECT, CALL PQ, DESCRIBE, могут использовать обычные имена таблиц без префикса имени кластера или могут использовать формат cluster_name:table_name. Если используется последний, компонент cluster_name игнорируется.
При использовании HTTP-эндпоинта json/search свойство cluster можно указать при желании, но его также можно опустить.
SELECT * FROM weekly_table
CALL PQ('posts:weekly_table', 'document is here')
POST /search -d '
{
"cluster":"posts",
"table":"weekly_table",
"query":{"match":{"title":"keyword"}}
}'
POST /search -d '
{
"table":"weekly_table",
"query":{"match":{"title":"keyword"}}
}'
SET CLUSTER click_query GLOBAL 'pc.bootstrap' = 1
POST /cli -d "
SET CLUSTER click_query GLOBAL 'pc.bootstrap' = 1
"
Возможна ситуация, когда реплицированные узлы расходятся друг от друга, что приводит к состоянию, когда все узлы помечены как non-primary. Это может произойти в результате сетевого разделения между узлами, сбоя кластера или если плагин репликации столкнется с исключением при определении primary component. В таком сценарии необходимо выбрать узел и повысить его до роли primary component.
Чтобы определить узел, который нужно повысить, следует сравнить значение переменной состояния кластера last_committed на всех узлах. Если все серверы в настоящее время работают, нет необходимости перезапускать кластер. Вместо этого можно просто повысить узел с наибольшим значением last_committed до primary component с помощью оператора SET (как показано в примере).
Остальные узлы затем переподключатся к основному компоненту и повторно синхронизируют свои данные на основе этого узла.
SET CLUSTER posts GLOBAL 'pc.bootstrap' = 1
POST /cli -d "
SET CLUSTER posts GLOBAL 'pc.bootstrap' = 1
"
Чтобы использовать репликацию, в конфигурационном файле нужно определить один порт listen для протокола SphinxAPI и один listen для адреса репликации и диапазона портов. Также укажите каталог data_dir для хранения входящих реплицируемых таблиц.
searchd {
listen = 9312
listen = 192.168.1.101:9360-9370:replication
data_dir = /var/lib/manticore/
...
}
Для репликации таблиц необходимо создать кластер на сервере, который содержит локальные таблицы для репликации.
POST /cli -d "
CREATE CLUSTER posts
"
$params = [
'cluster' => 'posts'
]
];
$response = $client->cluster()->create($params);
utilsApi.sql('CREATE CLUSTER posts')
await utilsApi.sql('CREATE CLUSTER posts')
res = await utilsApi.sql('CREATE CLUSTER posts');
utilsApi.sql("CREATE CLUSTER posts");
utilsApi.Sql("CREATE CLUSTER posts");
utils_api.sql("CREATE CLUSTER posts", Some(true)).await;
Добавьте эти локальные таблицы в кластер
ALTER CLUSTER posts ADD pq_title
ALTER CLUSTER posts ADD pq_clicks
POST /cli -d "
ALTER CLUSTER posts ADD pq_title
"
POST /cli -d "
ALTER CLUSTER posts ADD pq_clicks
"
$params = [
'cluster' => 'posts',
'body' => [
'operation' => 'add',
'table' => 'pq_title'
]
];
$response = $client->cluster()->alter($params);
$params = [
'cluster' => 'posts',
'body' => [
'operation' => 'add',
'table' => 'pq_clicks'
]
];
$response = $client->cluster()->alter($params);
utilsApi.sql('ALTER CLUSTER posts ADD pq_title')
utilsApi.sql('ALTER CLUSTER posts ADD pq_clicks')
await utilsApi.sql('ALTER CLUSTER posts ADD pq_title')
await utilsApi.sql('ALTER CLUSTER posts ADD pq_clicks')
res = await utilsApi.sql('ALTER CLUSTER posts ADD pq_title');
res = await utilsApi.sql('ALTER CLUSTER posts ADD pq_clicks');
utilsApi.sql("ALTER CLUSTER posts ADD pq_title");
utilsApi.sql("ALTER CLUSTER posts ADD pq_clicks");
utilsApi.Sql("ALTER CLUSTER posts ADD pq_title");
utilsApi.Sql("ALTER CLUSTER posts ADD pq_clicks");
utils_api.sql("ALTER CLUSTER posts ADD pq_title", Some(true)).await;
utils_api.sql("ALTER CLUSTER posts ADD pq_clicks", Some(true)).await;
Все остальные узлы, которые хотят получить реплику таблиц кластера, должны присоединиться к кластеру следующим образом:
JOIN CLUSTER posts AT '192.168.1.101:9312'
POST /cli -d "
JOIN CLUSTER posts AT '192.168.1.101:9312'
"
$params = [
'cluster' => 'posts',
'body' => [
'192.168.1.101:9312'
]
];
$response = $client->cluster->join($params);
utilsApi.sql('JOIN CLUSTER posts AT \'192.168.1.101:9312\'')
await utilsApi.sql('JOIN CLUSTER posts AT \'192.168.1.101:9312\'')
res = await utilsApi.sql('JOIN CLUSTER posts AT \'192.168.1.101:9312\'');
utilsApi.sql("JOIN CLUSTER posts AT '192.168.1.101:9312'");
utilsApi.Sql("JOIN CLUSTER posts AT '192.168.1.101:9312'");
utils_api.sql("JOIN CLUSTER posts AT '192.168.1.101:9312'", Some(true)).await;
При выполнении запросов добавьте к имени таблицы префикс имени кластера posts: или используйте свойство cluster для объекта HTTP-запроса.
INSERT INTO posts:pq_title VALUES ( 3, 'test me' )
POST /insert -d '
{
"cluster":"posts",
"table":"pq_title",
"id": 3
"doc":
{
"title" : "test me"
}
}'
$table->addDocuments([
3, ['title' => 'test me']
]);
indexApi.insert({"cluster":"posts","table":"pq_title","id":3"doc":{"title":"test me"}})
await indexApi.insert({"cluster":"posts","table":"pq_title","id":3"doc":{"title":"test me"}})
res = await indexApi.insert({"cluster":"posts","table":"pq_title","id":3"doc":{"title":"test me"}});
InsertDocumentRequest newdoc = new InsertDocumentRequest();
HashMap<String,Object> doc = new HashMap<String,Object>(){{
put("title","test me");
}};
newdoc.table("pq_title").cluster("posts").id(3L).setDoc(doc);
sqlresult = indexApi.insert(newdoc);
Dictionary<string, Object> doc = new Dictionary<string, Object>();
doc.Add("title", "test me");
InsertDocumentRequest newdoc = new InsertDocumentRequest(table: "pq_title", cluster: "posts", id: 3, doc: doc);
var sqlresult = indexApi.Insert(newdoc);
let mut doc = HashMap::new();
doc.insert("title".to_string(), serde_json::json!("test me"));
let insert_req = InsertDocumentRequest {
table: serde_json::json!("pq_title"),
doc: serde_json::json!(doc),
cluster: serde_json::json!("posts"),
id: serde_json::json!(3),
};
let insert_res = index_api.insert(insert_req).await;
Все запросы, которые изменяют таблицы в кластере, теперь реплицируются на все узлы в кластере.