Версия:

Руководство разработчика / Внутреннее устройство
Руководство разработчика / Внутреннее устройство

Внутреннее устройство

Внутреннее устройство

Бинарный протокол Tarantool’а

Бинарный протокол Tarantool’а представляет собой бинарный запросно-ответный протокол.

Система обозначений в схематическом представлении

0    X
+----+
|    | - X + 1 bytes
+----+
 TYPE - type of MsgPack value (if it is a MsgPack object)

+====+
|    | - Variable size MsgPack object
+====+
 TYPE - type of MsgPack value

+~~~~+
|    | - Variable size MsgPack Array/Map
+~~~~+
 TYPE - type of MsgPack value

Типы MsgPack-данных:

  • MP_INT - целое число
  • MP_MAP - ассоциативный массив
  • MP_ARR - массив
  • MP_STRING - строка
  • MP_FIXSTR - строка фиксированной длины
  • MP_OBJECT - любой MsgPack-объект
  • MP_BIN - бинарный формат MsgPack

Пакет приветствия

ПРИВЕТСТВИЕ TARANTOOL'А:

 0                                     63
 +--------------------------------------+
 |                                      |
 | Приветствие Tarantool'а (версия сервера)  |
 |               64 байта               |
 +---------------------+----------------+
 |                     |                |
 | СОЛЬ в кодировке BASE64 |      NULL      |
 |      44 байта       |                |
 +---------------------+----------------+
 64                  107              127

Экземпляр сервера начинает диалог с отправки клиенту текста приветствия фиксированного размера (128 байтов). Приветствие всегда содержит две 64-байтные строки текста в формате ASCII, каждая строка заканчивается символом разрыва строки („\n“). Первая строка описывает версию экземпляра и тип протокола. Вторая строка содержит случайную строку в кодировке base64 размером до 44 байтов для использования в пакете аутентификации и заканчивается на пробелы (до 23).

Унифицированная структура пакета

После того, как приветствие прочитано, протокол становится простым запросно-ответным протоколом и предоставляет полный доступ к функциям Tarantool’а, включая:

  • мультиплексирование запросов, т.е. возможность асинхронной отправки множества запросов по одному соединению;
  • формат ответа, который поддерживает запись в режиме без копирования (zero-copy).

The protocol uses msgpack for data structures and encoding.

The protocol uses maps that contain some integer constants as keys. These constants are defined in src/box/iproto_constants.h. We list common constants here:

-- user keys
<iproto_sync>          ::= 0x01
<iproto_schema_id>     ::= 0x05  /* also known as schema_version */
<iproto_space_id>      ::= 0x10
<iproto_index_id>      ::= 0x11
<iproto_limit>         ::= 0x12
<iproto_offset>        ::= 0x13
<iproto_iterator>      ::= 0x14
<iproto_key>           ::= 0x20
<iproto_tuple>         ::= 0x21
<iproto_function_name> ::= 0x22
<iproto_username>      ::= 0x23
<iproto_expr>          ::= 0x27 /* also known as expression */
<iproto_ops>           ::= 0x28
<iproto_data>          ::= 0x30
<iproto_error>         ::= 0x31
-- -- Value for <code> key in request can be:
-- User command codes
<iproto_select>       ::= 0x01
<iproto_insert>       ::= 0x02
<iproto_replace>      ::= 0x03
<iproto_update>       ::= 0x04
<iproto_delete>       ::= 0x05
<iproto_call_16>      ::= 0x06 /* as used in version 1.6 */
<iproto_auth>         ::= 0x07
<iproto_eval>         ::= 0x08
<iproto_upsert>       ::= 0x09
<iproto_call>         ::= 0x0a
-- Admin command codes
-- (including codes for replica-set initialization and master election)
<iproto_ping>         ::= 0x40
<iproto_join>         ::= 0x41 /* i.e. replication join */
<iproto_subscribe>    ::= 0x42
<iproto_request_vote> ::= 0x43

-- -- Value for <code> key in response can be:
<iproto_ok>           ::= 0x00
<iproto_type_error>   ::= 0x8XXX /* where XXX is a value in errcode.h */

И заголовок <header> и тело сообщения <body> представляют собой ассоциативные массивы в формате msgpack:

