В прошлом году Tarantool исполнилось 15 лет. Он прошел долгий путь от обычного кэша до платформы управления данными с десятками разных внутренних продуктов и расширений. Такое обилие инструментов создает множество возможностей — и в этой статье мы расскажем о десяти, о которых многие забывают или попросту не знают.
Кластерный конфиг
Одно из главных нововведений в последней мажорной версии Tarantool — единая точка конфигурации.
Настраивать Tarantool через box.cfg слишком сложно? Неудобно хранить настройки в Lua-файлах или передавать их через env? В Tarantool 3.0 появилась возможность создать общий кластерный конфиг для всех узлов Tarantool.
Чтобы запустить Tarantool, вам понадобится конфиг следующего вида:
# объявляем пользователей в кластере
credentials:
users:
guest:
roles: [super]
# объявляем настройки для подключения к узлу
iproto:
listen:
- uri: 'unix/:./{{ instance_name }}.iproto'
# описываем топологию кластера и настройки всех узлов
groups:
group-001:
replicasets:
replicaset-001:
instances:
instance-001: {}
Потом понадобится выполнить следующую команду:
tarantool --config config.yaml --name instance-001
Что можно указать в конфигурационном файле:
все параметры для настройки Tarantool (то, что раньше указывалось в box.cfg);
топологию, в том числе и для шардированного кластера;
параметры и роли приложений (похожим образом, как в картридже);
параметры фейловера — в скором времени мы добавим их еще больше.
В Tarantool Enterprise конфиг можно хранить в etcd, и обновления будут загружаться в Tarantool автоматически.
Больше примеров лежит в гитхабе.
Персистентность и WAL
Многие все еще думают о Tarantool как о простом кэше, который нельзя использовать для хранения важных данных. Когда-то это было так, но все давно изменилось. Персистентность в Tarantool включена по умолчанию: он всегда сохраняет данные на диске, перед тем как отдать пользователю подтверждение записи.
Но вы всегда можете превратить Tarantool в кэш всего одной настройкой:
box.cfg{wal_mode = 'none'} -- делаем из Tarantool кэш
А для автоматического удаления устаревших данных можно использовать наш модуль expirationd:
local expirationd = require("expirationd")
function is_expired(args, tuple)
return true
end
function delete_tuple(space, args, tuple)
box.space[space]:delete{tuple[1]}
end
expirationd.start(job_name, space.id, is_expired, {
process_expired_tuple = delete_tuple,
args = nil,
tuples_per_iteration = 50,
full_scan_time = 3600
})
Синхра и рафт
Вам нужны дополнительные гарантии при записи? Вас пугает асинхронная репликация? Попробуйте синхру! А если вы хотите автоматическое переключение лидера, не забудьте настроить рафт.
Мы писали о рафте и синхре на Хабре много раз, но, если вы пропустили, рекомендуем ознакомиться подробнее в предыдущих статьях:
Балансируем между консистентностью и доступностью в распределенной системе: опыт Tarantool
Raft (не)всемогущий: какие надстройки повышают надежность алгоритма
Как пользоваться синхрой и рафтом?
Первый узел:
# объявляем пользователей в кластере
credentials:
users:
admin:
password: 'passwd'
roles: [super]
# объявляем настройки для общения инстансов между собой
# (их также можно задавать на уровне узлов, репликасетов и групп)
iproto:
listen:
- uri: 'unix/:./{{ instance_name }}.iproto'
advertise:
peer:
login: 'admin'
# включаем автоматический фейловер на базе Raft
replication:
failover: election
# описываем топологию кластера и настройки всех узлов
groups:
group-001:
replicasets:
replicaset-001:
instances:
instance-001: {}
instance-002: {}
instance-003: {}
Запускаем кластер:
tarantool --config config.yaml --name instance-001
tarantool --config config.yaml --name instance-002
tarantool --config config.yaml --name instance-003
Снова на первом узле:
box.ctl.promote()
box.schema.space.create('employees', {is_sync = true})
box.space.customers:format({
{name = 'id', type = 'integer'},
{name = 'name', type = 'string'},
{name = 'salary', type = 'integer'},
{name = 'department', type = 'string'},
})
box.space.employees:create_index('pk', {parts = {'id'})
-- теперь можно работать с данными:
box.space.customers:insert{...}
Хранение на диске с Vinyl
Tarantool, как и все современные базы данных, умеет сохранять данные на диске.
Процесс создания дискового спейса в Tarantool ничем не отличается от спейсов в памяти:
-- создаем спейс с дисковым движком
box.schema.create_space('former_employees', {engine = 'vinyl'})
box.space.former_employees:format({
{name = 'id', type = 'integer'},
{name = 'name', type = 'string'},
{name = 'salary', type = 'integer'},
{name = 'department', type = 'string'},
{name = 'end_date', type = 'datetime'},
})
box.space.former_employees:create_index('pk', {parts = {'employee_id'}})
-- переносим устаревшие данные в холодное хранилище
box.begin()
for key, tuple in box.space.employees.index.end_date:pairs(now, {iterator = 'LE'}) do
box.space.former_employees:insert(tuple)
box.space.employees:delete(key)
end
box.commit()
При работе с vinyl всегда стоит помнить о том, что Tarantool в первую очередь in-memory-технология. У дискового движка есть серьезные ограничения — например, мы не рекомендуем создавать больше одного индекса для таких данных и делать апдейты в сохраненных данных.
Подробнее о vinyl можно прочитать в статье на Хабре.
Констрейнты и внешние ключи
Хотите, чтобы было как в SQL? Попробуйте констрейнты и внешние ключи.
-- Define a tuple constraint function --
box.schema.func.create('check_dates', {
language = 'LUA',
is_deterministic = true,
body = [[function(t)
if t.end_date then
return t.start_date < t.end_date
end
return
end]]
})
-- Define a field constraint function --
box.schema.func.create('check_salary', {
language = 'LUA',
is_deterministic = true,
body = 'function(f) return f > 0 and f < 10^9 end'
})
box.schema.space.create('departments')
box.space.deparments:format({
{name = 'id', type = 'string'},
{name = 'name', type = 'string'},
{name = 'manager', type = 'string'},
})
box.space.deparments:create_index('pk', {parts = {'id'}})
box.schema.space.create('employees', {constraint = 'check_dates'})
box.space.customers:format({
{name = 'id', type = 'integer'},
{name = 'name', type = 'string'},
{name = 'salary', type = 'integer', constraint = 'check_salary'},
{name = 'department', type = 'string', foreign_key = {space = 'departments', field = 'id'}},
{name = 'start_date', type = 'datetime'},
{name = 'end_date', type = 'datetime', is_nullable = true},
})
box.space.employees:create_index('pk', {parts = {'id'})
Помните, что для некоторых изменений формата вам потребуется произвести дополнительные миграции.
Транзакции, стримы и отменяемые запросы
Вы привыкли, что в транзакциях в Tarantool нельзя идлить? Привыкли к тому, что все транзакции по умолчанию serializable? Хотите более гибкого контроля над транзакциями? Используйте MVCC-движок:
local fiber = require('fiber')
local log = require('log')
box.cfg{memtx_use_mvcc_engine = true}
box.schema.create_space('test')
box.space.test:format{{name='first', type='string'}, {name='second', type='integer'}}
box.space.test:create_index('pk', {parts = {'first'}})
box.space.test:put{'value', 1}
fiber.new(function() v1 = box.space.test:select() end) -- этот код исполнится после йилда
box.atomic(function()
box.space.test:put{'value', 2}
fiber.yield()
v2 = box.space.test:select()
box.space.test:put{'value', 3}
end)
v3 = box.space.test:select()
log.info(v1, v2, v3)
---
# это значение в момент йилда в транзакции,
# транзакция не завершена, поэтому мы получаем старое значение
- - ['value', 1]
# это значение, прочитанное в транзакции
# в рамках самой транзакции эти данные уже существуют, поэтому здесь 2
- - ['value', 2]
# это значение, полученное после завершения транзакции
- - ['value', 3]
...
Вы также можете использовать интерактивные транзакции или выбирать время, через которое долгие запросы будут отменены:
fiber.set_max_slice{warn = 0.5, err = 1.0}
SQL
Вы страдаете от того, что нужно писать сложные запросы на Lua? Боитесь, что ваши аналитики не справятся с Tarantool? Напишите запросы на SQL! (Или прочитайте статью про сложные запросы в Tarantool).
Вот так можно создать таблицу и написать к ней запросы на SQL.
1. Для начала вам потребуется поменять язык в тарантульной консоли (если вы работаете через подключение к узлу) с помощью \set language sql
или позвать команду box.execute
и передать туда свой SQL-запрос.
Создаем таблицы:
CREATE TABLE employees (
id INTEGER,
name STRING,
salary INTEGER,
department STRING,
PRIMARY KEY (id)
);
CREATE TABLE departments (
id STRING,
manager STRING,
PRIMARY KEY (id)
);
2. Создаем индексы:
CREATE INDEX salary ON employees (salary);
3. Вставляем данные:
INSERT INTO departments VALUES ('Sales', 'Mighty Manager');
...
INSERT INTO employees VALUES (1, 'John', 100000, 'Sales');
...
4. Пишем запросы:
SET SESSION "sql_seq_scan" = true;
SELECT department, AVG(salary)
FROM employees
GROUP BY department;
---
- metadata:
- name: department
type: string
- name: COLUMN_1
type: integer
rows:
- ['Sales', 100000]
5. Пишем джойны:
SELECT employees.name, employees.salary, employees.department, department.manager
FROM employees JOIN departments
ON (employees.department = departments.id);
- metadata:
- name: name
type: string
- name: salary
type: integer
- name: department
type: string
- name: manager
type: string
rows:
- ['John', 100000, 'Sales', 'Mighty Manager']
Больше примеров — в гайде по Tarantool.
И помните, что с любыми тарантульными спейсами можно работать как из Lua, так и из SQL, меняя подход в зависимости от задачи.
Шардирование
Шардирование — стандартный подход к масштабированию баз данных. В отличие от многих реляционных БД, в Tarantool шардирование давно стало стандартным модулем и поддерживается в конфиге. Все, что нужно сделать, — установить модуль vshard (tt rocks install vshard
) и написать кластерный конфиг для Tarantool:
credentials:
users:
admin:
password: 'passwd'
roles: [super]
iproto:
listen:
- uri: 'unix/:./{{ instance_name }}.iproto'
advertise:
peer:
login: 'admin'
sharding:
login: 'admin'
sharding:
bucket_count: 10000
replication:
failover: election
groups:
storages:
sharding:
roles: [storage]
replicasets:
storages-001:
instances:
storage-001:
iproto:
listen:
- uri: 'localhost:3301'
storage-002:
iproto:
listen:
- uri: 'localhost:3302'
storages-002:
instances:
storage-003:
iproto:
listen:
- uri: 'localhost:3303'
storage-004:
iproto:
listen:
- uri: 'localhost:3304'
routers:
replicasets:
routers-001:
sharding:
roles: [router]
instances:
router-001:
iproto:
listen:
- uri: 'localhost:3300'
tt connect admin:passwd@localhost:3300
Чтобы начать работать с шардом, надо забутстрапить кластер:
vshard.router.bootstrap()
Теперь можно вставлять данные. На роутере:
vshard.router.callrw('box.space.test_space:insert', {...})
Хранение данных в различных парадигмах
Современный Tarantool позволяет вам применять различные парадигмы для работы с данными. Вот некоторые из примеров различных подходов, которые вы можете комбинировать в Tarantool:
key-value, document. Настройки формата в Tarantool позволяют вам не применять никакой формат вообще, что дает вам построить документное или kv-хранилище.
cache/master-storage. Tarantool можно использовать в качестве кэша (см. выше) или надежно хранить там важные данные.
in-memory/disk. Держите ваши спейсы в памяти, сгрузите все на диск или сделайте и то, и другое.
graph. Вы можете написать графовое хранилище самостоятельно, воспользовавшись нашими R-TREE-индексами, или взять готовый Tarantool Graph DB (Enterprise only).
column. Что? Хранение данных в колонках в памяти? В Tarantool? Да, с недавнего времени в Tarantool ЕЕ появился новый движок для хранения колонок.
queue. На Tarantool построено огромное количество очередей. Есть как бесплатные Tarantool queue и Sharded-queue, а есть Tarantool Queue Enterprise. Подробнее об очередях можно почитать в статье на Хабре.
cluster, multi-cluster. Tarantool отлично работает в кластерном режиме. Вы можете строить свои шардированные хранилища на базе опенсорсных vshard и Cartridge, а можете взять энтерпрайзные TDG или TDB. Также на Tarantool можно создать два кластера, которые полностью дублируют свои данные, с помощью Tarantool Clusters Federation.
Графический интерфейс
Управлять кластером всегда проще, когда можно взглянуть на страницу с обновляющимися онлайн статусами. Tarantool есть что вам предложить.
Grafana Dashboard
Тарантульный дашборд с метриками предоставляет все возможные параметры, за которыми можно следить. Все, что вам нужно знать о вашем тарантульном кластере, находится здесь: память, CPU и все возможные параметры рантайма в одном месте. Подробнее в статье на Хабре.
Если вы не любите настраивать кластеры через консоль или вам просто нужен удобный дашборд, где можно быстро узнать статус кластера и исправить известные проблемы в несколько кликов мышки, то вам следует попробовать один из наших продуктов с графическим интерфейсом.
Cartridge
Для Tarantool 1.10 / 2.х есть Cartridge — как создать первое приложение c его помощью, можно прочитать по ссылке. Помимо отображения информации о состоянии кластера, Cartridge занимается управлением кластерами и предоставляет свое API для различных настроек в кластере.
Tarantool Cluster Manager
Для новых кластеров на Tarantool 3.х есть Tarantool Cluster Manager (EE-only). В отличие от Cartridge, в TCM меньше управления кластером Tarantool (теперь кластерными вещами занимается сам Tarantool), но при этом больше возможностей для мониторинга и управления отдельными узлами, в том числе в разных кластерах.
Заключение
Это далеко не все возможности, которые может вам предложить современный Tarantool: достойны упоминания триггеры, кастомные аллокаторы, подключение к узлу из разных потоков и метрики в ядре по умолчанию. Но в одной статье все не перечислишь. Поэтому если вы пользуетесь Tarantool, следите за обновлениями. А если еще нет — обязательно попробуйте, у Tarantool наверняка есть что вам предложить.
Узнавайте о новых релизах, вебинарах и выходящих статьях в телеграм-канале Tarantool News.
Задать вопросы команде разработчиков про использование Tarantool можно в официальном канале сообщества.
О принципах и примерах работы продуктов Tarantool читайте в блоге на сайте.
theonevolodya
А что насчёт ttl записей?
hogstaberg
Хранить в отдельном поле timestamp записи, отфильтровывать неактуальные записи при выборке данных, периодически в фоне гонять задачу удаления устаревших записей. Отдельного расширения это не требует.