Привет! Меня зовут Денис, я бэкенд-тимлид в KTS.
Tarantool и Redis по большей части — два очень разных продукта. Начиная от заложенной в них функциональности и заканчивая протоколом, репликацией и кластерными решениями.
Тем не менее в них много схожего. И Tarantool, и Redis — «однопоточные» базы данных. Однопоточные взято в кавычки, потому что имеется в виду только транзакционный поток, работающий непосредственно с хранилищем. Конечно, есть и сетевые потоки, и потоки репликации, и потоки работы с диском. Также оба эти продукта — in-memory решения, если не брать в расчёт отдельный дисковый vinyl движок в Tarantool.
В статье мы хотим рассмотреть: что, если взять Tarantool как замену Redis? Просядет ли производительность из-за всех «дополнительных» фичей в Tarantool? Насколько хорошо или плохо справится дисковая подсистема с нагрузкой?
Мы взяли типичные кейсы работы с Redis и реализовали такие же механики на Tarantool, начиная от простых K-V операций и заканчивая вторичными ключами и производительностью кластерных решений: для Tarantool это Tarantool Cartridge, для Redis — Redis Cluster.
Выводы из нашего исследования можно прочесть в конце статьи.
Для тестирования использовали Grafana K6 — инструмент для нагрузочного тестирования, который позволяет создавать и запускать тестовые сценарии на JavaScript и анализировать результаты тестирования. Он имеет широкий набор функций для создания тестовых сценариев и может работать с различными протоколами, такими как HTTP, WebSocket, gRPC и т.д.
Grafana K6 можно использовать как в командной строке, так и в интерфейсе Grafana. Удобной особенностью K6 является возможность подключать сторонние расширения для работы с протоколами, которые изначально не поддерживаются К6. Так, из коробки К6 не умеет работать с Tarantool, поэтому мы использовали следующее расширение. Для подключения необходимо пересобрать исполняемый файл К6 с использованием исходного кода нужного дополнения.
За время тестирования мы рассмотрели 7 сценариев:
Сценарии 1-5 выполнялись на виртуальной машине со следующими характеристиками: Ubuntu, 4CPU, 16GB RAM, 30GB SSD.
Сценарий 6 выполнялся на двух виртуальных машинах Ubuntu, 4CPU 16GB RAM, 30GB SSD.
Все сценарии выполнялись с профилем нагрузки в 100 виртуальных пользователей, длительностью 120 секунд.
Сценарий 1. Простой Key-Value
Берем 3 операции:
установка значения по ключу
чтение
удаление
Создаём 100 пользователей, которые начинают слать запросы. Ключом является id пользователя + номер его запроса, значение сохраняем равное ключу.
Redis:
Дефолтная конфигурация
Tarantool:
Дефолтная конфигурация
Спейс с полями (key string, value string)
? Redis
Скрытый текст
import redis from 'k6/experimental/redis';
import exec from 'k6/execution';
export const options = {
discardResponseBodies: true,
scenarios: {
test: {
executor: 'constant-vus',
exec: 'set_keys',
vus: 100,
duration: '120s',
},
test: {
executor: 'constant-vus',
exec: 'get_keys',
vus: 100,
duration: '120s',
},
test: {
executor: 'constant-vus',
exec: 'del_keys',
vus: 100,
duration: '120s',
},
},
};
const client = new redis.Client({
addrs: new Array('host:port’),
password: '',
db: 0,
});
export function set_keys() {
client.set(exec.vu.iterationInInstance + exec.vu.idInInstance * 100, exec.vu.iterationInInstance + exec.vu.idInInstance * 100);
}
export function get_keys() {
client.get(exec.vu.iterationInInstance + exec.vu.idInInstance * 100);
}
export function del_keys() {
client.del(exec.vu.iterationInInstance + exec.vu.idInInstance * 100);
}
Сценарии set_keys, get_keys, del_keys запускались последовательно.
Результаты выполнения сценариев
Среднее RPS
Set_keys | 25583 |
Get_keys | 31967 |
Del_keys | 31355 |
Время выполнения запросов
Действие | avg | min | med | max | p(90) | p(95) |
Set_keys | 3.78ms | 75.47µs | 2.19ms | 132.3ms | 8.36ms | 12.36ms |
Get_keys | 2.96ms | 52.6µs | 1.76ms | 132.48ms | 6.36ms | 9.37ms |
Del_keys | 3.09ms | 55.94µs | 1.83ms | 115.23ms | 6.62ms | 9.95ms |
? Tarantool
Для проведения сценария создадим таблицу в Тарантуле вида ключ-значение, где ключ – это число, а значение – строка. Для поиска по ключу нам понадобится первичный индекс:
Скрытый текст
box.cfg{listen="127.0.0.1:3301"}
box.schema.space.create("test")
box.space.test:format({{name="name", type="unsigned"}, {name="value", type="string"}})
box.space.test:create_index("primary", {parts={"name"}})
Скрытый текст
import tarantool from "k6/x/tarantool";
import exec from 'k6/execution';
const conn = tarantool.connect(“host:port”);
export const options = {
discardResponseBodies: true,
scenarios: {
set: {
executor: 'constant-vus',
exec: 'set',
vus: 100,
duration: '120s',
},
get: {
executor: 'constant-vus',
exec: 'get',
vus: 100,
duration: '120s',
},
del: {
executor: 'constant-vus',
exec: 'del',
vus: 100,
duration: '120s',
},
},
};
export function set() {
tarantool.replace(conn, "test", [exec.vu.iterationInInstance + exec.vu.idInInstance * 100, (exec.vu.iterationInInstance + exec.vu.idInInstance * 100).toString()])
};
export function get() {
tarantool.call(conn, "box.space.test:select", [exec.vu.iterationInInstance + exec.vu.idInInstance * 100])
};
export function del() {
tarantool.call(conn, "box.space.test:delete", [exec.vu.iterationInInstance + exec.vu.idInInstance * 100])
};
Сценарии set_keys, get_keys, del_keys запускались последовательно.
Результаты выполнения сценариев
Среднее RPS
Set_keys | 26813 |
Get_keys | 33250 |
Del_keys | 30873 |
Время выполнения запросов
Действие | avg | min | med | max | p(90) | p(95) |
Set_keys | 4.14ms | 122.3µs | 3.21ms | 60.34ms | 8.31ms | 10.85ms |
Get_keys | 3.17ms | 89.51µs | 2.58ms | 71.09ms | 5.69ms | 7.85ms |
Del_keys | 3.46ms | 114.76µs | 2.74ms | 61.46ms | 6.38ms | 8.84ms |
Сравнительная таблица по RPS
Redis | Tarantool | |
Set_keys | 25583 | 26813 |
Get_keys | 31967 | 33250 |
Del_keys | 31355 | 30873 |
Сравнительная таблица по медиане и перцентилям времени выполнения запросов
Действие | Redis | Tarantool | ||||
med | p(90) | p(95) | med | p(90) | p(95) | |
Set_keys | 2.19ms | 8.36ms | 12.36ms | 2.94ms | 7.51ms | 9.93ms |
Get_keys | 1.76ms | 6.36ms | 9.37ms | 2.18ms | 4.72ms | 6.7ms |
Del_keys | 1.83ms | 6.62ms | 9.95ms | 2.32ms | 5.29ms | 7.35ms |
Вывод по сценарию 1
Tarantool чуть быстрее в операциях записи и чтения, а Redis — в удалении.
Сценарий 2. Счетчик
Создаем 100 пользователей, которые отправляют запросы на увеличение или уменьшение счетчика. Для каждого пользователя используется свой счетчик.
Используем INCR/DECR в Redis и аналогичные update операции в Tarantool.
Сценарии incr и decr выполняются параллельно.
Конфигурация
Redis:
Дефолтная конфигурация
Tarantool:
Дефолтная конфигурация.
Создаем спейс с полями (key string, value integer).
? Redis
Скрытый текст
import redis from "k6/experimental/redis";
import exec from "k6/execution";
export const options = {
discardResponseBodies: true,
scenarios: {
test_incr: {
executor: "constant-vus",
exec: "incr",
vus: 50,
duration: "120s",
},
test_decr: {
executor: "constant-vus",
exec: "decr",
vus: 50,
duration: "120s",
},
},
};
const client = new redis.Client({
addrs: new Array("host:port"),
password: "",
db: 0,
});
export function incr() {
client.incr("id"+exec.vu.idInInstance);
}
export function decr() {
client.decr("id"+exec.vu.idInInstance);
}
Среднее RPS
INCR/DECR | 38985 |
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
INCR/DECR | 2.53ms | 46.41µs | 1.57ms | 78.78ms | 5.27ms | 7.92ms |
? Tarantool
Скрытый текст
box.cfg{listen="127.0.0.1:3301"}
box.schema.space.create("test")
box.space.test:format({{name="name", type="string"}, {name="value", type="integer"}})
box.space.test:create_index("primary", {parts={"name"}})
Скрытый текст
import tarantool from "k6/x/tarantool";
import exec from 'k6/execution';
const conn = tarantool.connect("host:port");
export const options = {
discardResponseBodies: true,
scenarios: {
incr: {
executor: 'constant-vus',
exec: 'decr',
vus: 50,
duration: '120s',
},
decr: {
executor: 'constant-vus',
exec: 'decr',
vus: 50,
duration: '120s',
},
},
};
export function setup() {
for (let i = 1; i < 51; i++) {
tarantool.replace(conn, "test", [i.toString(), 0]);
}
};
export function incr() {
tarantool.call(conn, "box.space.test:update", [exec.vu.idInInstance.toString(), [["+", 2, 1]]])
}
export function decr() {
tarantool.call(conn, "box.space.test:update", [exec.vu.idInInstance.toString(), [["-", 2, 1]]])
}
Средний RPS
INCR/DECR | 32552 |
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
INCR/DECR | 3.06ms | 100.01µs | 2.51ms | 72.29ms | 5.26ms | 7.05ms |
Сравнительная таблица по RPS
Redis | Tarantool | |
INCR/DECR | 38985 | 32552 |
Сравнительная таблица по медиане и перцентилям времени выполнения запросов
Redis | Tarantool | |||||
med | p(90) | p(95) | med | p(90) | p(95) | |
INCR/DECR | 1.57ms | 5.27ms | 7.92ms | 2.51ms | 5.26ms | 7.05ms |
Вывод по сценарию 2
По результатам теста Tarantool незначительно уступил Redis.
Сценарий 3. Работа с множествами
Добавляем и получаем значения из множества, используя команды SADD/SMEMBERS в Redis и реализовываем аналогичные возможности в Tarantool.
Конфигурация
Redis:
Дефолтная конфигурация
Tarantool:
Дефолтная конфигурация.
Создаем спейс kv с полями (key string, element string)
? Redis
Скрытый текст
import redis from 'k6/experimental/redis';
import exec from 'k6/execution';
export const options = {
discardResponseBodies: true,
scenarios: {
test_add: {
executor: 'constant-vus',
exec: 'add',
vus: 100,
duration: '120s',
},
test_mem: {
executor: 'constant-vus',
exec: 'members',
vus: 100,
duration: '120s',
},
},
};
const client = new redis.Client({
addrs: new Array('host:port'),
password: '',
db: 0,
});
export function add() {
client.sadd('test'+exec.vu.idInInstance*1000, (exec.vu.iterationInInstance + exec.vu.idInInstance * 100).toString());
}
export function members() {
client.smembers('test'+exec.vu.idInInstance*1000);
}
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 4.2ms | 83.29µs | 2.44ms | 142.34ms | 9.5ms | 13.87ms |
Get_keys | 3.33ms | 63.02µs | 2.01ms | 121.31ms | 7.18ms | 10.63ms |
Среднее RPS
Set_keys | 22983 |
Get_keys | 29242 |
? Tarantool
Скрытый текст
box.cfg{listen="127.0.0.1:3301"}
box.schema.space.create("test")
box.schema.sequence.create('S',{min=1, start=1})
box.space.test:format({{name="id", type="unsigned"},{name="name", type="string"}, {name="value", type="string"}})
box.space.test:create_index("primary", {sequence='S', parts={"id"}})
box.space.test:create_index("name", {unique=false, parts={"name"}})
Скрытый текст
import tarantool from "k6/x/tarantool";
import exec from 'k6/execution';
const conn = tarantool.connect(“host:port”);
export const options = {
discardResponseBodies: true,
scenarios: {
add: {
executor: 'constant-vus',
exec: 'add',
vus: 100,
duration: '120s',
},
members: {
executor: 'constant-vus',
exec: 'members',
vus: 100,
duration: '120s',
},
},
};
export function add() {
tarantool.insert(conn, "test", [null, (exec.vu.idInInstance*1000).toString(), (exec.vu.iterationInInstance + exec.vu.idInInstance * 100).toString()])
}
export function members() {
tarantool.call(conn, "box.space.test.index.name:select", [(exec.vu.idInInstance*1000).toString()])
}
Среднее RPS
Set_keys | 22355 |
Get_keys | 29766 |
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 4.45ms | 126.08µs | 3.47ms | 73.07ms | 8.93ms | 11.73ms |
Get_keys | 3.34ms | 104.19µs | 2.71ms | 73.04ms | 5.98ms | 8.33ms |
Сравнительная таблица по RPS
Redis | Tarantool | |
Set_keys | 22983 | 22355 |
Get_keys | 29242 | 29766 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis | Tarantool | |||||
med | p(90) | p(95) | med | p(90) | p(95) | |
Set_keys | 2.44ms | 9.5ms | 13.87ms | 3.47ms | 8.93ms | 11.73ms |
Get_keys | 2.01ms | 7.18ms | 10.63ms | 2.71ms | 5.98ms | 8.33ms |
Вывод по сценарию 3
При работе со множествами Tarantool и Redis показали себя одинаково.
Сценарий 4. Работа с диском
Тестируем Сценарий 1, меняя конфигурацию БД. Цель теста — определить производительность работы с диском Redis vs Tarantool.
Конфигурация
Redis:
Для тестов меняем параметр appendfsync (no, everysecond, always)
Tarantool:
Для тестов меняем параметр wal_mode (none, write, fsync)
? Redis appendfsync: everysec
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.78ms | 75.47µs | 2.19ms | 132.3ms | 8.36ms | 12.36ms |
Get_keys | 2.96ms | 52.6µs | 1.76ms | 132.48ms | 6.36ms | 9.37ms |
Del_keys | 3.09ms | 55.94µs | 1.83ms | 115.23ms | 6.62ms | 9.95ms |
Среднее RPS
Set_keys | 25583 |
Get_keys | 31967 |
Del_keys | 31355 |
? Redis appendfsync: Always
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 4.07ms | 79.09µs | 2.35ms | 135.39ms | 9.17ms | 13.28ms |
Get_keys | 3.22ms | 60.93µs | 1.93ms | 129.49ms | 7.01ms | 10.32ms |
Del_keys | 3.13ms | 57.96µs | 1.88ms | 113.63ms | 6.8ms | 10.02ms |
Среднее RPS
Set_keys | 23773 |
Get_keys | 29467 |
Del_keys | 30741 |
? Redis appendfsync: No
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.94ms | 76.25µs | 2.26ms | 155.91ms | 8.81ms | 12.95ms |
Get_keys | 3.14ms | 63.19µs | 1.74ms | 122.84ms | 6.81ms | 9.98ms |
Del_keys | 2.73ms | 54.07µs | 1.63ms | 110.17ms | 5.8ms | 8.46ms |
Среднее RPS
Set_keys | 24541 |
Get_keys | 31999 |
Del_keys | 35179 |
? Tarantool wal_mode Write
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.78ms | 117.33µs | 2.94ms | 53.07ms | 7.51ms | 9.93ms |
Get_keys | 2.7ms | 86.38µs | 2.18ms | 45.22ms | 4.72ms | 6.7ms |
Del_keys | 2.93ms | 108.13µs | 2.32ms | 70.4ms | 5.29ms | 7.35ms |
Среднее RPS
Set_keys | 26813 |
Get_keys | 33250 |
Del_keys | 30873 |
? Tarantool wal_mode: None
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.82ms | 76.2µs | 2.2ms | 41.77ms | 7.85ms | 10.36ms |
Get_keys | 3.28ms | 95.29µs | 2.66ms | 57.78ms | 5.9ms | 8.23ms |
Del_keys | 2.9ms | 87.79µs | 2.32ms | 56.83ms | 5.17ms | 7.25ms |
Среднее RPS
Set_keys | 26335 |
Get_keys | 33017 |
Del_keys | 34273 |
? Tarantool wal_mode: Fsync
Среднее RPS
Set_keys | 25055 |
Get_keys | 31342 |
Del_keys | 28758 |
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 4.14ms | 122.3µs | 3.21ms | 60.34ms | 8.31ms | 10.85ms |
Get_keys | 3.17ms | 89.51µs | 2.58ms | 71.09ms | 5.69ms | 7.85ms |
Del_keys | 3.46ms | 114.76µs | 2.74ms | 61.46ms | 6.38ms | 8.84ms |
Сравнительная таблица по RPS
Redis | Tarantool | |
appendfsync: everysec | wal_mode Write | |
Set_keys | 25583 | 26813 |
Get_keys | 31967 | 33250 |
Del_keys | 31355 | 30873 |
appendfsync: no | wal_mode: None | |
Set_keys | 24541 | 26335 |
Get_keys | 31999 | 33017 |
Del_keys | 35179 | 34273 |
appendfsync: Always | wal_mode: Fsync | |
Set_keys | 23773 | 25055 |
Get_keys | 29467 | 31342 |
Del_keys | 30741 | 28758 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis | Tarantool | |||||
appendfsync: everysec | wal_mode: write | |||||
med | p(90) | p(95) | med | p(90) | p(95) | |
Set_keys | 2.19ms | 8.36ms | 12.36ms | 2.94ms | 7.51ms | 9.93ms |
Get_keys | 1.76ms | 6.36ms | 9.37ms | 2.18ms | 4.72ms | 6.7ms |
Del_keys | 1.83ms | 6.62ms | 9.95ms | 2.32ms | 5.29ms | 7.35ms |
appendfsync: no | wal_mode: None | |||||
Set_keys | 2.26ms | 8.81ms | 12.95ms | 3.28ms | 7.85ms | 10.36ms |
Get_keys | 1.74ms | 6.81ms | 9.98ms | 2.66ms | 5.9ms | 8.23ms |
Del_keys | 1.63ms | 5.8ms | 8.46ms | 2.32ms | 5.17ms | 7.25ms |
appendfsync: always | wal_mode: fsync | |||||
Set_keys | 2.35ms | 9.17ms | 13.28ms | 3.21ms | 8.31ms | 10.85ms |
Get_keys | 1.93ms | 7.01ms | 10.32ms | 2.58ms | 5.69ms | 7.85ms |
Del_keys | 1.88ms | 6.8ms | 10.02ms | 2.74ms | 6.38ms | 8.84ms |
Вывод по сценарию
При логировании каждой записи (appendfsync always в redis и wal_mode fsync в tarantool) tarantool показал лучший результат.
Сценарий 5. Вторичные индексы
KV в tarantool (id, value) со вторичным индексом на value.
Для добавления вторичных индексов в Redis использовался модуль Redisearch, скомпилированный под Ubuntu и запущенный через loadmodule
? Tarantool
Скрытый текст
box.cfg{listen="127.0.0.1:3301"}
box.schema.space.create("test")
box.space.test:format({{name="id", type="unsigned"}, {name="value", type="string"}})
box.space.test:create_index("primary", {parts={"id"}})
box.space.test:create_index("secondary", {parts={"value"}})
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.43ms | 112.54µs | 2.68ms | 48.15ms | 6.63ms | 8.96ms |
Search | 2.73ms | 82.87µs | 2.21ms | 56.86ms | 4.78ms | 6.7ms |
Среднее RPS
Set_keys | 29031 |
Get_keys | 36382 |
? Redis
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.66ms | 70.83µs | 2.26ms | 89.86ms | 8.27ms | 12.15ms |
Search | 2.94ms | 62.1µs | 1.86ms | 83.79ms | 6.32ms | 9.41ms |
Среднее RPS
Set_keys | 27122 |
Get_keys | 33801 |
Сравнительная таблица по RPS
Redis | Tarantool | |
Set_keys | 27122 | 29031 |
Search | 33801 | 36382 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis | Tarantool | |||||
med | p(90) | p(95) | med | p(90) | p(95) | |
Set_keys | 2.68ms | 6.63ms | 8.96ms | 2.26ms | 8.27ms | 12.15ms |
Get_keys | 2.21ms | 4.78ms | 6.7ms | 1.86ms | 6.32ms | 9.41ms |
Вывод по сценарию 5
Tarantool производительнее на несколько тысяч rps при работе со вторичными индексами и не требует дополнительных модулей для их работы.
Сценарий 6. Влияние репликации на производительность
Тестируем cценарий 1, меняя конфигурацию репликации. Цель теста — определить влияние репликации на производительность БД.
Проводим 2 теста:
Сценарий 6.1 Redis с репликацией на 1 узел
Сценарий 6.2 Tarantool с master-slave репликацией на 1 узел
? Сценарий 6.1 Redis master-slave
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.86ms | 72.13µs | 2.76ms | 78.53ms | 8.12ms | 11.68ms |
Get_keys | 3.08ms | 52.11µs | 1.81ms | 64.77ms | 6.65ms | 9.93ms |
Del_keys | 2.96ms | 50.08µs | 1.74ms | 72.78ms | 6.34ms | 9.39ms |
Среднее RPS
Set_keys | 25766 |
Get_keys | 30689 |
Del_keys | 32378 |
? 6.2 Tnt-master-slave
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 4.35ms | 129.16µs | 3.32ms | 72.58ms | 8.83ms | 11.61ms |
Get_keys | 3.27ms | 84.55µs | 2.66ms | 41ms | 5.95ms | 8.15ms |
Del_keys | 3.45ms | 99.55µs | 2.78ms | 89.36ms | 6.21ms | 8.53ms |
Среднее RPS
Set_keys | 22883 |
Get_keys | 30452 |
Del_keys | 28830 |
Сравнительная таблица по RPS
Redis | Tarantool | |
Репликация на 1 узел | Репликация на 1 узел (master-slave) | |
Set_keys | 25766 | 22883 |
Get_keys | 30689 | 30452 |
Del_keys | 32378 | 28830 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis | Tarantool | |||||
Репликация на 1 узел | Репликация на 1 узел (master-slave) | |||||
med | p(90) | p(95) | med | p(90) | p(95) | |
Set_keys | 2.76ms | 8.12ms | 11.68ms | 3.32ms | 8.83ms | 11.61ms |
Get_keys | 1.81ms | 6.65ms | 9.93ms | 2.2ms | 4.67ms | 6.46ms |
Del_keys | 1.74ms | 6.34ms | 9.39ms | 2.78ms | 6.21ms | 8.53ms |
Вывод по сценарию 6
По результатам теста при репликации master-slave Redis с минимальным перевесом обошёл Tarantool в операциях чтения и удаления.
Сценарий 7. Кластер
В данном сценарии были развернуты кластеры Redis и Tarantool в следующей конфигурации: 3 шарда, в каждом шарде 1 мастер и 2 реплики.
Характеристики виртуальных машин: Ubuntu, 4 cpu, 8 gb RAM, 10 gb ssd. Запускались тесты из сценария 1.
? Кластер Redis
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 3.17ms | 231.06µs | 2.31ms | 220.89ms | 6.5ms | 8.62ms |
Get_keys | 2.73ms | 223.4µs | 2.01ms | 124.6ms | 6.01ms | 7.05ms |
Del_keys | 2.61ms | 214.5µs | 1.91ms | 105.32ms | 5.06ms | 6.89ms |
Среднее RPS
Set_keys | 31390 |
Get_keys | 37245 |
Del_keys | 38158 |
? Кластер Tarantool
Топология кластера
Реализована пользовательская роль, реализующая следующие функции:
Скрытый текст
function del(id)
local result, err = crud.delete('test', id)
if err ~= nil then
return err
end
return result
end
function add(id, value)
local result, err = crud.insert('test', {id, value})
if err ~= nil then
return err
end
return result
end
function get(id)
local result, err = crud.get('test', id)
if err ~= nil then
return err
end
return result
end
Для операций с данными использовался модуль crud.
Время выполнения запросов
avg | min | med | max | p(90) | p(95) | |
Set_keys | 5.13ms | 636.41µs | 4.35ms | 75.47ms | 8.34ms | 11ms |
Get_keys | 4ms | 399.47µs | 3.4ms | 39.65ms | 6.16ms | 8.69ms |
Del_keys | 4.22ms | 548.83µs | 3.65ms | 73.67ms | 6.5ms | 8.73ms |
Среднее RPS
Set_keys | 19436 |
Get_keys | 24888 |
Del_keys | 23627 |
Сравнительная таблица по RPS
Redis | Tarantool | |
Set_keys | 31390 | 19436 |
Get_keys | 37245 | 24888 |
Del_keys | 38158 | 23627 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis | Tarantool | |||||
med | p(90) | p(95) | med | p(90) | p(95) | |
Set_keys | 2.31ms | 6.5ms | 8.62ms | 4.35ms | 8.34ms | 11ms |
Get_keys | 2.01ms | 6.01ms | 7.05ms | 3.4ms | 6.16ms | 8.69ms |
Del_keys | 1.91ms | 5.06ms | 6.89ms | 3.65ms | 6.5ms | 8.73ms |
Вывод по сценарию 7
При запуске в режиме кластера Redis выигрывает.
Общие выводы из сравнительного нагрузочного тестирования
Redis
Имеет важные преимущества. Он популярнее и как следствие, имеет большее комьюнити. В интернете представлено больше информации по вопросам использования Redis, следовательно, у технологии более низкий порог входа.
В основном Redis используется для реализации кеширования, и он хорошо подходит для этой роли: например, в первом сценарии он лишь незначительно уступил Tarantool.
Tarantool
Если требуется полноценное решение для кеширования или хранения данных и взаимодействия с ними, которое можно использовать в качестве основной БД, рекомендуем присмотреться к Tarantool. Он ближе к привычной табличной организации данных, потому что поддерживает реляционную модель и запросы на SQL. При этом в большинстве случаев он способен выдерживать нагрузку, сравнимую с K-V БД вроде Redis.
Tarantool показал себя лучше под нагрузкой в сценариях 1, 4, 5: у него меньше время ответа, он держит большее количество RPS, в нем из коробки реализованы вторичные индексы. В сценарии 3 Tarantool и Redis проявили себя одинаково.
В результате Tarantool:
Немного быстрее работает в режиме key-value и при использовании вторичных индексов
Быстрее работает в режиме полной персистентности
Рассмотрим эти преимущества подробнее.
1. Tarantool немного быстрее работает в режиме key-value и при использовании вторичных индексов
Вторичные индексы часто используются при проектировании структуры базы данных. В случае Tarantool это позволяет производить поиск не только по ключам, но и по значениям.
Например: у нас имеется таблица вида «табельный номер — ФИО сотрудника», где табельный номер является ключом.
В Tarantool при создании вторичного индекса появляется возможность производить поиск не только по табельному номеру, но и по ФИО, что даёт возможность узнать табельный номер конкретного сотрудника
В Redis тоже есть такая возможность, но для этого необходимо устанавливать модуль RediSearch. При этом производительность решения будет уступать Tarantool
2. Tarantool быстрее работает в режиме полной персистентности в in-memory базах
Tarantool показывает большую производительность при сбросе данных на диск в режимах wal_mode=write и wal_mode=fsync — в сравнении с Redis с параметром appendfsync=everysec и appendfsync=always соответственно.
Почему это важно: эти режимы нужны для возможности восстановления данных в случае сбоя. Все операции сохраняются в специальный файл, и в случае неисправности их можно воспроизвести и не потерять данные.
Разница между режимами выше — в частоте записи в файл. В режиме полной персистентности, когда операции записываются в файл сразу после их выполнения с wal_mode=fsync, Tarantool выигрывает у Redis. Это дает возможность выдерживать большую нагрузку при максимальной сохранности данных.
Совсем кратко
Tarantool во многих сценариях показал лучшее или такое же быстродействие по сравнению с Redis. Tarantool можно использовать как в качестве основного хранилища данных, так и для реализации кеширования.
Исходный код тестов для К6 можно посмотреть в репозитории.