Запрос / ответ:

 0        5
 +--------+ +============+ +===================================+
 | BODY + | |            | |                                   |
 | HEADER | |   HEADER   | |               BODY                |
 |  SIZE  | |            | |                                   |
 +--------+ +============+ +===================================+
   MP_INT       MP_MAP                     MP_MAP
УНИФИЦИРОВАННЫЙ ЗАГОЛОВОК:

 +================+================+=====================+
 |                |                |                     |
 |   0x00: CODE   |   0x01: SYNC   |    0x05: SCHEMA_ID  |
 | MP_INT: MP_INT | MP_INT: MP_INT |  MP_INT: MP_INT     |
 |                |                |                     |
 +================+================+=====================+
                           MP_MAP

Они различаются лишь набором допустимых ключей и значений. Ключ определяет тип следующего за ним значения. Если в теле сообщения нет ключей, может отсутствовать весь ассоциативный массив в формате msgpack для тела сообщения. Так и случится при запросе проверки связи <ping>. schema_id может отсутствовать в заголовке запроса, что означает отсутствие проверки версии, но этот ключ обязательно должен присутствовать в ответе. Если schema_id отправляется в заголовке, будет выполнена соответствующая проверка.

Аутентификация

Когда клиент подключается к экземпляру сервера, экземпляр отвечает 128-байтным текстовым сообщением приветствия. Часть приветствия представляет собой закодированное в формате base-64 значение соль для сессии (случайная строка), которое можно использовать для аутентификации. Длина расшифрованного значения соль (44 байта) выходит за пределы сообщения для аутентификации (первые 20 байтов). Остаток предназначается для будущих схем аутентификации.

ПОДГОТОВКА КОДИРОВАНИЯ:

     LEN(ENCODED_SALT) = 44;
     LEN(SCRAMBLE)     = 20;

 подготовить кодирование 'chap-sha1':

     salt = base64_decode(encoded_salt);
     step_1 = sha1(password);
     step_2 = sha1(step_1);
     step_3 = sha1(salt, step_2);
     scramble = xor(step_1, step_3);
     return scramble;

 ТЕЛО СООБЩЕНИЯ АВТОРИЗАЦИИ: CODE = 0x07

 +==================+====================================+
 |                  |        +-------------+-----------+ |
 |  (KEY)           | (TUPLE)|  len == 9   | len == 20 | |
 |   0x23:USERNAME  |   0x21:| "chap-sha1" |  SCRAMBLE | |
 | MP_INT:MP_STRING | MP_INT:|  MP_STRING  |  MP_BIN   | |
 |                  |        +-------------+-----------+ |
 |                  |                   MP_ARRAY         |
 +==================+====================================+
                         MP_MAP

<key> содержит имя пользователя. <tuple> должен представлять собой массив из 2 полей: механизм аутентификации (в данный момент поддерживается только механизм «chap-sha1») и пароль, закодированный в соответствии с указанным механизмом. Аутентификация в Tarantool’е необязательна: если аутентификация не проводится, то пользователем в сессии будет „guest“. Экземпляр отвечает на пакет аутентификации стандартным ответом с 0 кортежей.

Запросы

  • SELECT: CODE - 0x01 Поиск кортежей, соответствующих шаблону поиска
ТЕЛО СООБЩЕНИЯ ВЫБОРКИ SELECT:

 +==================+==================+==================+
 |                  |                  |                  |
 |   0x10: SPACE_ID |   0x11: INDEX_ID |   0x12: LIMIT    |
 | MP_INT: MP_INT   | MP_INT: MP_INT   | MP_INT: MP_INT   |
 |                  |                  |                  |
 +==================+==================+==================+
 |                  |                  |                  |
 |   0x13: OFFSET   |   0x14: ITERATOR |   0x20: KEY      |
 | MP_INT: MP_INT   | MP_INT: MP_INT   | MP_INT: MP_ARRAY |
 |                  |                  |                  |
 +==================+==================+==================+
                           MP_MAP
  • INSERT: CODE - 0x02 Вставка кортежа в спейс, если нет кортежей с такими же уникальными ключами. Если есть, выдать ошибку duplicate key (повторяющееся значение ключа).
  • REPLACE: CODE - 0x03 Вставка кортежа в спейс или замена существующего кортежа.
