Хранение данных | Tarantool
Документация на русском языке
поддерживается сообществом
Concepts Модель данных Хранение данных

Хранение данных

Tarantool обрабатывает данные в виде кортежей.

кортеж

Кортеж — это группа значений данных в памяти Tarantool. По сути, это «запись в базе данных» или «строка». Значения данных в кортеже называются полями.

Когда Tarantool выводит значение кортежа в консоль, по умолчанию используется формат YAML, например: [3, 'Ace of Base', 1993].

В Tarantool кортежи хранятся в виде массивов в формате MsgPack.

поле

Поля — это отдельные значения данных, которые содержатся в кортеже. Они играют ту же роль, что и «столбцы» или «поля записи» в реляционных базах данных, но несколько усовершенствованы:

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

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

Номер поля служит его идентификатором. В Lua и некоторых других языках нумерация начинается с 1, в других — с 0 (например, в PHP или C/C++). Таким образом, в некоторых контекстах у первого поля кортежа будет индекс 1 или 0.

Tarantool stores tuples in containers called spaces.

спейс

В Tarantool спейс — это первичный контейнер, хранящий данные. Он похож на таблицы в реляционных базах данных. Спейсы содержат кортежи — так в Tarantool называются записи в базе данных. Количество кортежей в спейсе не ограничено.

Для хранения данных с помощью Tarantool требуется хотя бы один спейс. У каждого спейса есть следующие атрибуты:

  • уникальное имя, указанное пользователем;
  • уникальный числовой идентификатор, обычно Tarantool назначает его автоматически, но пользователь может его указать сам, если посчитает нужным;
  • движок: memtx (по умолчанию) — движок «in-memory», быстрый, но ограниченный в размере, или vinyl — дисковый движок для огромных наборов данных.

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

Tarantool представляет собой систему управления базой данных и сервер приложений одновременно. Поэтому разработчику часто приходится работать с двумя системами типов данных: типы языка программирования (например, Lua) и типы формата хранения данных Tarantool (MsgPack).

Скалярный / составной MsgPack type Lua-тип Пример значения
скалярный nil cdata box.NULL
скалярный boolean boolean true
скалярный string string 'A B C'
скалярный integer number 12345
скалярный integer cdata 12345
скалярный float64 (double) number 1.2345
скалярный float64 (double) cdata 1.2345
скалярный binary cdata [!!binary 3t7e]
скалярный ext (for Tarantool decimal) cdata 1.2
скалярный ext (for Tarantool datetime) cdata '2021-08-20T16:21:25.122999906 Europe/Berlin'
скалярный ext (for Tarantool interval) cdata +1 months, 1 days
скалярный ext (for Tarantool uuid) cdata 12a34b5c-de67-8f90-123g-h4567ab8901
составной map (ассоциативный массив) table (with string keys) {'a': 5, 'b': 6}
составной array (массив) table (with integer keys) [1, 2, 3, 4, 5]
составной array (массив) tuple (cdata) [12345, 'A B C']

Примечание

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

Примечание

The Lua nil type is encoded as MsgPack nil but decoded as msgpack.NULL.

nil. В языке Lua у типа nil есть только одно значение, также называемое nil. Tarantool отображает его как null при использовании формата по умолчанию YAML. Значение nil можно сравнивать со значениями любых типов данных с помощью операторов == (равно) или ~= (не равно), но никакие другие операции сравнения к nil не применимы. Значение nil также нельзя использовать в Lua-таблицах — в качестве обходного пути вместо nil в таком случае можно указать box.NULL, поскольку условие nil == box.NULL является истинным. Пример: nil.

boolean. Логический тип данных boolean принимает значения true или false. Пример: true.

integer. В Tarantool тип полей integer используется для целых чисел от −9 223 372 036 854 775 808 до 18 446 744 073 709 551 615, то есть до примерно 18 квинтиллионов. Такой тип данных соответствует типам number в Lua и integer в MsgPack. Пример: -2^63.

unsigned. Тип unsigned в Tarantool используется для целых чисел от 0 до 18 446 744 073 709 551 615. Он представляет собой подмножество типа integer. Пример: 123456.

double. Поле типа double существует главным образом для соответствия типу DOUBLE data type в Tarantool/SQL . В msgpuck.h (интерфейс Tarantool к MsgPack) тип в хранилище — MP_DOUBLE, а размер закодированного значения всегда составляет 9 байтов. В Lua поля типа double могут содержать только не целые числовые значения и значения cdata с числами с плавающей точкой двойной точности (double). Примеры: 1.234, -44, 1.447e+44.

Чтобы избежать случайного использования неправильного типа значений, используйте явное преобразование типа ffi.cast(), когда вы ищете или изменяете поля типа double. Например, вместо space_object:insert{value} используйте ffi = require('ffi') ... space_object:insert({ffi.cast('double',value)}). Пример:

s = box.schema.space.create('s', {format = {{'d', 'double'}}})
s:create_index('ii')
s:insert({1.1})
ffi = require('ffi')
s:insert({ffi.cast('double', 1)})
s:insert({ffi.cast('double', tonumber('123'))})
s:select(1.1)
s:select({ffi.cast('double', 1)})

Арифметические операции с cdata формата double работают ненадёжно, поэтому для Lua лучше использовать тип number. Это не относится к Tarantool/SQL, так как Tarantool/SQL применяет неявное приведение типов.

number. Поле number в Tarantool может содержать значения как целые, так и с плавающей точкой, хотя в Lua тип number означает число с плавающей точкой двойной точности.

Tarantool по возможности сохраняет числа языка Lua в виде чисел с плавающей запятой, если числовое значение содержит десятичную запятую или если оно очень велико (более 100 триллионов = 1e14). В противном случае Tarantool сохраняет такое значение в виде целого числа. Чтобы даже очень большие величины гарантированно сохранялись как целые числа, используйте функцию tonumber64 или приписывайте в конце суффикс LL (Long Long) или ULL (Unsigned Long Long). Вот примеры записи чисел в обычном представлении, экспоненциальном, с суффиксом ULL и с использованием функции tonumber64: −55, −2.7e+20, 100000000000000ULL, tonumber64('18446744073709551615').

You can also use the ffi module to specify a C type to cast the number to. In this case, the number will be stored as cdata.

decimal. The Tarantool decimal type is stored as a MsgPack ext (Extension). Values with the decimal type are not floating-point values although they may contain decimal points. They are exact with up to 38 digits of precision. Example: a value returned by a function in the decimal module.

datetime. Introduced in v. 2.10.0. The Tarantool datetime type facilitates operations with date and time, accounting for leap years or the varying number of days in a month. It is stored as a MsgPack ext (Extension). Operations with this data type use code from c-dt, a third-party library.

For more information, see Module datetime.

interval. Introduced in v. 2.10.0. The Tarantool interval type represents periods of time. They can be added to or subtracted from datetime values or each other. Operations with this data type use code from c-dt, a third-party library. The type is stored as a MsgPack ext (Extension). For more information, see Module datetime.

string. Строка (string) представляет собой последовательность байтов переменной длины. Обычно она записывается буквенно-цифровыми символами в одинарных кавычках. Как Lua, так и MsgPack рассматривают строки как двоичные данные, не пытаясь определить кодировку строки или как-то её преобразовать, кроме случаев, когда указаны необязательные правила сравнения символов. Таким образом, обычно сортировка и сравнение строк выполняются побайтово, а дополнительные правила сравнения символов не применяются. Например, числа упорядочены по их положению на числовой прямой, поэтому 2345 больше, чем 500. Строки же упорядочены сначала по кодировке первого байта, затем по кодировке второго байта и так далее, так что '2345' меньше '500'. Пример: 'A, B, C'.

