Tarantool vs Redis: что умеют in-memory технологии

ygt6kvwjr0gdvnmpds7s7n-iros.jpeg

В этой статье я хочу сравнить Redis и Tarantool. У меня нет цели сделать громогласный вывод «Tarantool лучше!» или «Redis круче!». Я хочу понять их сходства и отличия, разобраться, для каких задач какую технологию выбрать. Потому что это очень близкие на первый взгляд вещи, и вопросы про их отличия я вижу часто.

Для этого мы посмотрим на технологии в трёх частях:

  • Вначале посмотрим глазами новичка. Что такое БД в памяти? Какие задачи они решают лучше дисковых БД?
  • Потом посмотрим архитектурно. Как обстоит вопрос с производительностью, надёжностью, масштабированием?
  • В третьей части лезем в технические вещи поглубже. Типы данных, итераторы, индексы, транзакции, ЯП, репликация, коннекторы.

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

Поехали!

Содержание


  1. Вводная часть
    • Что такое БД в памяти
    • Зачем нужны решения в памяти
    • Что такое Redis
    • Что такое Tarantool
  2. Архитектурная часть
    • Производительность
    • Надёжность
    • Масштабируемость
    • Валидация схемы данных
  3. Технические особенности
    • Какие типы данных можно хранить
    • Вытеснение данных
    • Итерация по ключам
    • Вторичные индексы
    • Транзакции
    • Персистентность
    • Язык программирования для хранимых процедур
    • Репликация
    • Коннекторы из других языков программирования
    • Под какие задачи плохо подходят
    • Экосистема
    • Чем Redis лучше
    • Чем Tarantool лучше
  4. Вывод
  5. Ссылки

1. Вводная часть


Что такое БД в памяти


Redis и Tarantool — это in-memory технологии. Их ещё называют «резидентными БД», но я буду писать короче — «в памяти» или in-memory. Так что такое БД в памяти?

Это база, которая весь объём данных хранит целиком в оперативной памяти. Размер такой базы лимитирован ёмкостью оперативной памяти узла, что может ограничивать нас в количестве данных, но увеличивает скорость на порядок.

Если данных слишком много, БД в памяти способны сохранять данные на диск. Можно перезагрузить узел и не потерять информацию. Стереотип про ненадёжность БД в памяти сильно устарел, их можно использовать как основное хранилище в production. Например, Mail.ru Cloud Solutions использует Tarantool как основную БД для хранения метаинформации в своём объектном хранилище [1].

БД в памяти нужны для высокой скорости доступа к данным, условно от 10 000 запросов в секунду. Например, запросы к ленте новостей Кинопоиска в день релиза Снайдерката «Лиги Справедливости», Яндекс.Маркет перед Новым Годом или Delivery Club вечером в пятницу.

Зачем нужны решения в памяти


Кеш. БД в памяти традиционно используют как кеш для более медленных баз данных. Это логично, память быстрее диска (даже SSD). Но в жизненном цикле любого кеша случаются перезагрузки, падения, сетевая недоступность, нехватка памяти и прочие инфраструктурные беды.

С течением времени кеши стали уметь в персистентность, резервирование и шардирование.

  • Персистентность — кеши сохраняют данные на диск. После перезагрузки восстанавливаем своё состояние без обращений к основному хранилищу. Если этого не делать, то обращение к холодному кешу будет очень долгим или даже положит основную БД.
  • Резервирование — кеши содержат функции репликации данных. Если упал один узел, то второй возьмёт на себя запросы. Основное хранилище не упадёт от перегруза, нас спасает резервная нода.
  • Шардирование — если горячие данные не помещаются в оперативную память одного узла, мы используем несколько узлов параллельно. Это горизонтальное масштабирование.

Шардирование — это большая система. Резервирование — это надёжная система. Вместе с персистентностью получается кластерное хранилище данных. Можно положить туда терабайты информации и крутить их на скорости 1 000 000 RPS.
4fd403efee1116d9830aab7f064aa6e1.jpg