ТЕЛО СООБЩЕНИЯ ВСТАВКИ/ЗАМЕНЫ INSERT/REPLACE:

 +==================+==================+
 |                  |                  |
 |   0x10: SPACE_ID |   0x21: TUPLE    |
 | MP_INT: MP_INT   | MP_INT: MP_ARRAY |
 |                  |                  |
 +==================+==================+
                  MP_MAP
  • UPDATE: CODE - 0x04 Обновление кортежа
ТЕЛО СООБЩЕНИЯ ОБНОВЛЕНИЯ UPDATE:

 +==================+=======================+
 |                  |                       |
 |   0x10: SPACE_ID |   0x11: INDEX_ID      |
 | MP_INT: MP_INT   | MP_INT: MP_INT        |
 |                  |                       |
 +==================+=======================+
 |                  |          +~~~~~~~~~~+ |
 |                  |          |          | |
 |                  | (TUPLE)  |    OP    | |
 |   0x20: KEY      |    0x21: |          | |
 | MP_INT: MP_ARRAY |  MP_INT: +~~~~~~~~~~+ |
 |                  |            MP_ARRAY   |
 +==================+=======================+
                  MP_MAP
OP:
     Работает только для целочисленных полей:
     * Сложение    OP = '+' . space[key][field_no] += argument
     * Вычитание OP = '-' . space[key][field_no] -= argument
     * Побитовое И OP = '&' . space[key][field_no] &= argument
     * Исключающее ИЛИ OP = '^' . space[key][field_no] ^= argument
     * Побитовое ИЛИ  OP = '|' . space[key][field_no] |= аргумент
     Работает для любых полей:
     * Удаление      OP = '#'
       удалить поля <argument>, начиная
       с поля <field_no> в спейсе с ключом space[<key>]

 0           2
 +-----------+==========+==========+
 |           |          |          |
 |    OP     | FIELD_NO | ARGUMENT |
 | MP_FIXSTR |  MP_INT  |  MP_INT  |
 |           |          |          |
 +-----------+==========+==========+
               MP_ARRAY
   * Вставка      OP = '!'
      вставить <argument> до поля <field_no>
    * Присвоение      OP = '='
      присвоить <argument> полю <field_no>.
      увеличит кортеж, если <field_no> == <max_field_no> + 1

0           2
+-----------+==========+===========+
|           |          |           |
|    OP     | FIELD_NO | ARGUMENT  |
| MP_FIXSTR |  MP_INT  | MP_OBJECT |
|           |          |           |
+-----------+==========+===========+
              MP_ARRAY

    Работает со строковыми полями:
    * Разделение      OP = ':'
      взять строку из space[key][field_no] и
      заменить <offset> байтов из положения <position> на <argument>
0           2
 +-----------+==========+==========+========+==========+
 |           |          |          |        |          |
 |    ':'    | FIELD_NO | POSITION | OFFSET | ARGUMENT |
 | MP_FIXSTR |  MP_INT  |  MP_INT  | MP_INT |  MP_STR  |
 |           |          |          |        |          |
 +-----------+==========+==========+========+==========+
                          MP_ARRAY

Указать аргумент типа, который отличается от ожидаемого типа, будет ошибкой.

  • DELETE: CODE - 0x05 Удаление кортежа
ТЕЛО СООБЩЕНИЯ УДАЛЕНИЯ DELETE:

 +==================+==================+==================+
 |                  |                  |                  |
 |   0x10: SPACE_ID |   0x11: INDEX_ID |   0x20: KEY      |
 | MP_INT: MP_INT   | MP_INT: MP_INT   | MP_INT: MP_ARRAY |
 |                  |                  |                  |
 +==================+==================+==================+
                           MP_MAP
  • CALL_16: CODE - 0x06 Вызов хранимой функции с возвратом массива кортежей. Объявлен устаревшим; рекомендуется использовать CALL (0x0a).
ТЕЛО СООБЩЕНИЯ CALL_16:

 +=======================+==================+
 |                       |                  |
 |   0x22: FUNCTION_NAME |   0x21: TUPLE    |
 | MP_INT: MP_STRING     | MP_INT: MP_ARRAY |
 |                       |                  |
 +=======================+==================+
                     MP_MAP
  • EVAL: CODE - 0x08 Оценка Lua-выражения