bin. Значения типа bin (двоичные значения) не поддерживаются непосредственно в Lua, но в Tarantool есть тип varbinary, который кодируется в тип binary из MsgPack. Пример вставки varbinary в базу данных см. в рекомендациях по разработке ffi_varbinary_insert (продвинутого уровня). Пример: "\65 \66 \67".

uuid. Тип uuid в Tarantool используется для универсальных уникальных идентификаторов (UUID). Начиная с версии 2.4.1, Tarantool хранит значения uuid в формате MsgPack ext (Extension).

Пример: 64d22e4d-ac92-4a23-899a-e5934af5479.

array. В Lua массив (array) обозначается {...} (фигурными скобками). Примеры: списки чисел, которые обозначают точки геометрической фигуры: {10, 11}, {3, 5, 9, 10}.

table. Lua-таблицы со строковыми ключами хранятся в виде ассоциативных массивов MsgPack map. Lua-таблицы с целочисленными ключами, начиная с 1, хранятся в виде массивов MsgPack array. В Lua-таблицах нельзя использовать nil; вместо этого можно использовать box.NULL. Пример: запрос box.space.tester:select() вернет Lua-таблицу.

tuple. Кортеж (tuple) представляет собой легкую ссылку на массив типа MsgPack array, который хранится в базе данных. Это особый тип (cdata), который создан во избежание конвертации в Lua-таблицу при выборке данных. Некоторые функции могут возвращать таблицы с множеством кортежей. Примеры с кортежами см. в разделе box.tuple.

scalar. Значения в поле типа scalar могут быть следующих типов: boolean, integer, unsigned, double, number, decimal, string, uuid, varbinary. Они не могут иметь тип array, map или tuple. Примеры: true, 1, 'xxx'.

any. Значения в поле типа any могут быть следующих типов: boolean, integer, unsigned, double, number, decimal, string, uuid, varbinary, array, map, tuple. Примеры: true, 1, 'xxx', {box.NULL, 0}.

Примеры запросов вставки с разными типами полей:

tarantool> box.space.K:insert{1,nil,true,'A B C',12345,1.2345}
---
- [1, null, true, 'A B C', 12345, 1.2345]
...
tarantool> box.space.K:insert{2,{['a']=5,['b']=6}}
---
- [2, {'a': 5, 'b': 6}]
...
tarantool> box.space.K:insert{3,{1,2,3,4,5}}
---
- [3, [1, 2, 3, 4, 5]]
...

О том, какие значения можно хранить в индексированных полях, читайте в разделе об индексах»

Когда Tarantool сравнивает строки, по умолчанию он использует двоичные параметры сортировки (binary collation). При этом он учитывает только числовое значение каждого байта в строке. Например, код символа 'A' (раньше называлась «значение ASCII») — число 65, код 'B' — число 66, а код 'a' – число 98. Поэтому 'A' < 'B' < 'a', если строка закодирована в ASCII или UTF-8.

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

Но если вы хотите такое упорядочение, как в телефонных справочниках и словарях, то вам нужна одна из дополнительных сортировок Tarantool: unicode или unicode_ci. Они обеспечивают 'a' < 'A' < 'B' и 'a' == 'A' < 'B' соответственно.

Дополнительные виды сортировки unicode и unicode_ci обеспечивают упорядочение в соответствии с Таблицей сортировки символов Юникода по умолчанию (DUCET) и правилами, указанными в Техническом стандарте Юникода №10 – Алгоритм сортировки по Юникоду (Unicode® Technical Standard #10 Unicode Collation Algorithm (UTS #10 UCA)). Единственное отличие между двумя видами сортировки — вес:

  • сортировка unicode принимает во внимание уровни веса L1, L2 и L3 (уровень = „tertiary“, третичный);
  • сортировка unicode_ci принимает во внимание только вес L1 (уровень = „primary“, первичный), поэтому, например, 'a' == 'A' == 'á' == 'Á'.

Для примера возьмем некоторые русские слова:

'ЕЛЕ'
'елейный'
'ёлка'
'еловый'
'елозить'
'Ёлочка'
'ёлочный'
'ЕЛь'
'ель'

…и покажем разницу в упорядочении и выборке по индексу:

  • с сортировкой по unicode:

    tarantool> box.space.T:create_index('I', {parts = {{field = 1, type = 'str', collation='unicode'}}})
    ...
    tarantool> box.space.T.index.I:select()
    ---
    - - ['ЕЛЕ']
      - ['елейный']
      - ['ёлка']
      - ['еловый']
      - ['елозить']
      - ['Ёлочка']
      - ['ёлочный']
      - ['ель']
      - ['ЕЛь']
    ...
    tarantool> box.space.T.index.I:select{'ЁлКа'}
    ---
    - []
    ...
    
  • с сортировкой по unicode_ci:

    tarantool> box.space.T:create_index('I', {parts = {{field = 1, type ='str', collation='unicode_ci'}}})
    ...
    tarantool> box.space.T.index.I:select()
    ---
    - - ['ЕЛЕ']
      - ['елейный']
      - ['ёлка']
      - ['еловый']
      - ['елозить']
      - ['Ёлочка']
      - ['ёлочный']
      - ['ЕЛь']
    ...
    tarantool> box.space.T.index.I:select{'ЁлКа'}
    ---
    - - ['ёлка']
    ...
    

Сортировка включает в себя ещё множество аспектов, кроме показанного в этом примере сопоставления букв верхнего и нижнего регистра, с диакритическими знаками и без них, и с учётом алфавита. Учитываются также вариации одного и того же символа, системы неалфавитного письма и применяются специальные правила для комбинаций символов.

Для английского, русского и большинства других языков используйте «unicode» и «unicode_ci». Если вам нужно, чтобы у кириллических букв „Е“ и „Ё“ веса 1 уровня были одинаковыми, попробуйте киргизскую сортировку.

Специализированные дополнительные виды сортировки: Для других языков Tarantool предлагает специализированные виды сортировки для любого современного языка, на котором говорят более миллиона человек. Кроме того, специализированные дополнительные виды сортировки возможны для особых случаев, когда слова в словаре упорядочиваются не так, как в телефонном справочнике. Чтобы увидеть полный список, выполните команду box.space._collation:select().

Названия специализированных видов сортировки имеют вид unicode_[language code]_[strength]], где language code — это стандартный код языка из 2 или 3 символов, а значение strength может быть s1 для уровня «primary» (вес уровня 1), s2 для уровня «secondary», s3 для уровня «tertiary». Tarantool использует те же коды языков, что указаны в списке специализированных вариантов языковых настроек на страницах руководств по Ubuntu и Fedora. Схемы, в деталях объясняющие отличия от упорядочения по DUCET, можно найти в Общем репозитории языковых данных (Common Language Data Repository).

For better control over stored data, Tarantool supports constraints – user-defined limitations on the values of certain fields or entire tuples. Together with data types, constraints allow limiting the ranges of available field values both syntactically and semantically.

For example, the field age typically has the number type, so it cannot store strings or boolean values. However, it can still have values that don’t make sense, such as negative numbers. This is where constraints come to help.

There are two types of constraints in Tarantool:

  • Field constraints check that the value being assigned to a field satisfies a given condition. For example, age must be non-negative.
  • Tuple constraints check complex conditions that can involve all fields of a tuple. For example, a tuple contains a date in three fields: year, month, and day. You can validate day values based on the month value (and even year if you consider leap years).

Field constraints work faster, while tuple constraints allow implementing a wider range of limitations.

Constraints use stored Lua functions, which must return true when the constraint is satisfied. Other return values (including nil) and exceptions make the check fail and prevent tuple insertion or modification.

To create a constraint function, use func.create with function body.

