Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Когда мы видим в сети сообщение о выходе новой версии того или иного продукта, это, как правило, скучно. Ченджлог описывает лишь перечень изменений, а вот зачем и для чего они, понятно не всегда.
В этой статье я попытаюсь рассказать о том новом, что появится в Tarantool 3.0, не простым перечислением изменений, а описывая проблемы, которые мы пытались решить. В таком ключе это может быть интересно и тем, кто не собирается устанавливать Tarantool, — просто с технической точки зрения.
Tarantool 3.0 — сервер с конфигом
Начнём с самого большого, вернее самого бросающегося в глаза изменения — интерфейса запуска базы данных.
Изначально Tarantool был не только базой данных. Помимо собственно производительного персистентного In-memory-хранилища, Tarantool всегда предоставлял пользователям возможность написания программ, работающих в непосредственной близости от данных.
Так что такое Tarantool — база данных с сервером приложений на борту или сервер приложений со встроенной базой данных? Лагеря сторонников того и другого подхода имеют сильные аргументы, однако истина находится где-то посредине. Tarantool крут потому, что сочетает в себе обе ипостаси.
Когда Tarantool ещё был маленьким, адепты «тёмной стороны» имели численное превосходство. Интерфейс запуска сервера был спроектирован так, будто ничего, кроме App-сервера, клиентам не нужно. В итоге пользователь манипулировал инстансами базы данных так, будто это программа:
$ tarantool application.lua
От такого подхода страдали пользователи, которым Tarantool был нужен как чистая база данных. Для запуска хранилища, позволяющего записать и прочитать данные, им приходилось хоть чуть-чуть, но изучать программирование на Lua.
Трезвое осмысление сложившейся ситуации и анализ множества отзывов клиентов позволило создать новый вариант запуска, к которому пользователи привыкли при работе с другими системами. При этом он обратносовместимый, то есть возможность использовать Tarantool как интерпретатор Lua никуда не делась.
Теперь пользователь, желающий только настроить и запустить базу данных, может манипулировать обычным декларативным конфигом:
$ tarantool --name my-application --config /path/to/config.yaml
Практика показывает, что простые декларативные файлы конфигурации воспринимаются пользователями лучше. При необходимости эксплуатация умеет генерировать такие структуры на лету. А для популярных систем автоматизации развертывания различные части конфига можно задавать через переменные окружения.
О подробностях и строении файла конфигурации можно рассказывать долго, здесь же я остановлюсь только на ключевых вещах.
Ориентация на кластерность
Сейчас мало кто запускает какой-либо сервер изолированно от других. Если база данных, то зачастую с репликами. Если распределённая, то с шардингом. И так далее.
При разработке формата конфига мы постарались максимально облегчить пользователям работу с ним в кластерной среде.
С Tarantool 3.0 вы можете иметь кластер с десятком тысяч инстансов, описываемый одним конфигом. Роль конкретного узла вычленяется из общего описания при помощи параметра --name, как в примере выше. Де-факто пользователь теперь составляет конфигурационный файл не для определенного инстанса Tarantool, а для кластера в целом.
В Enterprise-ветке доступны GUI для манипуляции конфигурацией кластера, система распространения конфигурации через выделенный сервис конфигурации (etcd или Tarantool) и многие другие вещи. В перспективе можно будет составлять/редактировать карту кластера, «возя мышкой по экрану». Но это Enterprise, вернёмся к Tarantool.
Конфигурационный файл позволяет:
- описать общую схему кластера;
- назначить индивидуальные роли отдельным узлам. При этом узлы могут входить в те или иные группы, определяющие ту или иную функциональность;
- внешним приложениям использовать конфигурационный файл, для того чтобы применять данные о его топологии внутри себя. Теперь наконец-то автоматизированным способом можно получить, например, список всех роутеров кластера.
И всё это пользователь может делать, не программируя ни строчки: теперь в его распоряжении привычный по другим продуктам обычный конфигурационный файл. Полная документация по конфигурации расположена здесь.
Пример файла конфигурации для кластера с использованием встроенного RAFT в качестве фейловера:
config:
context:
replicator_password:
from: file
file: secrets/replicator_password.txt
rstrip: true
client_password:
from: file
file: secrets/client_password.txt
rstrip: true
credentials:
users:
replicator:
password: '{{ context.replicator_password }}'
roles: [replication]
client:
password: '{{ context.client_password }}'
roles: [super]
iproto:
listen:
- uri: 'unix/:./{{ instance_name }}.iproto'
advertise:
peer:
login: 'replicator'
log:
to: file
replication:
failover: election
groups:
group-001:
replicasets:
replicaset-001:
instances:
instance-001: {}
instance-002: {}
instance-003: {}
Дальнейшее развитие
Прежде всего мы завершим преобразование к обычным конфигам всех подсистем кластеров на Tarantool. Хотим, чтобы в дальнейшем пользователь манипулировал понятиями «здесь я хочу включить фичу (хранилище, роутер, CRUD и так далее), а не занимался написанием программ, как сейчас». И все доступные на сегодня фичи мы соберём в один конфиг. В идеале пользователь сможет настроить любой возможный вид кластера или single, не обращаясь к программированию. И совсем в идеале — используя простой и информативный GUI (правда, пока только в Enterprise).
Со временем интегрируем конфигурацию кластера со всеми доступными системами фейловеров и синхронной репликации. Часть шагов на этом пути уже сделана, часть ещё впереди.
Например, RAFT в Tarantool 2.x так и не получил широкого распространения именно из-за отсутствия простого способа его конфигурирования. Начиная с версии 3.0, у пользователя появляется возможность использовать RAFT, просто установив «вкл» в соответствующий переключатель файла конфигурации. Настройки синхронной репликации тоже не за горами. Надеемся, это приведёт к росту пользователей этих инструментов.
Большой рефакторинг системы триггеров
Tarantool 3.0 делает большой шаг в направлении пользователей, которые хотят видеть только базу данных. Но при этом мы продолжаем развивать и ту часть, которая позволяет писать программы на Tarantool. В частности, мы довольно серьёзно переработали систему триггеров.
Важная ремарка: все произведённые изменения сделаны обратно-совместимо, поэтому ранее написанный код не пострадает. Однако, используя новый интерфейс, пользователь сможет многие вещи делать проще и с меньшим числом ошибок.
Раньше у системы триггеров было несколько серьезных недостатков.
Первый недостаток: чтобы установить триггер, нужно, чтобы его можно было установить. Это предложение кажется бессмысленным, но это действительно было проблемой. Поясню.
В Tarantool пользователь может устанавливать триггеры на множество событий: запись в БД, подключение клиентов, отключение, коммит транзакций и так далее. Чтобы установить триггер на ту или иную подсистему, требуется, чтобы эта подсистема была запущена, а сущности были созданы. Для контроля запуска использовался специальный триггер
on_schema_init
, который позволял в его обработчике разместить код установки других триггеров. И… брр!!! Код превращался в лапшу:space_name = 'my_space'
-- триггеры вкладывали в триггеры, которые вкладывали в триггеры!
box.ctl.on_schema_init(function()
box.space._space:on_replace(function(old, new)
if new and new[3] == space_name then
box.on_commit(function()
box.space[space_name]:on_replace(function()
print('on_replace in ' .. space_name)
end)
end)
end
end)
end)
Как видим, на момент установки триггера требовалось контролировать не только факт инициализации, но и существование нужных объектов в БД. А при перехвате транзакций код был ещё сложнее!
В новой парадигме триггеров мы дадим пользователям возможность устанавливать триггеры задолго до появления в системе сущностей. Это позволит значительно ослабить связи своего кода с подсистемами Tarantool, не потеряв в функциональности. Нет объекта — триггер просто не вызывается.
Вторым недостатком старых триггеров было отсутствие возможности работы кода, устанавливающего триггеры, при его горячей перезагрузке. Так называемый Hot-Reload кода. Проблема состояла в том, что если пользователь перезагрузил один или несколько Lua-модулей, не перегружая Tarantool, то в момент перезагрузки инициализационная часть кода может выполниться повторно. Из-за этого пользователь должен был крайне осторожно устанавливать триггеры изнутри секции инициализации модулей.
Для того чтобы разобраться со всеми обозначенными проблемами, мы предоставляем пользователям новый инструмент — реестр триггеров (gh-8656).
Реестр триггеров по своему функционалу очень похож на различные шины именованных событий (вроде DBus в Linux). Чтобы подписаться на событие, нужно просто знать его имя. Неважно, существует ли уже система, которая впоследствии отправит сообщение, или она будет загружена позднее — на подписчике это не отразится.
Каждый триггер теперь определяется двумя понятиями:
- имя события — связывает триггер с событием, которое приведёт к его срабатыванию;
- имя триггера (определяет пользователь) — позволяет привязать обработчик к коду, решая вторую проблему, описанную выше.
Например, чтобы подписаться на изменения в спейсе
myspace
, нужно установить обработчик приблизительно так:trigger = require 'trigger'
trigger.set(
'box.space.myspace.on_replace', -- имя события
'my-cool-trigger', -- имя триггера
function(...)
-- код триггера
end
)
Никакой «лапши» теперь не требуется. Устанавливать обработчики триггеров можно там, где удобно. Никакой Hot-Reload кода не повредит таким триггерам: просто используйте один и тот же идентификатор при установке триггера, и всё.
Если
myspace
по какой-то причине будет отсутствовать, то триггер просто не будет использоваться до тех пор, пока спейс не создадут.Полезные эффекты
В такой парадигме можно писать код, умеющий работать в разном окружении. Например, модуль A генерирует событие X. Но модуль A не всегда используется. Пользователь всё равно может установить обработчик на X. Если модуль A отсутствует (например, пользователь не включил фичу в конфиге), то обработчик просто не будет вызываться.
Это позволит ослаблять связи не только между кодом пользователя и ядром, но и между различными частями пользовательского кода.
Метаданные в API (IPROTO)
В 3.0 мы наконец решили одну из очень старых проблем. Если рассматривать практически любой кластер на Tarantool, часто можно встретить:
- запросы от одного узла кластера к другому узлу;
- каскадную трансляцию запросов по кластеру.
При этом если рассмотреть call-запросы к базе данных, то большинство функций, которые пишет пользователь, так или иначе транслируют выборки из базы данных:
function foo(...)
-- какой-то код
local data = space:select(...)
for _, t in pairs(data) do
print(t.id, t.name)
end
return data
end
Проблема такого кода в том, что в самих функциях мы можем обращаться к именам полей в таплах, однако клиент, сделавший IPROTO-call, информацию о структуре тапла не получит.
Чтобы решить эту проблему, пользователи были вынуждены прибегать к разным мерам, например:
- сопоставлять на клиенте имена функций с форматами возвращаемых ими данных;
- игнорируя повышение трафика возвращать не таплы, а мапы с данными (по аналогии с передачей всех данных в JSON).
В 3.0 IPROTO-протокол расширен. Теперь если тапл выбран из БД, клиент может манипулировать метаданными (имена полей тапла) так, как если бы запрос происходил локально. Помимо прочего, нововведения протокола позволят радикально упростить процесс создания новых коннекторов к Tarantool. Важно отметить, что выполненные изменения позволяют сохранить обратную совместимость и не увеличивают трафик (и иной оверхед) в случае, если не используются клиентом.
Переопределение API (IPROTO)
Tarantool постоянно развивается. Появляются новые запросы, иногда расширяются имеющиеся. Как раз очередное расширение описано в разделе выше.
При этом изменения функциональности новых версий Tarantool — часто недостаточное условие для апдейта инсталляции. Но именно новые фичи могут быть интересны или полезны пользователям.
Начиная с версии 3.0, в Tarantool появится возможность писать полифилы. Полифил в данном случае будет представлять собой модуль, который можно будет приложить, например, к версии 3.0, чтобы в ней эмулировать функциональность, которая появится только в версии 4.0 или 5.0.
Вообще-то, поддержка полифилов отчасти доступна уже с версии 2.11, но полностью реализуется только в 3.0. Планируется, что этой возможностью смогут воспользоваться разработчики как Tarantool, так и тех или иных внешних модулей. Если бы подобный механизм был доступен ранее, то сегодня мы могли бы выпустить, например, полифил для недавно появившегося механизма — однократных подписок.
Теперь клиентский код сможет переопределить любой сетевой запрос, а также доопределить новые. Интерфейс у этой подсистемы реализован через вышеописанный реестр триггеров.
local trigger = require 'trigger'
-- все запросы IPROTO::select будут возвращать тапл (1, 2, 3)
trigger.set(
'box.iproto.override.select',
'my-cool-select',
function (header, body)
-- Здесь мы перехватили запрос SELECT,
-- можем передать системному обработчику, вернув false
-- а можем ответить что-то своё, например так:
box.iproto.send(
box.session.id(),
{
request_type = box.iproto.type.OK,
sync = header.SYNC,
schema_version = box.info.schema_version
},
{
data = { box.tuple.new{1, 2, 3} }
}
)
-- уведомляем систему, что мы сделали всю работу сами
-- обрабатывать запрос/отправлять ответ не требуется
return true
end
)
Упрощение настройки репликации
Мы поработали над тем, чтобы процесс бутстрапа репликасета и подключения к репликам стал более интуитивным. В конфигурации появилась возможность указать
bootstrap_leader
репликасета. Эта настройка позволяет указать узел, который создаст начальный снапшот и зарегистрирует всех участников репликасета. Так можно избавиться от ситуаций, когда порядок запуска узлов, отдельные настройки на них или долгий запуск отдельных узлов иногда приводил к тому, что репликасет собирался неправильно. С ними особенно часто сталкивались новички.Также мы убрали опцию
replication_connect_quorum
. Она отвечала за необходимое количество подключений к другим репликам и применялась в двух совершенно разных случаях: при бутстрапе и при обычном запуске узла. В результате нельзя было настроить эту опцию так, чтобы она хорошо работала в обоих случаях. Теперь этой опции нет, а Тарантул автоматически вычисляет необходимое количество подключений для каждого случая.Имена узлов вместо UUID
В Tarantool долгое время не существовало удобного способа идентифицировать конкретный инстанс базы данных. Узлам присваивались случайные (или указанные пользователем) UUID.
В результате в логах можно было встретить следующие сообщения:
subscribed replica bdd101b9-0164-48ff-b94c-2987bdb24152 at fd 21, aka
[::1]:3301, peer of [::1]:65379
Было неудобно каждый раз выяснять, кто такая
replica bdd101b9-0164-48ff-b94c-2987bdb24152
и из какого именно она репликасета. Это добавляло нагрузки на эксплуатацию при разборе инцидентов и мониторинге.Некоторые пришли к тому, что задавали UUID явным образом, кодируя в одной его части репликасет, к которому принадлежит узел, а в другой — номер узла в репликасете:
aaaaaaaa-0000-0000-0000-111111111111
А отдельные умельцы даже писали утилиты, превращающие понятные для человека имена инстансов в их относительно читаемый аналог, подходящий для UUID:
tarantool> encode_uuid("storage-alfa-15")
---
- 57012a6e-a1fa-1500-0000-000000000000
...
tarantool> encode_uuid("router-alfa-15")
---
- 12047e12-a1fa-1500-0000-000000000000
...
Начиная с версии 3.0, пользователь может назначать узлам и репликасетам обычные человекочитаемые имена — например,
router_001, storage_001, replicaset_001
. Эти же имена понимает утилита tt.Расширенная статистика по потреблению памяти
Иногда у пользователей возникает желание узнать размер памяти, занятой конкретным спейсом. Обычно для этого используют метод
space:bsize()
, который возвращает суммарный размер данных (после кодирования в MessagePack) всех кортежей в спейсе. Но кроме самих данных, каждый кортеж требует ещё некоторый размер памяти для хранения служебной информации.Те, кто читал статью «Работа с памятью в Tarantool», знают, что для выделения кортежей используется аллокатор Small, который обладает следующей особенностью:
Small alloc — это коллекция Mempools разного размера. Если нам надо выделить, скажем, 28 байт, то для этого выбирается наиболее подходящий Mempool, например тот, который выделяет 32 байта, и такой блок будет результатом.
Если размеры Mempools при настройке были выбраны неудачно, то потери памяти от этого округления могут быть довольно большими. И если раньше не было простых способов выявить такую фрагментацию, то в Tarantool 3.0 на помощь приходит метод
space:stat()
.Рассмотрим на примере. Пусть тестовый спейс содержит около 500 Кб данных.
tarantool> box.space.test:bsize()
---
- 504548
...
В базе нет других спейсов, поэтому
box.info.memory()
вернёт примерно такое же число. Оно немного больше, так как учитывает внутренние системные данные.tarantool> box.info.memory().data
---
- 546701
...
Но если посмотреть в
box.slab.info()
, то оказывается, что на хранение данных потрачено 800 Кб.tarantool> box.slab.info().items_used
---
- 808552
...
Куда делись ещё 260 Кб? На этот вопрос отвечает новый метод
space:stat()
.tarantool> box.space.test:stat()
---
- tuple:
memtx:
header_size: 8992
field_map_size: 4000
data_size: 504548
waste_size: 245972
malloc:
header_size: 0
field_map_size: 0
data_size: 0
waste_size: 0
...
Где:
header_size
иfield_map_size
— размер служебной информации;
data_size
— размер данных, это число полностью совпадает сbsize()
;
waste_size
— те самые потери из-за округлений до размера Mempools.
Более того, можно получить такую же информацию не обо всём спейсе, а о конкретном кортеже.
tarantool> box.space.test:get(42):info()
---
- arena: memtx
header_size: 10
field_map_size: 4
data_size: 361
waste_size: 273
...
Ещё одной особенностью аллокатора Small является то, что размер выделяемого блока ограничен несколькими мегабайтами. Но кортеж должен храниться в памяти непрерывно, поэтому для аллокации больших кортежей используется простой системный
malloc()
. Теперь это тоже видно в статистике.Значения полей по умолчанию
Теперь любому полю кортежа можно назначить значение по умолчанию, которое применится при вставке, если соответствующее поле в кортеже отсутствует. Это значение указывается в формате спейса при помощи ключевого слова
default
, например:tarantool> box.space.test:format({
> {name = 'id', type = 'unsigned'},
> {name = 'name', type = 'string', default = 'No name'},
> {name = 'pass', type = 'string'},
> {name = 'shell', type = 'string', default = '/bin/sh'}})
---
...
Попробуем вставить кортеж, у которого есть поля
id
и pass
, но нет name
и shell
:tarantool> box.space.test:insert({1000, nil, 'qwerty'})
---
- [1000, 'No name', 'qwerty', '/bin/sh']
...
Как видно, отсутствующие поля заполнились значениями по умолчанию.
Кроме того, значение по умолчанию может не быть статическим, а вычисляться пользовательской функцией в момент вставки. В качестве примера напишем функцию
random_point
, которая генерирует случайное число в диапазоне от min
до max
:tarantool> box.schema.func.create('random_point', {
> language = 'Lua',
> body = 'function(param) return math.random(param.min,
> param.max) end'})
---
...
Имя функции указывается в формате спейса при помощи ключевого слова
default_func
:tarantool> box.space.test:format({
> {name = 'id', type = 'unsigned'},
> {name = 'latitude', type = 'number',
> default_func = 'random_point',
> default = {min = -90, max = 90}},
> {name = 'longitude', type = 'number',
> default_func = 'random_point',
> default = {min = -180, max = 180}}})
---
...
Обратите внимание, что если задана
default_func
, то default
выступает в качестве параметра этой функции. Вставляем кортеж с полем id
, и оставшиеся 2 поля заполняются автоматически:tarantool> box.space.test:insert({1})
---
- [1, 56, 38]
...
Новые DEB/RPM-пакеты
В этом релизе мы не только ограничились различными функциональными улучшениями и новыми фичами, но и немного пересмотрели подход доставки нашего продукта до конечного пользователя. На данный момент самым простым способом получить Tarantool является установка соответствующего набора DEB/RPM-пакетов на хост. В предыдущих версиях мы старались поддержать все наиболее популярные Linux-дистрибутивы и их свежие версии, но число запросов на поддержку того или иного дистрибутива с той или иной версией не уменьшалось.
В итоге мы приняли решение пересмотреть концепцию поставки Tarantool и отказаться от бесчисленного количества пакетов, собранных под определенный дистрибутив и версию. Вместо этого, начиная с релиза 3.0, мы будем поставлять только 2 набора пакетов (DEB и RPM), но со статически скомпилированным Tarantool внутри. Такой подход позволит устанавливать DEB/RPM-пакеты на любые дистрибутивы (основанные на каноничных СentOS/Debian), в том числе и на отечественные (REDOS, Astra и др.), набирающие популярность в последнее время.
Для гарантированной работы Tarantool даже на не самых молодых дистрибутивах статическая сборка Tarantool для RPM-пакетов будет производиться на CentOS 7 (где glibc имеет версию 2.17), а для DEB-пакетов — на Ubuntu Xenial (где glibc имеет версию 2.23).
Обработка Lua-ошибок на трассах
В процессе исполнения Lua-скриптов в Tarantool посредством LuaJIT выделяются «горячие» участки кода. Эти участки затем компилируются в нативно исполняемый машинный код, тем самым увеличивая производительность. У скомпилированного Lua-кода была очень старая проблема, связанная с отсутствием возможности поймать и обработать внутреннюю ошибку компилятора с помощью pcall.
К таким ошибкам, например, относятся:
- Out of memory, которую можно было часто наблюдать в пользовательских скриптах на сборках Tarantool с 32-битным Lua GC, например при длинном select;
- Table overflow, возникающая при преодолении максимально допустимого количества ключей в таблице.
Рассмотрим, например, вот такой скрипт, который пытается заполнить Lua-таблицу большим количеством ключей:
local function memory_payload()
local t = {}
for i = 1, 1e10 do
t[ffi.new('uint64_t')] = i
end
end
local res, err = pcall(memory_payload)
print(res, err)
Ранее, несмотря на наличие pcall, он завершился бы для сборки Tarantool с 32-битным Lua GC вот с такой ошибкой:
PANIC: unprotected error in call to Lua API (not enough memory)
Теперь же он корректно обработает ошибку и выведет следующее:
false not enough memory
Этот же скрипт для Tarantool с 64-битным Lua GC ранее завершался с ошибкой переполнения таблицы:
PANIC: unprotected error in call to Lua API (table overflow)
Теперь и эта ошибка будет обработана корректно:
false table overflow
Таким образом, использование Tarantool 3.0 позволит системе сохранять устойчивость даже при ошибках в пользовательских алгоритмах, которые раньше приводили к краху всего сервера.
Новый CLI
Несмотря на то что новый CLI развивается независимо от Tarantool, он всё равно стоит упоминания в этой статье.
Исторически сложилось, что у Tarantool было несколько частично реализованных CLI.
tarantoolctl
, который был не столько CLI, сколько средством запуска инстансов Tarantool, реализованном для интеграции с операционными системами. Cartridge CLI, с одной стороны, отчасти дублировал функции tarantoolctl
, а с другой — был предназначен исключительно для управления cartridge
.В какой-то момент произошла классическая ситуация:
То есть было принято решение реализовать обобщённый CLI, который реализует в себе все старые фичи и будет вбирать новые.
Возможно, кто-то решит пошутить, но уже сейчас новый CLI действительно умеет больше, чем сумма двух предыдущих.
Репозиторий нового CLI расположен здесь. Для удобства запоминания главную утилиту нового CLI решили назвать просто
tt
.Важно, что это переносит разработку CLI с серверной стороны в отдельный проект, не зависящий от сервера. Теперь, чтобы получить новые фичи в консоли, не требуется обновлять сервер.
Ну и консоль наконец-то начала развиваться: появилась возможность вывода не только в YAML. Теперь можно делать запросы и видеть их результаты в виде привычных (по другим БД) табличек.
Помимо прочего, новый CLI включает или в самое ближайшее время будет включать команды:
- создания
backup
(раньше требовалось файлики копировать вручную);
- импорта/экспорта данных из выбранных спейсов (пока в Enterprise-версии);
- интеграции всех вариантов кластера с операционной системой (скрипты и юниты для systemd и других систем инициализации);
- сборки/установки пользовательских Tarantool-программ в пакеты tgz, deb, RPM, Docker;
- поддержки работы разных версий, разных инсталляций Tarantool из разных директорий (а-ля Git);
- позволяющие установить ту или иную версию Tarantool в пользовательское окружение (в том числе Enterprise);
- и многое другое.
А самое интересное — при помощи этого CLI можно манипулировать множеством версий Tarantool в userspace разработчика. С помощью нового CLI вы можете легко перенастраивать ваше приложение из, скажем, Tarantool 1.10 в, например, Tarantool 3.0. Это будет невероятно помогать при миграциях как в процессе разработки, так и в процессе эксплуатации.
Заключение
Конечно же, общее количество новых фич в Tarantool 3.0 существенно выше — ознакомиться с полным перечнем можно, заглянув в Changelog. Но если описать их все, размер статьи станет неприлично огромным. Поэтому пробуйте, испытывайте, изучайте. Может быть, у вас получится более точно определить, что же такое Tarantool: сервер приложений с БД на борту или наоборот?