Попробуем создать спейс состоящий из трех полей и двух индексов, а затем всячески будем пробовать манипулировать данными.
Название | Тип | Описание | Индекс |
---|---|---|---|
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, в которой можно выполнять все перечисленные ниже команды.
На новом самостоятельном инстансе список спейсов будет пустым. Cоздадим наш первый спейс:
box.schema.space.create('my_test_space', {
engine = 'vinyl',
field_count = 3,
if_not_exists = true
})
Здесь мы указываем:
engine = 'vinyl'
- используем движок vinyl
(данные будут храниться на диске)field_count = 3
- количество полей/столбцов 3if_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 индекса:
уникальный первичный
по полю id
box.space.my_test_space:create_index('primary', {
type = 'TREE',
unique = true,
parts = {
{field = 1, type = 'integer'}
}
})
неуникальный вторичный
по полю created
: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
Смысл перечисленных полей можно взять из документации по создаю спейса, а если коротко:
is_local
: локальное пространство, WAL есть, но репликации нетis_sync
: транзакции с DML-запросами
(см. ниже) становятся синхронными, то есть ждут ответа реплик о получении данныхtemporary
: временное пространство, WAL
и репликации нетengine
: движок данных memtx или 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-запросы
для манипулирования данными (создание/обновление/выборка/удаление).
Таблица DML-запросов
в Tarantool:
Запрос | Описание |
---|---|
box.space. |
Вставка новой записи |
box.space. |
Вставка или замена записи |
box.space. |
Выборка записей |
box.space. |
Получение одной записи по первичному ключу |
box.space. |
Обновление записи |
box.space. |
Обновление записи, если она существует, или вставка, если нет |
box.space. |
Удаление записи |
box.space. |
Очистка всех записей из пространства |
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
: условие сравненияlimit
: лимит выборкиТаблица итераторов (или в документации):
Итератор (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 можно посмотреть в документации.
Выше мы рассмотрели запросы управления записями в спейсе. Как можно было заметить управлять данными только по первичному индексу может быть не всегда удобно. Для управления данными по конкретным индексам можно использовать методы индекса, они очень похожи на методы спейса, только обращаться нужно через индекс, например:
-- подсчет кортежей удовлетворяющих условию
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-запросов
в спейсе, в индексе есть специфичные запросы управления данными.
Получение кортежа с максимальным или минимальным значением:
localhost:3303> box.space.my_test_space.index.created:max()
---
- [59762, 1740123200, {...}]
...
Принудительный запуск компакции:
localhost:3303> box.space.my_test_space.index.created:compact()
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, нужно только знать смысл сущностей, который можно почерпнуть из документации.