Репликация в Reindexer

В Reindexer реализована асинхронная репликация и синхронная с поддержкой RAFT-like алгоритма консенсуса, совместимая с асинхронной.

Журнал упреждающей записи (WAL)

Каждый запрос на изменение данных в Reindexer записывается в журнал упреждающей записи (WAL), который хранится как часть неймспейса. Каждая запись в WAL имеет уникальный 64-битный регистрационный номер (LSN), который также содержит идентификатор сервера.

Журнал упреждающей записи — это кольцевая структура. Список перезаписывается после N обновлений (по умолчанию количество записей в WAL=4000000). По достижении количества записей N самые старые автоматически удаляются.

WAL используется для частичной синхронизации между отдельными неймспейсами Leader и Follower. Например, в ситуациях временной потери связности между узлами или их перезапуском. В таких случаях Leader сверяет свой текущий WAL и WAL Follower. Если последняя запись, присутвующая на follower-ноде всё ещё доступна на Leader, то будет выполнена попытка последовательного применения отсутвующих записей на Follower через механизм снэпштов с последующей проверкой контрольных сумм. В случае непредвиденных логических ошибок или, если отставание между узлами слишком велико (превышает wal_size) для достижения консистетного состояния будет выполнена полная синхронизация неймпейса.

В WAL записываются только запросы на изменение данных (добавление, обновление, удаление, изменение структуры индексов). Операции добавления/обновления документов не хранятся как выделенные записи в WAL, но каждый документ имеет свой собственный LSN — этого достаточно, чтобы восстановить полный WAL в оперативной памяти при загрузке неймспейса с диска.

Побочным эффектом этого механизма является отсутствие точной последовательности обновлений индексов и данных. Поэтому в случае несовместимой миграции данных (например, при изменении типа индексного поля) Follower вернется к полной синхронизации (force sync).

Оверхед WAL равен 18 байтам на каждую запись об изменении данных.

WAL в Reindexer используется исключительно для механизмов репликации. Он не даёт никаких дополнительных гарантий при записи данных на диск и сам попадает в сторадж асинхронно через LevelDB/RocksDB, как и другие данные.

Полная синхронизация (force sync)

Полная синхронизация подразумевает полное копирование данных во временный неймспейс (его имя будет начинаться с перфикса @tmp_) и повторное заполнение индексов и дискового стораджа с последующей атомарной заменой старого неймспейса на новый среплицированный. Во-первых, такой вид синхронизации требует дополнтиельной оперативной памяти, достаточной для полной in-memory копии целевого неймспейса, а во-вторых, при большом количестве данных или при проблемах с сетевым соединением force sync может занимать довольно много времени.

Во время полной синхронизации все модификации неймспейса, происходящие на leader-ноде, продолжают штатным образом записываться в WAL, а также накапливаться в очереди онлайн-обновлений. В некоторых ситуациях циклический буфер WAL и очередь онлайн-обновлений могут переполняться до завершения force sync. Это приведёт к тому, что процесс полной синхронизации для целевого неймспейса будет запущен повторно. Если такая ситуация возникает регулярно, то следует скорректировать размер WAL (описано в разделе ниже) и/или размер очередь онлайн-обновлений (задаётся при запуске СУБД, см. CLI-параметр --updatessize и опцию файла конфигурации net.maxupdatessize в разделе конфигурации)

Настройка максимального количества записей в WAL

По умолчанию количество записей в WAL=4000000. Его можно изменить через Reindexer Face, утилиту reindexer_tool или с помощью операции upsert по HTTP или через коннектор. Этот параметр меняется в поле wal_size объекта namespaces служебного неймспейса #config:

{
  "type": "namespaces",
  "namespaces": [
    {
      "namespace": "*",
      "log_level": "none",
      "lazyload": false,
      "unload_idle_threshold": 0,
      "join_cache_mode": "off",
      "start_copy_policy_tx_size": 10000,
      "copy_policy_multiplier": 5,
      "tx_size_to_always_copy": 100000,
      "optimization_timeout_ms": 800,
      "optimization_sort_workers": 4,
      "wal_size": 4000000,
      "min_preselect_size": 1000,
      "max_preselect_size": 1000,
      "max_preselect_part": 0.1
    }
  ]
}