Constraint functions take two parameters:

  • The field value and the constraint name for field constraints.

    tarantool> box.schema.func.create('check_age',
             > {language = 'LUA', is_deterministic = true, body = 'function(f, c) return (f >= 0 and f < 150) end'})
    ---
    ...
    
  • The tuple and the constraint name for tuple constraints.

    tarantool> box.schema.func.create('check_person',
             > {language = 'LUA', is_deterministic = true, body = 'function(t, c) return (t.age >= 0 and #(t.name) > 3) end'})
    ---
    ...
    

Предупреждение

Tarantool doesn’t check field names used in tuple constraint functions. If a field referenced in a tuple constraint gets renamed, this constraint will break and prevent further insertions and modifications in the space.

To create a constraint in a space, specify the corresponding function’s name in the constraint parameter:

  • Field constraints: when setting up the space format:

    tarantool> box.space.person:format({
             > {name = 'id',   type = 'number'},
             > {name = 'name', type = 'string'},
             > {name = 'age',  type = 'number', constraint = 'check_age'},
             > })
    
  • Tuple constraints: when creating or altering a space:

    tarantool> box.schema.space.create('person', { engine = 'memtx', constraint = 'check_tuple'})
    

In both cases, constraint can contain multiple function names passed as a tuple. Each constraint can have an optional name:

constraint = {'age_constraint' = 'check_age', 'name_constraint' = 'check_name'}

Примечание

When adding a constraint to an existing space with data, Tarantool checks it against the stored data. If there are fields or tuples that don’t satisfy the constraint, it won’t be applied to the space.

Foreign keys provide links between related spaces, therefore maintaining the referential integrity of the database.

Some fields can only contain values present in other spaces. For example, shop orders always belong to existing customers. Hence, all values of the customer field of the orders space must exist in the customers space. In this case, customers is a parent space for orders (its child space). When two spaces are linked with a foreign key, each time a tuple is inserted or modified in the child space, Tarantool checks that a corresponding value is present in the parent space.

../../../_images/foreign_key.svg

There are two types of foreign keys in Tarantool:

  • Field foreign keys check that the value being assigned to a field is present in a particular field of another space. For example, the customer value in a tuple from the orders space must match an id stored in the customers space.
  • Tuple foreign keys check that multiple fields of a tuple have a match in another space. For example, if the orders space has fields customer_id and customer_name, a tuple foreign key can check that the customers space contains a tuple with both these values in the corresponding fields.

Field foreign keys work faster while tuple foreign keys allow implementing more strict references.

Важно

For each foreign key, there must exist an index that includes all its fields.

To create a foreign key in a space, specify the parent space and linked fields in the foreign_key parameter. Fields can be referenced by name or by number:

  • Field foreign keys: when setting up the space format.

    tarantool> box.space.orders:format({
             > {name = 'id',   type = 'number'},
             > {name = 'customer_id', foreign_key = {space = 'customers', field = 'id'}}, -- or field = 1
             > {name = 'price_total',  type = 'number'},
             > })
    
  • Tuple foreign keys: when creating or altering a space. Note that for foreign keys with multiple fields there must exist an index that includes all these fields.

tarantool> box.schema.space.create("orders", {foreign_key={space='customers', field={customer_id='id', customer_name='name'}}})
---
...
tarantool> box.space.orders:format({
         > {name = "id", type = "number"},
         > {name = "customer_id" },
         > {name = "customer_name"},
         > {name = "price_total",    type = "number"},
         > })

Примечание

Type can be omitted for foreign key fields because it’s defined in the parent space.

Foreign keys can have an optional name.

foreign_key = {customer = {space = '...', field = {...}}}

A space can have multiple tuple foreign keys. In this case, they all must have names.

foreign_key = {customer = {space = '...', field = {...} }, item = { space = '...', field = {...}}}

Tarantool performs integrity checks upon data modifications in parent spaces. If you try to remove a tuple referenced by a foreign key or an entire parent space, you will get an error.

Важно

Renaming parent spaces or referenced fields may break the corresponding foreign keys and prevent further insertions or modifications in the child spaces.

Нашли ответ на свой вопрос?
Обратная связь