ТЕЛО СООБЩЕНИЯ EVAL:

 +=======================+==================+
 |                       |                  |
 |   0x27: EXPRESSION    |   0x21: TUPLE    |
 | MP_INT: MP_STRING     | MP_INT: MP_ARRAY |
 |                       |                  |
 +=======================+==================+
                     MP_MAP
  • UPSERT: CODE - 0x09 Обновление кортежа, если он уже существует, попытка вставить кортеж. Всегда используйте первичный индекс.
ТЕЛО СООБЩЕНИЯ ОБНОВЛЕНИЯ И ВСТАВКИ UPSERT:

 +==================+==================+==========================+
 |                  |                  |             +~~~~~~~~~~+ |
 |                  |                  |             |          | |
 |   0x10: SPACE_ID |   0x21: TUPLE    |       (OPS) |    OP    | |
 | MP_INT: MP_INT   | MP_INT: MP_ARRAY |       0x28: |          | |
 |                  |                  |     MP_INT: +~~~~~~~~~~+ |
 |                  |                  |               MP_ARRAY   |
 +==================+==================+==========================+
                                 MP_MAP

 Структура операции аналогична структуре операции обновления UPDATE.
    0           2
 +-----------+==========+==========+
 |           |          |          |
 |    OP     | FIELD_NO | ARGUMENT |
 | MP_FIXSTR |  MP_INT  |  MP_INT  |
 |           |          |          |
 +-----------+==========+==========+
               MP_ARRAY

 Поддерживаются следующие операции:

 '+' - прибавление значения к числовому полю. Если поле не является числовым, оно
       сначала изменяется на 0. Если поле отсутствует, операция
       пропускается. В случае переполнения ошибки также не будет, значение
       просто переносится в стиле языка C. Диапазон целых чисел в формате MsgPack:
       от -2^63 до 2^64-1
 '-' - как в предыдущей операции, но значение вычитается
 '=' - присвоение значения полю. Если поле отсутствует,
       операция пропускается.
 '!' - вставка поля. Можно вставить поле, если при этом не будут созданы
       промежутки с нулевым значением nil между полями. Например, можно добавить поле между
       существующими полями или последнее поле в кортеже.
 '#' - удаление поля. Если поле отсутствует, операция пропускается.
       Нельзя с помощью операции обновления update изменить компонент первичного
       ключа (это проверяется перед выполнением операции upsert).
  • CALL: CODE - 0x0a Аналог CALL_16, но как и операция EVAL, CALL возвращает список неконвертированных значений
ТЕЛО СООБЩЕНИЯ CALL:

 +=======================+==================+
 |                       |                  |
 |   0x22: FUNCTION_NAME |   0x21: TUPLE    |
 | MP_INT: MP_STRING     | MP_INT: MP_ARRAY |
 |                       |                  |
 +=======================+==================+
                     MP_MAP

Структура пакета ответа

Здесь мы продемонстрируем пакеты полностью:

OK:    LEN + HEADER + BODY

 0      5                                          OPTIONAL
 +------++================+================++===================+
 |      ||                |                ||                   |
 | BODY ||   0x00: 0x00   |   0x01: SYNC   ||   0x30: DATA      |
 |HEADER|| MP_INT: MP_INT | MP_INT: MP_INT || MP_INT: MP_OBJECT |
 | SIZE ||                |                ||                   |
 +------++================+================++===================+
  MP_INT                MP_MAP                      MP_MAP

Предполагается, что набор кортежей в ответе <data> будет представлять собой msgpack-массив кортежей, поскольку команда EVAL возвращается произвольный MsgPack-массив MP_ARRAY с произвольными MsgPack-значениями.

ОШИБКА: LEN + HEADER + BODY

 0      5
 +------++================+================++===================+
 |      ||                |                ||                   |
 | BODY ||   0x00: 0x8XXX |   0x01: SYNC   ||   0x31: ERROR     |
 |HEADER|| MP_INT: MP_INT | MP_INT: MP_INT || MP_INT: MP_STRING |
 | SIZE ||                |                ||                   |
 +------++================+================++===================+
  MP_INT                MP_MAP                      MP_MAP

 Где 0xXXX -- это код ошибки ERRCODE.