OLTP. Расшифровывается как Online Transaction Processing, обработка транзакций в реальном времени. In-memory решения подходят для такого типа задач благодаря своей архитектуре. OLTP — это большое количество коротких on-line транзакций (INSERT, UPDATE, DELETE). Главное в OLTP-системах — быстро обработать запросы и обеспечить целостность данных. Эффективность чаще всего определяется количеством RPS.

Что такое Redis


  • Redis называет себя «in-memory хранилище структур данных».
  • Redis — это key-value.
  • Больше всего известен по кешированию дисковых баз. Если вы будете искать по ключевым словам «кеширование баз данных», то в каждой статье найдёте упоминания Redis.
  • Redis поддерживает первичные индексы, не поддерживает вторичные.
  • Redis содержит в себе механизм хранимых процедур на Lua.

Что такое Tarantool


  • Tarantool называет себя «платформа для in-memory вычислений».
  • Tarantool умеет в key-value. А еще в документы и реляционную модель данных.
  • Создан для горячих данных — кеширования MySQL в соцсети. С течением времени развился в полноценную базу данных.
  • Tarantool может строить произвольное количество индексов по данным.
  • В Tarantool тоже можно написать хранимую процедуру и тоже на Lua.

Разобрались с основами, давайте переходить на следующий уровень.

2. Архитектурная часть


Производительность


Это самый любимый запрос про БД в памяти —, а насколько вы быстрые? «Сколько миллионов РПС можно снять с одного ядра?» Проведём простой синтетический тест, в нём максимально приблизим настройки баз данных. Скрипт на Go наполняет хранилище случайными ключами со случайными значениями.
MacBook Pro 2,9 GHz Quad-Core Intel Core i7
Redis version=6.0.9, bits=64
Tarantool 2.6.2

Redis
redis_test.go
package main

import (
    "context"
    "fmt"
    "log"
    "math/rand"
    "testing"

    "github.com/go-redis/redis"
)

func BenchmarkSetRandomRedisParallel(b *testing.B) {
    client2 := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379", Password: "", DB: 0})
    if _, err := client2.Ping(context.Background()).Result(); err != nil {
        log.Fatal(err)
    }

    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            key := fmt.Sprintf("bench-%d", rand.Int31())
            _, err := client2.Set(context.Background(), key, rand.Int31(), 0).Result()
            if err != nil {
                b.Fatal(err)
            }
        }
    })
}

Tarantool
tarantool>
box.cfg{listen='127.0.0.1:3301', wal_mode='none', memtx_memory=2*1024*1024*1024}
box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists=true,})
box.schema.space.create('kv', {if_not_exists=true,})
box.space.kv:create_index('pkey', {type='TREE', parts={{field=1, type='str'}},
                                   if_not_exists=true,})
tarantool_test.go
package main

import (
    "fmt"
    "math/rand"
    "testing"

    "github.com/tarantool/go-tarantool"
)

type Tuple struct {
    _msgpack struct{} `msgpack:",asArray"`
    Key      string
    Value    int32
}

func BenchmarkSetRandomTntParallel(b *testing.B) {
    opts := tarantool.Opts{
        User: "guest",
    }
    pconn2, err := tarantool.Connect("127.0.0.1:3301", opts)
    if err != nil {
        b.Fatal(err)
    }
    b.RunParallel(func(pb *testing.PB) {
        var tuple Tuple
        for pb.Next() {
            tuple.Key = fmt.Sprintf("bench-%d", rand.Int31())
            tuple.Value = rand.Int31()
            _, err := pconn2.Replace("kv", tuple)
            if err != nil {
                b.Fatal(err)
            }
        }
    })
}

Запуск/ Чтобы полностью прогрузить базы данных, используем больше потоков.
go test -cpu 12 -test.bench . -test.benchtime 10s