Просмотр WAL неймспейса

Для просмотра содержимого WAL неймспейса используется оператор SELECT со специальным условием для индекса #lsn:

SELECT * FROM test_namespace WHERE #lsn > 1000
curl --location --request POST 'http://127.0.0.1:5000/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "#lsn",
      "cond": "GT",
      "value": 1000
    }
  ]
}'
Reindexer> SELECT * FROM test_namespace where #lsn > 1000

Для вывода всех записей WAL можно воспользоваться условием #lsn is not null. Также для ограничения выдачи возможно использование параметров LIMIT и OFFSET.

Настройка общих параметров репликации

Эти параметры являются общими, как для асинхронной, так и для синхронной репликации.

Общие параметры репликации можно задать одним из двух способов:

  1. в служебном неймспейсе #config,
  2. в файле конфигурации replication.conf.

Настройка общих параметров репликации в служебном неймспейсе #config

Общие параметры репликации задаются в служебном неймспейсе #config в поле replication:

{
  "type": "replication",
  "replication": {
    "server_id": 0,
    "cluster_id": 2,
    "admissible_replication_tokens":[
      {
        "token":"<some_token_1>",
        "namespaces":["ns1"]
      },
      {
        "token":"<some_token_2>",
        "namespaces":["ns2", "ns3"]
      },
      {
        "token":"<some_token_3>",
        "namespaces":["ns4"]
      }
    ]
  }
}

Здесь:

  • server_id — уникальный идентификатор сервера (ноды). Должен быть задан пользователем.

  • cluster_id — номер группы (или кластера), в которой участвует нода. Должен быть одинаковым для всех нод группы (кластера).

  • admissible_replication_tokens — Опциональный объект со списками неймспейсов и токенами, которые follower ожидает от подключающихся лидеров. Если для неймспейса задан какой-либо admissible-токен, то лидер сможет среплицировать его на текущую ноду, только если admissible-токен совпадает с self_replication_token, сконфигурированным на leader-ноде. В примере выше:

    {
      "token":"<some_token_2>",
      "namespaces":["ns2", "ns3"]
    }
    

    означает, что репликация для неймспейсов ns2 и ns3 будет осуществляться, только если в конфигурации leader-а задано соответствующее значение токена: self_replication_token: <some_token_2>.

    Каждый неймспейс может иметь только один токен.

    Для указания общего admissible-токена для всех неймспейсов можно использовать следующую запись:

    "admissible_replication_tokens":[
      {
        "token":"<some_common_token>",
        "namespaces":["*"]
      },
      {
        "token":"<some_token_1>",
        "namespaces":["ns1"]
      }
    ]
    

    Эта запись означает, что для ns1 ожидается токен <some_token_1>, а для остальных <some_common_token>.

    (Доступно с версии v5.2.0)

Настройка общих параметров репликации в файле конфигурации replication.conf

Общие параметры репликации настраиваются в файле replication.conf в формате YAML. Его необходимо разместить в директории реплицируемой базы данных.

Файл replication.conf считывается при старте СУБД. Если в этот момент он существует, то его содержимое будет синхронизироваться в рантайме со служебным неймспейсом #config: то, что записано в файл, будет перенесено в неймспейс, и наоборот.

Если же файла нет в директории реплицируемой базы данных при старте сервера, будут применяться настройки репликации из неймспейса #config, а содержимое добавленного replication.conf будет игнорироваться до перезапуска. При этом, если файла нет при старте сервера, создаваться автоматически он не будет.

Содержимое файла конфигурации аналогично содержимому поля replication служебного неймспейса config. Пример файла конфигурации для настройки общих параметров репликации.

Проверка целостности данных

Существуют потенциальные риски нарушения согласованности данных между Leader-ом и Follower-ом в ходе репликации. Для их исключения Reindexer вычисляет легкий инкрементный хеш всех данных неймспейса (DataHash), который используется для быстрой проверки, что данные Follower действительно совпадают с Leader. При обнаружении расхождения данных DataHash происходит повторная полная синхронизация.