Управление спейсами в Tarantool 2.X через консоль

2025.02.23
Разберем команды для работы Tarantool Space через консольную утилиту tarantoolctl для 2.X версии сервера. Попробуем создать спейс с индексами, просмотреть его данные и выполнять различные запросы.

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

Название Тип Описание Индекс
id unassigned уникальный числовой идентификатор первичный, уникальный
create unassigned timestamp создания записи вторичный, неуникальный
data * любое значение любого типа для сохранения метаданных -

Для начала пробуем войти в консоль управления Tarantool 2.X через tarantoolctl:

tarantoolctl connect usermame:password@localhost:3303

А если у вас еще нет развернутого сервера Tarantool то можно использовать docker-образ, например так:

$ docker run --name tarantool-test -t -i tarantool/tarantool:2.11.5-ubuntu20.04
...
tarantool >

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

Создание спейса и индексов в Tarantool

На новом самостоятельном инстансе список спейсов будет пустым. Cоздадим наш первый спейс:

box.schema.space.create('my_test_space', {
    engine = 'vinyl',
    field_count = 3,
    if_not_exists = true
})

Здесь мы указываем:

Создать поля в спейсе Tarantool можно также через метод format(), передав таблицу полей. Создадим 3 поля:

box.space.my_test_space:format({
    {name = 'id', type = 'integer'},
    {name = 'created', type = 'integer'},
    {name = 'data', type = '*'}
})

Здесь все просто: указываем имя поля и тип. Тип поля может быть любой допустимый: any | unsigned | string | integer | number | varbinary | boolean | double | decimal | uuid | array | map | scalar. Кстати, для поля data используется тип *, это значит что данные в этом поле могут быть любого типа.

Если поле планируется использовать под индекс, то нужно выбирать индексируемые типы.

А можно было создать поля в момент создания спейса:

box.schema.space.create('my_test_space', {
    engine = 'vinyl',
    field_count = 3,
    if_not_exists = true,
    format = {
        {name = 'id', type = 'integer'},
        {name = 'created', type = 'integer'},
        {name = 'data', type = '*'}
    }
})

Чтобы эффективно манипулировать данными нам нужны индексы, создадим 2 индекса:

box.space.my_test_space:create_index('primary', {
    type = 'TREE',
    unique = true,
    parts = {
        {field = 1, type = 'integer'}
    }
})
box.space.my_test_space:create_index('created', {
    type = 'TREE',
    unique = false,
    parts = {
        {field = 2, type = 'integer'}
    }
})

Более того, вставка данных в спейс без индексов приведет к ошибке:

localhost:3303> box.space.my_test_space:insert({1, 1740239900, {custom_data = {}}})
---
- error: 'No index #0 is defined in space ''my_test_space'''
...

Просмотр информации

Теперь можем просмотреть список доступных спейсов:

localhost:3301> box.space
---
- my_test_space:
    is_local: false
    is_sync: false
    temporary: false
    engine: vinyl

Смысл перечисленных полей можно взять из документации по создаю спейса, а если коротко:

По каждому спейсу можно получить более развернутую информацию, нужно просто указать его имя в box.space.:

localhost:3303> box.space.my_test_space
---
- engine: vinyl
  before_replace: 'function: 0x407b8e98'
  field_count: 3 -- количество полей
  is_sync: false
  ck_constraint: []
  on_replace: 'function: 0x407b8e70'
  temporary: false
  index: -- индексы
    0: &0
      unique: true -- индекс с уникальными значениями
      parts: -- состав индекса (первое поле, строковый тип)
      - type: integer
        is_nullable: false
        fieldno: 1
      options: -- настройки индекса
        page_size: 8192
        run_count_per_level: 2
        run_size_ratio: 3.5
        bloom_fpr: 0.05
      id: 0
      space_id: 725
      type: TREE
      name: primary -- первичный индекс
    1: &1
      unique: false
      parts:
      - type: integer
        is_nullable: false
        fieldno: 2
      options:
        page_size: 8192
        run_count_per_level: 2
        run_size_ratio: 3.5
        bloom_fpr: 0.05
      id: 1
      space_id: 725
      type: TREE
      name: created
    primary: *0
    created: *1
  is_local: false
  enabled: true
  name: my_test_space
  id: 725