goos: darwin
goarch: amd64
BenchmarkSetRandomRedisParallel-12          929368         15839 ns/op
BenchmarkSetRandomTntParallel-12            972978         12749 ns/op

Результаты. Среднее время запроса к Redis составило 15 микросекунд, к Tarantool — 12 микросекунд. Это даёт Redis 63 135 RPS, Tarantool — 78 437 RPS.

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

bcd22f9ddf40af09c6c11c5c184e9ea6.png

Надёжность


Для надёжности хранения данных используют две основные техники:
  • Персистентность. При перезагрузке БД загрузит свои данные с диска, не будет запросов в сторонние системы.
  • Репликация. Если упал один узел, то есть копия на втором. Бывает асинхронная и синхронная.

И Redis, и Tarantool содержат эти функции. Технические подробности мы рассмотрим далее.

Масштабируемость


Масштабирование может рассматриваться для двух задач:
  • Зарезервировать дополнительные узлы, которые могут заменять друг друга в случае, если сосед вышел из строя.
  • Данные не помещаются на один узел, и их необходимо распределить на несколько.

Redis

Узлы Redis можно соединить друг с другом асинхронной репликацией. Такие узлы будем называть репликационной группой, или replica set. Управлением такой репликационной группой занимается Redis Sentinel.

Redis Sentinel — это один или несколько объединенных в кластер специальных процессов, которые следят за узлами Redis. Они выполняют четыре основные задачи:

  • Мониторинг узлов в группе: живой или мертвый.
  • Уведомление администратора или какой-то системы, если что-то случилось в группе.
  • Автоматическое переключение мастера.
  • Провайдер конфигурации для внешних клиентов, чтобы они знали, к кому подключиться.

В случае, когда данные необходимо расшардировать на несколько узлов, Redis предлагает open source-версию Redis Cluster. Она позволяет построить кластер, состоящий из нескольких репликационных групп. Данные в кластере шардируются по 16 384 слотам. Диапазоны слотов распределяются между узлами Redis.

Узлы в кластере общаются по отдельному открытому порту, чтобы понимать состояния соседей. Приложение при работе с Redis Cluster должно использовать специальный коннектор.

Tarantool

Tarantool также содержит в себе оба механизма масштабирования: персистентность и репликацию. Основной инструмент управления масштабированием — Tarantool Cartridge. Он объединяет узлы в репликационные группы. В этой ситуации вы можете построить одну такую репликационную группу и использовать её аналогично Redis Sentinel. Tarantool Cartridge может управлять несколькими репликационными группами и шардировать данные между ними. Шардирование выполняется с помощью библиотеки vshard.

Различия

Администрирование

  • Администрирование Redis Cluster — с помощью скриптов и команд.
  • В Tarantool Cartridge администрирование — с помощью web-интерфейса или через API.

Корзины шардирования
  • Количество корзин шардирования в Redis фиксированное, 16 тыс.
  • Количество корзин шардирования Tarantool Cartridge (vshard) произвольное. Указывается один раз при создании кластера.

Ребалансировка корзин (решардинг)
  • В Redis Cluster настройка и запуск вручную.
  • В Tarantool Cartridge (vshard) — автоматически.

Маршрутизация запросов
  • Маршрутизация запросов в Redis Cluster происходит на стороне клиентского приложения.
  • В Tarantool Cartridge маршрутизация запросов происходит на узлах-роутерах кластера.

Инфраструктура
  • Tarantool Cartridge также содержит:

Валидация схемы данных


В Redis основная схема данных — ключ-значение. Но в значениях могут быть разные структуры. На стороне сервера нет механизма для задания правил. Мы не можем указать, в каком ключе какой тип данных должен использоваться и какая именно структура должна быть у значения. Валидацией схемы должен заниматься или коннектор, или клиентское приложение.

В Tarantool на стороне сервера можно использовать валидацию по схеме данных:

  • с помощью встроенной валидации box.space.format, которая затрагивает только верхний уровень полей;
  • с помощью установленного расширения avro-schema.

