Привет! Меня зовут Денис, я бэкенд-тимлид в 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 не умеет работать с Тарантулом, поэтому мы использовали следующее расширение. Для подключения необходимо пересобрать исполняемый файл К6 с использованием исходного кода нужного дополнения.
За время тестирования мы рассмотрели 7 сценариев:
Сценарии 1-5 выполнялись на виртуальной машине со следующими характеристиками: Ubuntu, 4 cpu, 16 gb RAM, 30 gb ssd.
Сценарий 6 выполнялся на двух виртуальных машинах Ubuntu, 4 cpu, 16 gb RAM, 30 gb ssd.
Все сценарии выполнялись с профилем нагрузки в 3500 виртуальных пользователей, длительностью 120 секунд.
Сценарий 1. Простой Key-Value
Берем 3 операции: установка значения по ключу, чтение и удаление. Создаем 3500 пользователей, которые начинают слать запросы. Ключом является id пользователя + номер его запроса, значение сохраняем равное ключу.
Используем SET/GET/DEL команды в редисе и аналоги в Tarantool.
Redis:
Дефолтная конфигурация
Tarantool:
Дефолтная конфигурация
Спейс с полями (key string, value string)
???? Redis
Код сценариев k6
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: 3500,
duration: '120s',
},
test: {
executor: 'constant-vus',
exec: 'get_keys',
vus: 3500,
duration: '120s',
},
test: {
executor: 'constant-vus',
exec: 'del_keys',
vus: 3500,
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 запускались последовательно.
Результаты выполнения сценариев
Set_keys
Get_keys
Del_keys
Среднее RPS
Set_keys |
21830 |
Get_keys |
26458 |
Del_keys |
27314 |
Время выполнения запросов
Действие |
avg |
min |
med |
max |
p(90) |
p(95) |
Set_keys |
148.73ms |
71.35µs |
127.72ms |
844.38ms |
313.32ms |
365.29ms |
Get_keys |
126.83ms |
55.91µs |
105ms |
1.09s |
264.7ms |
336.36ms |
Del_keys |
119.89ms |
59.4µs |
102.82ms |
768.7ms |
242.29ms |
313.14ms |
???? 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"}})
Код сценариев k6
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: 3500,
duration: '120s',
},
get: {
executor: 'constant-vus',
exec: 'get',
vus: 3500,
duration: '120s',
},
del: {
executor: 'constant-vus',
exec: 'del',
vus: 3500,
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 запускались последовательно.
Результаты выполнения сценариев
Set_keys
Get_keys
Del_keys
Среднее RPS
Set_keys |
22351 |
Get_keys |
28123 |
Del_keys |
25280 |
Время выполнения запросов
Действие |
avg |
min |
med |
max |
p(90) |
p(95) |
Set_keys |
156.18ms |
19.93ms |
136.63ms |
1.31s |
239.44ms |
289.31ms |
Get_keys |
124.14ms |
1.16ms |
101.6ms |
1.84s |
191.48ms |
255.43ms |
Del_keys |
138.01ms |
9ms |
121.4ms |
1.22s |
212.33ms |
247.8ms |
Сравнительная таблица по RPS
Действие |
Redis |
Tarantool |
Set_keys |
21830 |
22351 |
Get_keys |
26458 |
28123 |
Del_keys |
27314 |
25280 |
Сравнительная таблица по медиане и перцентилям времени выполнения запросов
Действие |
Redis |
Tarantool |
||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
Set_keys |
127.72ms |
313.32ms |
365.29ms |
136.63ms |
239.44ms |
289.31ms |
Get_keys |
105ms |
264.7ms |
336.36ms |
101.6ms |
191.48ms |
255.43ms |
Del_keys |
102.82ms |
242.29ms |
313.14ms |
121.4ms |
212.33ms |
247.8ms |
Вывод по сценарию 1
Tarantool чуть быстрее в операциях записи и чтения, а Redis — в удалении.
Сценарий 2. Счетчик
Создаем 100 пользователей, которые отправляют запросы на увеличение или уменьшение счетчика. Для каждого пользователя используется свой счетчик.
Используем INCR/DECR в Redis и аналогичные update операции в Tarantool.
Сценарии incr и decr выполняются параллельно.
Создаем спейс с полями (key string, value integer).
Конфигурация
Redis:
Дефолтная конфигурация
Tarantool:
Дефолтная конфигурация
???? Redis
Код сценариев к6
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: 1750,
duration: "120s",
},
test_decr: {
executor: "constant-vus",
exec: "decr",
vus: 1750,
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);
}
INCR/DECR
Среднее RPS
INCR/DECR |
29423 |
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
INCR/DECR |
111.28ms |
50.58µs |
98.95ms |
808.4ms |
192.29ms |
263.09ms |
???? Tarantool
Скрипт инициализации 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"}})
Код сценариев к6
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: 3500,
duration: '120s',
},
decr: {
executor: 'constant-vus',
exec: 'decr',
vus: 3500,
duration: '120s',
},
},
};
export function setup() {
for (let i = 1; i < 1751; 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]]])
}
INCR/DECR
Средний RPS
INCR/DECR |
28713 |
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
INCR/DECR |
121.59ms |
3.19ms |
103.85ms |
808.4ms |
192.29ms |
263.09ms |
Сравнительная таблица по RPS
Redis |
Tarantool |
|
INCR/DECR |
29423 |
28713 |
Сравнительная таблица по медиане и перцентилям времени выполнения запросов
Redis |
Tarantool |
|||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
INCR/DECR |
98.95ms |
192.29ms |
263.09ms |
103.85ms |
192.29ms |
263.09ms |
Вывод по сценарию 2
Результаты по rps практически одинаковые.
Сценарий 3. Работа с множествами
Добавляем и получаем значения из множества, используя команды SADD/SMEMBERS в Redis и реализовываем аналогичные возможности в Tarantool.
Создаем спейс kv с полями (key string, element string)/
Конфигурация
Redis:
Дефолтная конфигурация
Tarantool:
Дефолтная конфигурация
???? Redis
Код сценариев к6
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: 3500,
duration: '120s',
},
test_mem: {
executor: 'constant-vus',
exec: 'members',
vus: 3500,
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);
}
SADD
SMEMBERS
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
140.92ms |
7.55ms |
123.55ms |
1.44s |
214.78ms |
258.83ms |
Get_keys |
587.81ms |
86.55ms |
601.19ms |
2.35s |
721.35ms |
814.09ms |
Среднее RPS
Set_keys |
24743 |
Get_keys |
5928 |
???? 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"}})
Сценарий к6
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: 3500,
duration: '120s',
},
members: {
executor: 'constant-vus',
exec: 'members',
vus: 3500,
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()])
}
ADD
MEMBERS
Среднее RPS
Set_keys |
22254 |
Get_keys |
1418 |
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
156.45ms |
364.5µs |
136.36ms |
1.44s |
238.59ms |
290.24ms |
Get_keys |
2.44s |
55.61ms |
526.74ms |
2m2s |
774.05ms |
1.26s |
Сравнительная таблица по RPS
Redis |
Tarantool |
|
Set_keys |
24743 |
22254 |
Get_keys |
5928 |
1418 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis |
Tarantool |
|||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
Set_keys |
123.55ms |
214.78ms |
258.83ms |
136.36ms |
238.59ms |
290.24ms |
Get_keys |
601.19ms |
721.35ms |
814.09ms |
526.74ms |
774.05ms |
1.26s |
Вывод по сценарию 3
При работе со множествами Redis быстрее.
Сценарий 4. Работа с диском
Тестируем Сценарий 1, меняя конфигурацию БД. Цель теста - определить производительность работы с диском Redis vs Tarantool.
Конфигурация
Redis:
Для тестов меняем параметр appendfsync (no, everysecond, always)
Tarantool:
Для тестов меняем параметр wal_mode (none, write, fsync)
???? Redis appendfsync: everysec
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
148.73ms |
71.35µs |
127.72ms |
844.38ms |
313.32ms |
365.29ms |
Get_keys |
126.83ms |
55.91µs |
105ms |
1.09s |
264.7ms |
336.36ms |
Del_keys |
119.89ms |
59.4µs |
102.82ms |
768.7ms |
242.29ms |
313.14ms |
Среднее RPS
Set_keys |
21830 |
Get_keys |
26458 |
Del_keys |
27314 |
???? Redis appendfsync: Always
Set_keys
Get_keys
Delete_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
145.41ms |
75.94µs |
134.53ms |
1.35s |
220.65ms |
352.02ms |
Get_keys |
119.74ms |
62.79µs |
109.49ms |
806.55ms |
191.48ms |
283.33ms |
Del_keys |
121.68ms |
60.95µs |
112.98ms |
932.65ms |
219.2ms |
290.38ms |
Среднее RPS
Set_keys |
20693 |
Get_keys |
26583 |
Del_keys |
26034 |
???? Redis appendfsync: No
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
140.3ms |
78.16µs |
130.29ms |
1.02s |
283.05ms |
336.08ms |
Get_keys |
119.2ms |
63.19µs |
110.11ms |
818.5ms |
215.18ms |
276.44ms |
Del_keys |
118.68ms |
57.85µs |
109.41ms |
766.87ms |
220.51ms |
287.82ms |
Среднее RPS
Set_keys |
21576 |
Get_keys |
26634 |
Del_keys |
26788 |
???? Tarantool wal_mode Write
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
156.18ms |
19.93ms |
136.63ms |
1.31s |
239.44ms |
289.31ms |
Get_keys |
124.14ms |
1.16ms |
101.6ms |
1.84s |
191.48ms |
255.43ms |
Del_keys |
138.01ms |
9ms |
121.4ms |
1.22s |
212.33ms |
247.8ms |
Среднее RPS
Set_keys |
22351 |
Get_keys |
28123 |
Del_keys |
25280 |
???? Tarantool wal_mode: None
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
158.31ms |
6.15ms |
138.09ms |
1.32s |
239.88ms |
294.43ms |
Get_keys |
131.58ms |
199.29µs |
112.12ms |
1.37s |
202.19ms |
256.68ms |
Del_keys |
138.14ms |
6.81ms |
120.21ms |
1.57s |
209.11ms |
261.41ms |
Среднее RPS
Set_keys |
22018 |
Get_keys |
26477 |
Del_keys |
25294 |
???? Tarantool wal_mode: Fsync
Set_keys
Get_keys
Delete_keys
Среднее RPS
Set_keys |
22623 |
Get_keys |
29161 |
Del_keys |
25784 |
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
154.01ms |
284.62µs |
132.71ms |
1.27s |
242.54ms |
288.34ms |
Get_keys |
119.71ms |
3.37ms |
104.91ms |
1.14s |
175.43ms |
216.7ms |
Del_keys |
135.41ms |
7.11ms |
118.54ms |
1.75s |
207.48ms |
250.27ms |
Сравнительная таблица по RPS
Redis |
Tarantool |
|
appendfsync: everysec |
wal_mode Write |
|
Set_keys |
21830 |
22351 |
Get_keys |
26458 |
28123 |
Del_keys |
27314 |
25280 |
appendfsync: no |
wal_mode: None |
|
Set_keys |
21576 |
22018 |
Get_keys |
26634 |
26477 |
Del_keys |
26788 |
25294 |
appendfsync: Always |
wal_mode: Fsync |
|
Set_keys |
20693 |
22623 |
Get_keys |
26583 |
29161 |
Del_keys |
26034 |
25784 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis |
Tarantool |
|||||
appendfsync: everysec |
wal_mode: write |
|||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
Set_keys |
127.72ms |
313.32ms |
365.29ms |
136.63ms |
239.44ms |
289.31ms |
Get_keys |
105ms |
264.7ms |
336.36ms |
101.6ms |
191.48ms |
255.43ms |
Del_keys |
102.82ms |
242.29ms |
313.14ms |
121.4ms |
212.33ms |
247.8ms |
appendfsync: no |
wal_mode: None |
|||||
Set_keys |
130.29ms |
283.05ms |
336.08ms |
138.09ms |
239.88ms |
294.43ms |
Get_keys |
110.11ms |
215.18ms |
276.44ms |
112.12ms |
202.19ms |
256.68ms |
Del_keys |
109.41ms |
220.51ms |
287.82ms |
120.21ms |
209.11ms |
261.41ms |
appendfsync: always |
wal_mode: fsync |
|||||
Set_keys |
134.53ms |
220.65ms |
352.02ms |
132.71ms |
242.54ms |
288.34ms |
Get_keys |
109.49ms |
191.48ms |
283.33ms |
104.91ms |
175.43ms |
216.7ms |
Del_keys |
112.98ms |
219.2ms |
290.38ms |
118.54ms |
207.48ms |
250.27ms |
Вывод по сценарию
При логировании каждой записи (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"}})
Set_keys
Get_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
141.22ms |
8.2ms |
121.24ms |
1.19s |
224.38ms |
272.85ms |
Get_keys |
121.01ms |
13.76ms |
102.4ms |
1.59s |
185.15ms |
230.09ms |
Среднее RPS
Set_keys |
24728 |
Get_keys |
28835 |
???? Redis
Set_keys
Get_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
165.75ms |
100.78µs |
136.9ms |
1.04s |
314.04ms |
384.62ms |
Get_keys |
141.72ms |
67.04µs |
117.33ms |
1.04s |
248.71ms |
316.6ms |
Среднее RPS
Set_keys |
20639 |
Get_keys |
24292 |
Сравнительная таблица по RPS
Redis |
Tarantool |
|
Set_keys |
20639 |
24728 |
Get_keys |
24292 |
28835 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis |
Tarantool |
|||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
Set_keys |
136.9ms |
314.04ms |
384.62ms |
121.24ms |
224.38ms |
272.85ms |
Get_keys |
117.33ms |
248.71ms |
316.6ms |
102.4ms |
185.15ms |
230.09ms |
Вывод по сценарию 5
tarantool производительнее на несколько тысяч rps при работе со вторичными индексами.
Сценарий 6. Влияние репликации на производительность
Тестируем Сценарий 1, меняя конфигурацию репликации. Цель теста - определить влияние репликации на производительность БД.
Проводим 3 теста:
Сценарий 6.1 Redis с репликацией на 1 узел
Сценарий 6.2 Tarantool с master-slave репликацией на 1 узел
Сценарий 6.3 Tarantool с master-master репликацией на 1 узел
???? Сценарий 6.1 Redis master-slave
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
171.27ms |
86.5µs |
149.96ms |
900.37ms |
334.73ms |
382.17ms |
Get_keys |
121.6ms |
62.71µs |
110.02ms |
808.55ms |
223.22ms |
287.22ms |
Del_keys |
123.28ms |
59.67µs |
113.72ms |
906.09ms |
224.49ms |
290.85ms |
Среднее RPS
Set_keys |
19467 |
Get_keys |
26454 |
Del_keys |
25994 |
???? 6.2 Tnt-master-slave
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
166.18ms |
7.2ms |
147.92ms |
1.38s |
255.37ms |
300.44ms |
Get_keys |
121.53ms |
366.24µs |
107.22ms |
1.14s |
178.59ms |
223.19ms |
Del_keys |
151.34ms |
6.61ms |
133.7ms |
1.39s |
234.44ms |
274.56ms |
Среднее RPS
Set_keys |
21004 |
Get_keys |
28718 |
Del_keys |
23022 |
???? 6.3 tnt master-master
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
157.8ms |
15.68ms |
139.34ms |
1.32s |
241.63ms |
292.3ms |
Get_keys |
120.57ms |
2.85ms |
105.83ms |
1.17s |
177.39ms |
216.29ms |
Del_keys |
153.85ms |
5.93ms |
135.32ms |
1.34s |
237.6ms |
283.45ms |
Среднее RPS
Set_keys |
22131 |
Get_keys |
28951 |
Del_keys |
22695 |
Сравнительная таблица по RPS
Redis |
Tarantool |
|
Репликация на 1 узел |
Репликация на 1 узел (master-slave) |
|
Set_keys |
19467 |
21004 |
Get_keys |
26454 |
28718 |
Del_keys |
25994 |
23022 |
Репликация на 1 узел |
Репликация на 1 узел (master-master) |
|
Set_keys |
19467 |
22131 |
Get_keys |
26454 |
28951 |
Del_keys |
25994 |
22695 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis |
Tarantool |
|||||
Репликация на 1 узел |
Репликация на 1 узел (master-slave) |
|||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
Set_keys |
149.96ms |
334.73ms |
382.17ms |
147.92ms |
255.37ms |
300.44ms |
Get_keys |
110.02ms |
223.22ms |
287.22ms |
107.22ms |
178.59ms |
223.19ms |
Del_keys |
113.72ms |
224.49ms |
290.85ms |
133.7ms |
234.44ms |
274.56ms |
Репликация на 1 узел |
Репликация на 1 узел (master-master) |
|||||
Set_keys |
149.96ms |
334.73ms |
382.17ms |
139.34ms |
241.63ms |
292.3ms |
Get_keys |
110.02ms |
223.22ms |
287.22ms |
105.83ms |
177.39ms |
216.29ms |
Del_keys |
113.72ms |
224.49ms |
290.85ms |
135.32ms |
237.6ms |
283.45ms |
Вывод по сценарию 6
При репликациях master-slave и master-master tarantool быстрее в get/set сценариях, чем redis в режиме master-slave.
Сценарий 7. Кластер
В данном сценарии были развернуты кластеры Redis и Tarantool в следующей конфигурации: 3 шарда, в каждом шарде 1 мастер и 2 реплики.
Характеристики виртуальных машин: Ubuntu, 4 cpu, 8 gb RAM, 10 gb ssd. Запускались тесты из сценария 1.
???? Кластер Redis
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
175.32ms |
348.59µs |
138.85ms |
4.83s |
315.68ms |
384.42ms |
Get_keys |
146.07ms |
326.17µs |
118.21ms |
3.59s |
258.22ms |
348.84ms |
Del_keys |
139.25ms |
301.99µs |
112.69ms |
9.87s |
250.63ms |
343.67ms |
Среднее RPS
Set_keys |
19800 |
Get_keys |
23782 |
Del_keys |
24939 |
???? Кластер 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.
Set_keys
Get_keys
Del_keys
Время выполнения запросов
avg |
min |
med |
max |
p(90) |
p(95) |
|
Set_keys |
178.64ms |
11.28ms |
100.91ms |
3.37s |
324.6ms |
531.57ms |
Get_keys |
149.67ms |
494.49µs |
149.48ms |
1.97s |
232.28ms |
278.28ms |
Del_keys |
144.55ms |
578.51µs |
147.29ms |
1.56s |
226.8ms |
271.11ms |
Среднее RPS
Set_keys |
19534 |
Get_keys |
23300 |
Del_keys |
24138 |
Сравнительная таблица по RPS
Redis |
Tarantool |
|
Set_keys |
19800 |
19534 |
Get_keys |
23782 |
23300 |
Del_keys |
24939 |
24138 |
Сравнительная таблица по медиане и перцентилям iterations_duration
Redis |
Tarantool |
|||||
med |
p(90) |
p(95) |
med |
p(90) |
p(95) |
|
Set_keys |
138.85ms |
315.68ms |
384.42ms |
100.91ms |
324.6ms |
531.57ms |
Get_keys |
118.21ms |
258.22ms |
348.84ms |
149.48ms |
232.28ms |
278.28ms |
Del_keys |
112.69ms |
250.63ms |
343.67ms |
147.29ms |
226.8ms |
271.11ms |
Вывод по сценарию 7
при запуске в кластере Tarantool проигрывает Redis при операциях записи, производительности при получении и удалении данных почти не отличается.
Общие выводы из сравнительного нагрузочного тестирования
Redis
Имеет важные преимущества. Он популярнее и как следствие, имеет большее комьюнити. В интернете представлено больше информации по вопросам использования Redis, следовательно, у технологии более низкий порог входа.
В основном Redis используется для реализации кеширования, и он хорошо подходит для этой роли: например, в первом сценарии он лишь незначительно уступил Tarantool.
Tarantool
Если требуется полноценное решение для хранения данных и взаимодействия с ними, которое можно использовать в качестве основной БД, лучше присмотреться к Tarantool. Он ближе к привычной табличной организации данных, т.к поддерживает реляционную модель и запросы на SQL. При этом он способен выдерживать нагрузку, сравнимую с K-V БД вроде Redis.
Tarantool показал себя лучше под нагрузкой в сценариях 1, 4, 5, 6: у него меньше время ответа, он держит большее количество RPS, в нем из коробки реализованы вторичные индексы.
В результате 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. Это дает возможность выдерживать большую нагрузку при максимальной сохранности данных.
3. Tarantool показывает большее быстродействие в различных режимах репликации по сравнению с Redis
Tarantool выигрывает режимах репликации master-slave и master-master.
Почему это важно: репликация необходима для масштабирования баз данных. Данные с одного сервера постоянно копируются на другие сервера, называемые репликами. Это дает возможность приложениям использовать не один сервер для обработки запросов, а сразу несколько.
Также репликация обеспечивает отказоустойчивость: в случае выхода из строя одного сервера его роль возьмет на себя одна из реплик. В случае репликации master-slave запросы на запись идут только на главный сервер, на slave данные дублируются. При master-master репликации запросы идут на оба сервера.
Совсем кратко
Tarantool во многих сценариях показал лучшее быстродействие по сравнению с Redis. Tarantool можно использовать как в качестве основного хранилища данных, так и для реализации кеширования.
Исходный код тестов для К6 можно посмотреть в репозитории.
VladimirFarshatov
Не работал с Тарантулом, спасибо за хорошую статью. Но возник вопрос: каким будет сравнение Тарантула и MariaDb с движком инмемори, если они оба поддерживают sql формат запросов?