Что такое Tarantool: in-memory СУБД и сервер приложений

Рейтинг: 67.6% · 8 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Что такое Tarantool: in-memory СУБД и сервер приложений

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений (вы здесь)
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Краткий обзор

Tarantool - это in-memory СУБД и Lua-сервер приложений, объединённые в ОДНОМ процессе. То, что обычно делают две разные системы (например, PostgreSQL хранит данные, а отдельный сервис на Python/Java крутит бизнес-логику и ходит в Redis за кэшем), Tarantool делает внутри себя. Данные лежат в оперативной памяти рядом с кодом, который их обрабатывает, поэтому запрос не уходит по сети из приложения в базу и обратно - логика выполняется ТАМ ЖЕ, где данные.

Tarantool даёт ACID-хранилище: транзакции атомарны, изменения переживают перезапуск благодаря write-ahead log (WAL) и снапшотам. Он поставляется в двух редакциях - Community Edition (открытая, бесплатная) и Enterprise Edition (SDK с закрытыми модулями: шифрование трафика, аудит, supervised failover, сжатие кортежей и т.д.). В этом курсе мы работаем только с официальными компонентами CE.
Главная идея, которую надо унести из урока: Tarantool - это не "ещё один Redis" и не "ещё один Postgres". Это сервер приложений с встроенной транзакционной in-memory базой. Код живёт рядом с данными.
Как это устроено внутри

Один процесс - несколько ОС-потоков

Запрос, пришедший по сети, обрабатывают три типа потоков операционной системы:
  • Сетевой поток (network / iproto) - принимает запрос по бинарному протоколу iproto, парсит его, проверяет и превращает в сообщение (исполняемый стейтмент с опциями).
  • Транзакционный поток (TX thread) - получает это сообщение через lock-free шину сообщений и собственно работает с данными: ищет/меняет кортеж по индексу либо вызывает хранимую функцию. Lua-код исполняется прямо здесь, без отдельного парсинга.
  • Поток WAL - на коммите записывает изменения в журнал на диск; пока идёт запись, файбер транзакции приостановлен, а после ответа WAL - возобновляется, и результат уходит обратно в сетевой поток клиенту.
Ключевой и часто шокирующий факт: TX-поток в инстансе ровно ОДИН. Только он имеет доступ к базе. Никакого "поток №1 читает строку, поток №2 пишет строку" - этого в Tarantool не бывает. Потоки не делят состояние, а обмениваются данными через дешёвые очереди сообщений. Это убирает конкуренцию за шину памяти и блокировки, но означает: масштабирование на много ядер - это шардирование на несколько инстансов, а не "добавь потоков".

Файберы и кооперативная многозадачность

Внутри единственного TX-потока крутятся тысячи файберов (fibers) - лёгких корутин. В отличие от ОС-потоков (вытесняющая многозадачность, ядро в любой момент может прервать), файберы используют кооперативную многозадачность: файбер выполняется до тех пор, пока сам не дойдёт до точки yield. На yield управление передаётся следующему готовому (ready) файберу. Состояния файбера: running, suspended, ready (и dead после смерти).

Yield бывает явный (fiber.yield(), fiber.sleep(t)) и неявный - на сетевом I/O, файловых операциях, и, самое важное, на коммите транзакции (запись в WAL). Для memtx-движка чтение не делает yield (данные уже в RAM), а изменение делает yield только на коммите. Отсюда вытекает фундаментальное свойство: пока в критической секции нет yield, никто не вклинится в выполнение - это делает программные блокировки и мьютексы ненужными. Нет гонок, нет проблем с консистентностью памяти. Расплата: если ваша функция делает тяжёлые вычисления и долго не yield-ит, она "захватывает" весь инстанс и тормозит всех остальных клиентов. Ответственность за дружелюбные yield-ы лежит на авторе функции (есть защитный механизм fiber slice).

Движки хранения и память

Данные лежат в спейсах (spaces, аналог таблиц), записи - это кортежи (tuples, по сути массивы полей в формате MsgPack), доступ к ним - через индексы. Движков два:
  • memtx (по умолчанию) - всё в RAM, чтение обычно под 1 мс, индексы TREE/HASH/RTREE/BITSET, на чтении не yield-ит.
  • vinyl - дисковый LSM-движок для данных, которые больше доступной RAM; только TREE-индексы, yield-ит даже на чтении (данные могут быть на диске).
В TX-потоке выделена область памяти - Arena. Кортежи хранит slab-аллокатор (модуль box.slab для статистики). Память не "бесконечна": задаётся лимитом (memtx.memory / box.cfg.memtx_memory). Можно смешивать memtx- и vinyl-спейсы в одном инстансе, выбирая скорость или объём для каждой таблицы.

Долговечность: WAL + снапшоты