...

Получить информацию о спейсе и его полях по его идентификатору через _space:

localhost:3301> box.space._space:select(box.space.my_test_space.id)
---
- - [622, 32, 'my_test_space', 'vinyl', 0, {}, [{'name': 'id', 'type': 'integer'},
    {'name': 'created', 'type': 'integer'}, {'name': 'data', 'type': '*'}]]
    ...

Если вызвать метод format() без параметров, то в ответ получим таблицу полей спейса:

localhost:3301> box.space.my_test_space:format()
---
- [{'name': 'id', 'type': 'integer'}, {'name': 'created', 'type': 'integer'}, 
  {'name': 'data', 'type': '*'}]
  ...

Получить информацию по индексам в спейсе Tarantool можно обратившись к полю index:

localhost:3303> box.space.my_test_space.index
---
- 0: &0
    unique: true
    parts:
    - type: integer
      is_nullable: false
      fieldno: 1
    options:
      page_size: 8192
      run_count_per_level: 2
      run_size_ratio: 3.5
      bloom_fpr: 0.05
    id: 0
    space_id: 725
    type: TREE
    name: primary
  1: &1
    unique: false
    parts:
    - type: integer
      is_nullable: false
      fieldno: 2
    options:
      page_size: 8192
      run_count_per_level: 2
      run_size_ratio: 3.5
      bloom_fpr: 0.05
      range_size: 134217728
    id: 1
    space_id: 725
    type: TREE
    name: created
  primary: *0
  created: *1
...

Или можно в сжатом виде получить информацию об индексах в спейсе через _index:

localhost:3301> box.space._index:select(box.space.my_test_space.id)
---
- - [622, 0, 'primary', 'tree', {'unique': true, 'run_count_per_level': 2, 'run_size_ratio': 3.5,
    'bloom_fpr': 0.05, 'page_size': 8192}, [[0, 'string']]]
- [622, 2, 'created', 'tree', {'unique': false, 'run_count_per_level': 2, 'run_size_ratio': 3.5,
  'bloom_fpr': 0.05, 'page_size': 8192}, [[2, 'integer']]]
  ...

DML-запросы спейса в Tarantool

И наконец-то рассмотрим DML-запросы для манипулирования данными (создание/обновление/выборка/удаление).

Таблица DML-запросов в Tarantool:

Запрос Описание
box.space.:insert{} Вставка новой записи
box.space.:replace{} Вставка или замена записи
box.space.:select{} Выборка записей
box.space.:get{} Получение одной записи по первичному ключу
box.space.:update{} Обновление записи
box.space.:upsert{} Обновление записи, если она существует, или вставка, если нет
box.space.:delete{} Удаление записи
box.space.:truncate() Очистка всех записей из пространства

Вставка новой записи:

box.space.my_test_space:insert({1, 1740239900, {custom_data = {}}})

Замена существующей записи или вставка новой, replace/put в данном случае являются синонимами:

box.space.my_test_space:replace({2, 1740239901, {custom_data = {}}})

Выборка данных с условием по первичному ключу:

box.space.my_test_space:select(2, {iterator = 'LT', limit = 3})

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

Таблица итераторов (или в документации):

Итератор (Iterator) Описание
EQ == записи возвращаются в порядке возрастания индексного ключа (по умолчанию)
REQ То же что и == только записи возвращаются в порядке убывания индексного ключа
ALL Аналог GE
GT >
GE >=
LT <
LE <=

Обязательно используйте limit с указанием конкретного значения, иначе Tarantool вернет все кортежи из спейса!

space:select() возвращает массив кортежей, но можно получить итератор с условием по первичному ключу и через него пройтись по каждому кортежу удовлетворяющему условию:

for _, tuple in box.space.my_test_space:pairs('qwerty', {iterator = 'LT'}) do
    print(tuple)
end

Поиск одного кортежа по первичному ключу с учетом точного вхождения, в данном случае поиск по идентификатору:

box.space.my_test_space:get(1)

space:get() аналогично space:select() с лимитом выборки одного кортежа:

box.space.my_test_space:select(1, {limit = 1})

За один раз можно обновить только один кортеж, но при этом обновить можно несколько полей:

box.space.my_test_space:update(
    2, -- сравнение ==, обновляем кортеж с идентификатором 2
    -- в поле created прибавим 10
    {
        {
            '+',  -- оператор обновления
            2,    -- обновляем поле номер 2 (created)
            10    -- новое значение
        },
        -- в поле data установим новое значение
        {
            '=',
            3,
            {}
        }
    }
)

Если заранее не известно имеется ли такой кортеж в спейсе, но мы точно знаем что теперь он должен быть, то можно выполнить одну команду space:upsert():

box.space.my_test_space:upsert({'qwertys', 1740247527, {}}, {{'=', 2, 1740247527}, {'=', 3, {}}})

При space:upsert() если кортеж по ключам индексов не найден, то он будет создан как указано в первом параметре (поведение как у space:insert()), а если найден то будут применены условия space:update().

Удаление записи из спейса в Tarantool осуществляется по первичному ключу:

box.space.my_test_space:delete('qwerty')

А для удаления всех записей из спейса можно использовать space:truncate():

box.space.my_test_space:truncate()

Еще примеры CRUD можно посмотреть в документации.

DML-запросы индекса

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

-- подсчет кортежей удовлетворяющих условию
box.space.my_test_space.index.created:count(1740241542, {iterator = 'LT'})

-- получение одного кортежа по условию
-- если в спейсе будет несколько кортежей удовлетворяющих условию то будет ошибка
box.space.my_test_space.index.created:get(1740241542)

-- выборка по условию
box.space.my_test_space.index.created:select(1740241542, {iterator = 'LT', limit = 3})

-- итерация по условию
box.space.my_test_space.index.created:pairs(1740241542, {iterator = 'LT'})

-- удаление, вторичный индекс должен быть обязательно уникальным
box.space.my_test_space.index.created:delete(1740241542)

Специфичные для индекса DML-запросы

Кроме основных DML-запросов в спейсе, в индексе есть специфичные запросы управления данными.

Получение кортежа с максимальным или минимальным значением:

localhost:3303> box.space.my_test_space.index.created:max()
---
- [59762, 1740123200, {...}]
...

Принудительный запуск компакции:

localhost:3303> box.space.my_test_space.index.created:compact()

Количество записей в спейсе Tarantool

Количество записей в спейсе:

localhost:3303> box.space.my_test_space:count()
---
- 55098284
  ...

space:count() ведет пересмотр всех записей, а это значит что для vinyl все данные будут прочитаны с диска, что может занимать много времени. А для memtx просто возвращает значение из метаданных пространства, так как все данные хранятся в оперативной памяти.

Через len() можно узнать приблизительное количество кортежей в спейсе:

localhost:3303> box.space.my_test_space:len()
---
- 15906956
  ...

Статистика len() для vinyl, может быть не актуальной, если данные часто меняются или компакция еще не завершилось.

Опытным путем выяснил что для движка vinyl значение len() очень похоже на значение rows в первичном индексе:

localhost:3303> box.space.my_test_space.index.primary:stat().rows
---
- 15906956
  ...

А значение count() очень похоже на значение rows из статистики вторичного индекса, в моем случае в created:

localhost:3303> box.space.my_test_space.index.created:stat().rows
---
- 55098284
  ...

Другие операции со спейсами

Переименовать текущий спейс:

box.space.my_test_space:rename('my_test_space2')

Удалить спейс:

box.space.my_test_space:drop()

Другие операции с индексами

Получить статистику по индексу в спейсе:

box.space.my_test_space.index.created:stat()

Кроме статистики можно получить размер индекса в байтах:

localhost:3303> box.space.my_test_space.index.created:bsize()
---
- 4913597834
...

Удалить индекс из спейса:

box.space.my_test_space.index.created:drop()

При массовом удалении записей из спейса в Tarantool была зафиксирована деградация чтения, избежать ее помогает пересоздание индекса drop/create_index.

Также есть метод rename().


Оказывается вот так просто можно взять и начать работать с СУБД Tarantool. Мне понравилось что внутри консоли все построено на языке lua, нужно только знать смысл сущностей, который можно почерпнуть из документации.

В телеграм канале DevOps от первого лица можно оставить комментарий или почитать интересные истории из практики DevOps