3. Технические особенности


Какие типы данных можно хранить


В Redis ключом может быть только строка. В Redis можно хранить и манипулировать следующими типами данных:
  • строки;
  • списки строк;
  • неупорядоченные множества строк;
  • хешмапы или просто строковые пары ключ-значение;
  • упорядоченные множества строк;
  • Bitmap и HyperLogLog.

В Tarantool можно хранить и манипулировать следующими типами данных:
  • Атомарными:
  • Комплексными:

Типы данных Redis лучше подходят для счётчиков событий, в том числе уникальных, для хранения небольших готовых витрин данных. А типы данных Tarantool лучше подходят для хранения объектов и/или документов, как в SQL и NoSQL СУБД.

Вытеснение данных


Redis и Tarantool содержат в себе механизм ограничения занимаемой памяти. Когда клиент попытается добавить ещё, базы ответят ошибкой.

Перейдём к другому механизму, когда мы можем настроить алгоритм удаления «больше ненужных» данных. Redis содержит в себе несколько механизмов вытеснения:

  • TTL — вытеснение объектов по завершении срока жизни;
  • LRU — вытеснение давно использованных данных;
  • RANDOM — вытеснение случайно попавшихся под руку объектов;
  • LFU — вытеснение редко используемых данных.

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

В Tarantool для вытеснения данных можно использовать расширения expirationd или indexpiration, или создать собственную фоновую процедуру, которая будет проходить по вторичному индексу (с таймштампом) и удалять ненужные данные.

Итерация по ключам


В Redis можно это сделать с помощью операторов:
  • SCAN;
  • итерация по ключам.

Операции возвращают страницы с результатами. Для получения каждой новой страницы, необходимо передать «идентификатор» предыдущей. Операции поддерживают фильтрацию по шаблону. Для этого используется параметр MATCH. Фильтрация происходит на момент выдачи страницы, поэтому некоторые страницы могут оказаться пустыми. Это не будет означать, что страниц больше не осталось.

В Tarantool доступна гибкая схема итерации по ключам. Можно итерироваться в прямом и обратном направлении. В процессе можно дополнительно фильтровать значения. Можно сместиться на определённое значение ключа, затем проходить по следующим ключам в сторону возрастания или убывания. Направление прохода на лету менять нельзя.

Например:

results = {}
for _, tuple in box.space.pairs('key', 'GE') do
    if tuple['value'] > 10 then
        table.insert(results, tuple)
  end
end
return results

Вторичные индексы


Redis

У Redis нет вторичных индексов. Есть некоторые трюки, чтобы их имитировать:

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

Tarantool

В Tarantool можно строить произвольное количество вторичных индексов для данных:

  • Вторичные ключи могут состоять из нескольких полей.
  • Для вторичных индексов можно использовать типы HASH, TREE, RTREE, BITSET.
  • Вторичные индексы могут содержать уникальные и не уникальные ключи.
  • У любых индексов можно использовать настройки локали, например, для регистронезависимых строковых значений.
  • Вторичные индексы могут строиться по полям с массивом значений (иногда их называют мультииндексы).

Вывод

Вторичные ключи и удобные итераторы позволяют строить в Tarantool реляционные модели хранения данных. В Redis такую модель построить невозможно.

Транзакции


Механизм транзакций позволяет выполнить несколько операций атомарно. И Redis, и Tarantool поддерживают транзакции. Пример транзакции в Redis:
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

Пример транзакции в Tarantool:
TODO box.atomic
do
  box.begin()
  box.space.kv:update('foo', {{'+', 'value', 1}})
  box.space.kv:update('bar', {{'+', 'value', 1}})
  box.commit()
end

Персистентность


Персистентность данных обеспечивается двумя механизмами:
  • периодическим сбросом in-memory данных на диск — snapshoting;
  • последовательной упреждающей записью всех приходящих операций в файл — transaction journal.

И Redis, и Tarantool содержат оба механизма персистентности.

Redis

Redis периодически сбрасывает все данные из памяти на диск. Происходит это по-умолчанию каждые 60 секунд (настраивается). Redis использует механизм ОС fork для «копирования» текущих данных в памяти, затем информация сохраняется на диск. Если происходит аварийное завершение, то Redis восстановит состояние из последнего сохранения. Если последний снапшот был сделан давно, то данные, пришедшие после снапшота, будут утеряны.

Журнал операций используется для сохранения всей приходящей в базу информации. Каждая операция сохраняется в журнал на диске. Так, при запуске Redis восстанавливает своё состояние из снапшота и затем донакатывает оставшиеся транзакции из журнала.

  • Снапшот в Redis называется RDB (redis database).
  • Журнал операций в Redis называется AOF (append only file).

Tarantool
  • Механизм персистентности взят из архитектур баз данных.
  • Он является целостным — снапшоты и журналирование.
  • Этот же механизм позволяет существовать надежной WAL-based репликации.

Tarantool периодически сохраняет текущие in-memory данные на диск и записывает каждую операцию в журнал.
  • Снапшот в Tarantool называется snap (snapshot). Можно делать с произвольной частотой.
  • Журнал транзакций в Tarantool называется WAL (write ahead log).

И в Redis. и в Tarantool каждый из механизмов может быть выключен. Для надёжного хранения данных оба механизма надо включить. Для максимального быстродействия можно отключить снапшотинг и журналирование, заплатив персистентностью. Слабоумие и отвага!

Различия

Для снапшотинга в Redis используется механизм ОС fork. Tarantool использует внутренний readview всех данных, это работает быстрее чем fork.

В Redis по умолчанию включён только снапшотинг. В Tarantool включён снапшотинг и журнал.

Redis хранит и использует только по одному файлу для снапшотов и журнала операций. Tarantool по-умолчанию хранит два файла снапшотов (можно настроить и больше) и консистентно дополняющее неограниченное количество журналов операций. При повреждении снапшот-файла Tarantool сможет загрузиться из предыдущего. Для Redis необходимо наладить механизм бекапов.

В Tarantool, в отличие от Redis, снапшоты и журналы образуют единый механизм отображения данных в файловой системе. Это значит, что в Tarantool и в файлах снапшотов и в журналах хранится полная метаинформация о транзакции, кто её сделал и когда. Она одного формата и взаимодополняющая.

Troubleshooting

Если повреждён файл журнала в Redis:

redis-check-aof --fix

Если повреждён файл журнала в Tarantool:
tarantool> box.cfg{force_recovery=true}

Язык программирования для хранимых процедур


Хранимые процедуры — это код, выполняющийся рядом с данными. И Redis, и Tarantool предлагают Lua для создания хранимок. С точки зрения пользователя это очень простой язык. Он создавался для людей, для которых программирование будет инструментом решения задач в предметной области.

C точки зрения разработчика базы данных:

  • Lua — это язык, который легко встраивается в существующее приложение.
  • Он просто интегрируется с объектами и процессами приложения.
  • Lua имеет динамическую типизацию и автоматическое управление памятью.
  • Язык имеет сборщик мусора incremental Mark&Sweep.

Различия

Реализация

  • В Redis используется ванильная реализация PUC-Rio.
  • В Tarantool используется LuaJIT.

Таймаут задач
  • В Redis можно задать таймаут, после которого выполнение хранимой процедуры прервётся.
  • В Tarantool хранимые процедуры компилируются и выполняются быстрее, но в этом механизме нет возможности выставить таймаут. Для прерывания хранимой процедуры пользователь должен предусмотреть механизм проверки флага прерывания.

Runtime
  • В Redis используется однозадачность: задачи выполняются по одной и целиком.
  • В Tarantool используется кооперативная многозадачность. Задачи выполняются по одной, но при этом задача отдаёт управление на операциях ввода-вывода или явно с помощью yield.

Вывод
  • В Redis Lua — это просто хранимые процедуры.
  • В Tarantool — это кооперативный runtime, в котором можно взаимодействовать со внешними системами.

Репликация


Репликация — это механизм копирования объектов с одного узла на другой. Бывает асинхронная и синхронная.
  • Асинхронная репликация: при вставке объекта на один узел мы не дожидаемся, когда этот же объект будет отреплицирован на второй узел.
  • Синхронная репликация: при вставке объекта мы дожидаемся, когда он будет сохранён на первом и втором узлах.

И Redis, и Tarantool поддерживают асинхронную репликацию. Только Tarantool умеет в синхронную репликацию.

На практике бывают ситуации, когда мы хотим дождаться репликации объекта. И в Redis, и в Tarantool есть способы для этого:

  • В Redis это команда wait. Она принимает два параметра:
  • В Tarantool это можно сделать фрагментом кода:

псевдокод:
local netbox = require('net.box')
local replica = netbox.connect(...)
local replica_vclock, err = replica.eval([[
    return box.info().vclock
]])

while not vclock_compare(box.info().vclock, replica_vclock) do
    fiber.sleep(0.1)
end

Синхронная репликация

В Redis нет синхронной репликации. Начиная с Tarantool 2.6 синхронная репликация доступна [2].

Коннекторы из других языков программирования


И Redis, и Tarantool поддерживают коннекторы для популярных языков программирования:
  • Go;
  • Python;
  • NodeJS;
  • Java.

Полные списки:

Под какие задачи плохо подходят


И Redis, и Tarantool плохо подходят для решения OLAP-задач. Online analytical processing имеет дело с историческими или архивными данными. OLAP характеризуется относительно низким объёмом транзакций. Запросы часто очень сложны и включают агрегацию.

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

Redis и Tarantool — однопоточные базы данных, что не позволяет распараллелить аналитические запросы.

Экосистема


Redis

Модули Redis представлены в трёх категориях:

  • Enterprise;
  • проверенные и сертифицированные для Enterprise и Open source;
  • непроверенные.

Enterprise-модули:
  • полнотекстовый поиск;
  • хранение и поиск по bloom-фильтрам;
  • хранение временных рядов.

Сертифицированные:
  • хранение графов и запросы к ним;
  • хранение JSON и запросы к нему;
  • хранение и работа с моделями машинного обучения.

Все модули, отсортированные по количеству звёзд на Github: https://redis.io/modules

Tarantool

Модули представлены в трёх категориях:


Чем Redis лучше


  • Проще.
  • В интернете представлено больше информации, 20 тыс. вопросов на Stackoverflow (из них 7 тыс. без ответов).
  • Ниже порог входа.
  • Как следствие, проще найти людей, которые умеют работать с Redis.

Чем Tarantool лучше


  • Русскоязычная бесплатная поддержка в Telegram от разработчиков.
  • Есть вторичные индексы.
  • Есть итерация по индексу.
  • Есть UI для администрирования кластера.
  • Предлагает механику сервера приложений с кооперативной многозадачностью. Эта механика похожа на однопоточный Go.

4. Вывод


Redis — это классный продвинутый кеш, но его нельзя брать как основное хранилище. Tarantool — это мультипарадигменная база данных, можно брать как основное хранилище. Tarantool поддерживает:
  • Реляционную модель хранения с SQL.
  • Распределённое NoSQL-хранилище.
  • Создание продвинутых кешей.
  • Создание брокера очередей.

У Redis ниже порог входа. У Tarantool выше потолок в production.

Сравнение одной таблицей:


5. Ссылки


  1. Архитектура S3: три года эволюции Mail.ru Cloud Storage
  2. Синхронная репликация в Tarantool
  3. Скачать Tarantool можно на официальном сайте, а получить помощь — в Telegram-чате.

© Habrahabr.ru