Сообщение об ошибке будет включено в ответ только в случае ошибки; предполагается, что значение <error> будет msgpack-строкой.

Convenience macros which define hexadecimal constants for return codes can be found in src/box/errcode.h

Структура пакета при репликации

-- ключи для репликации
 <server_id>     ::= 0x02
 <lsn>           ::= 0x03
 <timestamp>     ::= 0x04
 <server_uuid>   ::= 0x24
 <cluster_uuid>  ::= 0x25
 <vclock>        ::= 0x26
-- коды для репликации
 <join>      ::= 0x41
 <subscribe> ::= 0x42
JOIN:

 Сначала необходимо отправить изначальный запрос JOIN
                HEADER                      BODY
 +================+================++===================+
 |                |                ||   SERVER_UUID     |
 |   0x00: 0x41   |   0x01: SYNC   ||   0x24: UUID      |
 | MP_INT: MP_INT | MP_INT: MP_INT || MP_INT: MP_STRING |
 |                |                ||                   |
 +================+================++===================+
                MP_MAP                     MP_MAP

 Затем экземпляр, к которому мы подключаемся, отправит последний файл снимка SNAP,
 просто создав количество запросов вставки INSERT (с дополнительным LSN и ServerID)
 (не отвечайте). Затем он отправит MP_MAP из vclock и закроет сокет.

 +================+================++============================+
 |                |                ||        +~~~~~~~~~~~~~~~~~+ |
 |                |                ||        |                 | |
 |   0x00: 0x00   |   0x01: SYNC   ||   0x26:| SRV_ID: SRV_LSN | |
 | MP_INT: MP_INT | MP_INT: MP_INT || MP_INT:| MP_INT: MP_INT  | |
 |                |                ||        +~~~~~~~~~~~~~~~~~+ |
 |                |                ||               MP_MAP       |
 +================+================++============================+
                MP_MAP                      MP_MAP

 SUBSCRIBE:

 Далее необходимо отправить запрос SUBSCRIBE:

                               HEADER
 +===================+===================+
 |                   |                   |
 |     0x00: 0x42    |    0x01: SYNC     |
 |   MP_INT: MP_INT  |  MP_INT: MP_INT   |
 |                   |                   |
 +===================+===================+
 |    SERVER_UUID    |    CLUSTER_UUID   |
 |   0x24: UUID      |   0x25: UUID      |
 | MP_INT: MP_STRING | MP_INT: MP_STRING |
 |                   |                   |
 +===================+===================+
                  MP_MAP

       BODY
 +================+
 |                |
 |   0x26: VCLOCK |
 | MP_INT: MP_INT |
 |                |
 +================+
       MP_MAP

 Затем следует обработать каждый запрос, который пришел от других мастеров.
 Каждый запрос между мастерами получит дополнительный LSN и SERVER_ID.

XLOG / SNAP

Файлы форматов XLOG и SNAP выглядят практически одинаково. Заголовок выглядит следующим образом:

<type>\n                  SNAP\n или XLOG\n
 <version>\n               в данный момент 0.13\n
 Server: <server_uuid>\n   где UUID -- это 36-байтная строка
 VClock: <vclock_map>\n    например, {1: 0}\n
 \n

После файла заголовка идут кортежи с данными. Кортежи начинаются с маркера строки 0xd5ba0bab, а после последнего кортежа может стоять маркер конца файла 0xd510aded. Таким образом, между заголовком файла и маркером конца файла могут быть кортежи с данными в следующем виде:

0            3 4                                         17
 +-------------+========+============+===========+=========+
 |             |        |            |           |         |
 | 0xd5ba0bab  | LENGTH | CRC32 PREV | CRC32 CUR | PADDING |
 |             |        |            |           |         |
 +-------------+========+============+===========+=========+
   MP_FIXEXT2    MP_INT     MP_INT       MP_INT      ---

 +============+ +===================================+
 |            | |                                   |
 |   HEADER   | |                BODY               |
 |            | |                                   |
 +============+ +===================================+
     MP_MAP                     MP_MAP

См. пример в предыдущем разделе.

Персистентность данных и формат WAL-файла

To maintain data persistence, Tarantool writes each data change request (insert, update, delete, replace, upsert) into a write-ahead log (WAL) file in the wal_dir directory. A new WAL file is created for every rows_per_wal records, or for every rows_per_wal bytes. Each data change request gets assigned a continuously growing 64-bit log sequence number. The name of the WAL file is based on the log sequence number of the first record in the file, plus an extension .xlog.

Помимо номера записи в журнале (LSN) и запроса на изменение данных (в формате бинарного протокола Tarantool’а), каждая запись в WAL-файле содержит заголовок, некоторые метаданные, а также данные, форматированные по правилам msgpack. Например, так выглядит WAL-файл после первого запроса вставки INSERT («s:insert({1})») для базы данных из песочницы, созданной в упражнениях в «Руководстве для начинающих». Слева представлены шестнадцатеричные байты, которые можно просмотреть с помощью:

$ hexdump 00000000000000000000.xlog

а справа – комментарии.

Шестнадцатеричный дамп WAL-файла       Комментарий
 --------------------       -------
 58 4c 4f 47 0a             "XLOG\n"
 30 2e 31 33 0a             "0.13\n" = version
 53 65 72 76 65 72 3a 20    "Server: "
 38 62 66 32 32 33 65 30 2d [Server UUID]\n
 36 39 31 34 2d 34 62 35 35
 2d 39 34 64 32 2d 64 32 62
 36 64 30 39 62 30 31 39 36
 0a
 56 43 6c 6f 63 6b 3a 20    "Vclock: "
 7b 7d                      "{}" = vclock value, initially blank
 ...                        (not shown = tuples for system spaces)
 d5 ba 0b ab                Magic row marker always = 0xab0bbad5
 19                         Length, not including length of header, = 25 bytes
 00                           Record header: previous crc32
 ce 8c 3e d6 70               Record header: current crc32
 a7 cc 73 7f 00 00 66 39      Record header: padding
 84                         msgpack code meaning "Map of 4 elements" follows
 00 02                         element#1: tag=request type, value=0x02=IPROTO_INSERT
 02 01                         element#2: tag=server id, value=0x01
 03 04                         element#3: tag=lsn, value=0x04
 04 cb 41 d4 e2 2f 62 fd d5 d4 element#4: tag=timestamp, value=an 8-byte "Float64"
 82                         msgpack code meaning "map of 2 elements" follows
 10 cd 02 00                   element#1: tag=space id, value=512, big byte first
 21 91 01                      element#2: tag=tuple, value=1-element fixed array={1}

Для чтения файлов в формате .xlog в Tarantool’е предусмотрен модуль xlog.

Tarantool обрабатывает запросы атомарно: изменение либо принимается и записывается в WAL-файл, или полностью исключается. Проясним, как этом работает, используя в качестве примера REPLACE-запрос:

  1. Экземпляр сервера пытается найти оригинальный кортеж по первичному ключу. Если кортеж найден, ссылка на него сохраняется для дальнейшего использования.
  2. Происходит проверка нового кортежа. Например, если в нем нет проиндексированного поля, или же тип проиндексированного поля не совпадает с типом в определении индекса, изменение прерывается.
  3. Новый кортеж заменяет старый кортеж во всех существующих индексах.
  4. В процесс записи, запущенный в потоке журнала упреждающей записи, отправляется сообщение о необходимости внесения записи в WAL-файл. Экземпляр переключается на работу со следующим запросом, пока запись не будет подтверждена.
  5. При успешном выполнении на клиент отправляется подтверждение. В случае ошибки начинается процедура отката. Во время процедуры отката поток обработки транзакций откатывается все изменения в базу данных, которые произошли после первого невыполненного изменения, от последнего с первому, вплоть до первого невыполненного изменения. Все запросы, которые подверглись откату, прерываются с ошибкой ER_WAL_IO. Новые изменения не применяются во время отката. По окончании процедуры отката сервер повторно запускает конвейер обработки операций.

Одно из преимуществ описанного алгоритма заключается в том, что достигается полная обработка запроса по конвейеру даже для запросов с одинаковым значением первичного ключа. В результате производительность базы данных не падает, даже если все запросы относятся к одному ключу в одном спейсе.

Поток обработки транзакций взаимодействует с потоком записи в журнал упреждающей записи с помощью асинхронного (однако надежного) обмена сообщениями. Поток обработки транзакций, который не блокируется при задачах записи в журнал, продолжает быстро обрабатывать запрос даже при большом объеме дискового ввода-вывода. Ответ на запрос отправляется по готовности, даже если ранее на том же соединении были незавершенные запросы. В частности, на производительность выборки не влияет загрузка диска, даже если SELECT-запросы передаются вместе с запросами UPDATE и DELETE.

При записи в WAL можно применять различные режимы долговечности, что определяет конфигурационная переменная wal_mode. Можно полностью отключить журнал упреждающей записи, присвоив wal_mode значение none. Даже без журнала упреждающей записи возможно сделать персистентную копию всего набора данных с помощью запроса box.snapshot().

Файл в формате .xlog всегда содержит изменения на основании первичного ключа. Даже если клиент запрашивает обновление или удаление по вторичному ключу, запись в файле в формате .xlog будет содержать первичный ключ.

Формат файла снимка

Формат файла снимка .snap практически такой же, что и формат WAL-файла .xlog. Тем не менее, заголовок снимка отличается: он содержит глобально уникальный идентификатор экземпляра и положения файла снимка в истории относительно более ранних файлов снимка. Кроме того, отличается содержание: .xlog-файл может содержать записи о любых запросах изменения данных (вставка, обновление, обновление и вставка и удаление), а .snap-файл может содержать лишь записи о вставках в спейсы memtx’а.

В первую очередь записи в .snap-файле упорядочены по идентификатору спейса. Таким образом, записи в системные спейсы – такие как _schema, _space, _index, _func, _priv и _cluster – будут находиться в начале .snap-файла до записей в другие спейсы, созданные пользователями.

Во вторую очередь записи в .snap-файле упорядочены по первичному ключу.

Процесс восстановления

Процесс восстановления начинается, когда box.cfg{} впервые используется после запуска экземпляра Tarantool-сервера.

Процесс восстановления должен восстановить базы данных на момент последнего отключения экземпляра. Для этого можно использовать последний файл снимка и любые WAL-файлы, которые были записаны после создания снимка. Ситуацию осложняет фактор того, что в Tarantool’е используются два движка – данные memtx’а должны быть реконструированы полностью из снимка и WAL-файлов, тогда как данные vinyl’а будут находиться на диске, но может потребоваться их обновление на время создания контрольной точки. (При создании снимка Tarantool передает движку vinyl команду создания контрольной точки, а операция создания снимка откатывается в случае какой-либо ошибки, поэтому контрольная точка vinyl’а будет настолько же актуальной, как и файл снимка.)

Шаг 1
Выполнить чтение конфигурационных параметров из запроса box.cfg{}. Параметры, которые могут повлиять на восстановление: work_dir, wal_dir, memtx_dir, vinyl_dir и force_recovery.
Шаг 2

Найти последний файл снимка. Использовать данные для реконструкции in-memory баз данных. Передать команду vinyl’у о восстановлении до последней контрольной точки.

На самом деле, есть два варианта реконструкции баз данных memtx’а в зависимости от того, выполняется ли стандартная процедура.

Если выполняется стандартная процедура (force_recovery = false), memtx может выполнить чтение данных из снимка с отключенными индексами. Сначала все кортежи считываются в память. Затем происходит массовая загрузка первичных ключей с учетом того, что данные уже отсортированы по первичному ключу в каждом спейсе.

Если выполняется нестандартная процедура принудительного восстановления (force_recovery = true), Tarantool проводит дополнительную проверку. Сначала индексы активны, и кортежи добавляются по одному. Это означает, что будут выявлены любые нарушения ограничений уникальности ключей, и все повторяющиеся значения пропускаются. Как правило, не будет нарушений ограничений или повторяющихся значений, поэтому такие проверки проводятся только в случае ошибки.

Шаг 3
Найти WAL-файл, который был создан во время создания файла снимка или позже. Выполнить чтение записей журнала до тех пор, пока LSN записи в журнале не будет больше LSN снимка или больше LSN контрольной точки в vinyl’е. Это и будет начальной точкой для процесса восстановления, которая соответствует текущему состоянию движков.
Шаг 4
Повторить записи журнала с начальной точки до конца WAL. Движок пропускает команду повторения, если данные старше контрольной точки движка.
Шаг 5
Повторно создать все вторичные индексы для движка memtx.