In-memory не значит "потеряем при выключении". Каждое изменение пишется в write-ahead log (файлы .xlog). Периодически checkpoint-демон (это тоже файбер) делает снапшот - полную копию данных на момент времени (файлы .snap). При восстановлении Tarantool грузит свежий снапшот и доигрывает (redo) запросы из WAL поверх него. Режимы WAL: write (по умолчанию, без ожидания flush), fsync (гарантированно на диск), none (без журнала - данные не переживут рестарт).

Изображение
Архитектура Tarantool: СУБД и сервер приложений в одном процессе

Ключевые команды и код

Запустить интерактивную консоль и создать спейс с индексом (классический трек, box.cfg - работает в 1.x/2.x/3.x):

Код: Выделить всё

-- стартуем базу, отдаём ей до 256 МБ под данные
box.cfg{ listen = 3301, memtx_memory = 256 * 1024 * 1024 }

-- создаём спейс и первичный индекс
local s = box.schema.space.create('users')
s:format({ {name='id', type='unsigned'}, {name='name', type='string'} })
s:create_index('primary', { parts = {'id'} })

-- пишем и читаем
s:insert{1, 'alice'}     -- INSERT (yield на коммите в WAL)
s:get{1}                 -- SELECT по PK (memtx: без yield)
s:replace{1, 'alice2'}   -- UPSERT-подобная замена по PK
Декларативный трек 3.x: ту же базу настраивают не кодом, а YAML-конфигом (config.yaml). Начиная с 3.0 настройка кодом считается legacy:

Код: Выделить всё

groups:
  group001:
    replicasets:
      replicaset001:
        instances:
          instance001:
            iproto:
              listen:
                - uri: '127.0.0.1:3301'
            memtx:
              memory: 268435456   # те же 256 МБ
Tarantool понимает и SQL - один инстанс принимает и Lua, и SQL-запросы:

Код: Выделить всё

box.execute([[ CREATE TABLE items (id INT PRIMARY KEY, qty INT) ]])
box.execute([[ INSERT INTO items VALUES (1, 10) ]])
box.execute([[ SELECT * FROM items WHERE id = 1 ]])
Где Tarantool особенно силён (его ниши):

Код: Выделить всё

Ниша          Что даёт Tarantool
-----------   --------------------------------------------------
Кэш           write-behind кэш + вторичные индексы + сложная
              инвалидация; часто заменяет связку Postgres+Redis
Очередь       smart queue: планирование задач, lifecycle,
              архивация выполненных (модуль queue)
Real-time     данные в RAM + логика рядом, латентность под 1 мс,
              сотни тысяч RPS на инстанс
Primary store ACID, durable (WAL+снапшоты), не нужна вторичная БД
Частые заблуждения и грабли
  • "Это просто быстрый Redis." Нет. Redis - внешнее хранилище, к которому ходят из любого языка. Tarantool - сервер приложений: вы заталкиваете бизнес-логику в слой данных через Lua-хранимки, получаете вторичные индексы и НАСТОЯЩИЕ ACID-транзакции.
  • "In-memory - значит, потеряю данные при сбое." Нет, при wal_mode write/fsync есть WAL + снапшоты и репликация (в т.ч. синхронная, на базе Raft).
  • "Добавлю потоков - станет быстрее." TX-поток один. Тяжёлый CPU-цикл без yield заблокирует ВЕСЬ инстанс. Масштабирование - шардирование на инстансы (vshard).
  • "select() и insert() по очереди в консоли внутри box.begin() - это одна транзакция." Без MVCC консоль делает неявный yield после каждой команды, и такая транзакция оборвётся. Оборачивайте логику в box.atomic()/функцию.
  • Out-of-memory при "свободной" памяти. Из-за фрагментации slab-аллокатора (много разных классов размеров) можно упереться в лимит, даже когда утилизация невысокая. Мониторьте box.slab.info() и box.slab.stats().
  • Операции модуля os НЕ кооперативные - os.* монопольно блокирует весь TX-поток. Для I/O используйте fio, socket, popen.
Мини-лаба
Запустите Tarantool в интерактивном режиме (tarantool без аргументов либо tt). Выполните box.cfg{} (память по умолчанию), создайте спейс events с первичным индексом по полю id (unsigned). Вставьте 3 кортежа. Затем выполните box.snapshot() и посмотрите в рабочей директории появившийся файл .snap. Бонус: вызовите box.slab.info() и найдите поля arena_used / arena_size - это и есть та самая Arena из урока.
Контрольные вопросы
  • Сколько TX-потоков в одном инстансе Tarantool и почему из этого следует, что тяжёлый цикл без yield опасен?
  • Чем кооперативная многозадачность файберов отличается от вытесняющей многозадачности ОС-потоков и почему это делает мьютексы ненужными?
  • Как Tarantool, будучи in-memory, не теряет данные при перезапуске? Назовите два механизма и опишите процесс восстановления.
  • В каких трёх нишах Tarantool заменяет связку "СУБД + отдельный кэш + сервер логики" и за счёт какого архитектурного свойства?
👍5 ❤️1 🔥 😄 🤔3
Ответить
Следующая глава →
Архитектура изнутри: процесс, потоки, event-loop

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость