Привет! Меня зовут Денис, я бэкенд-тимлид в 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 можно посмотреть в репозитории.

Комментарии (5)


  1. VladimirFarshatov
    26.04.2023 12:51
    +1

    Не работал с Тарантулом, спасибо за хорошую статью. Но возник вопрос: каким будет сравнение Тарантула и MariaDb с движком инмемори, если они оба поддерживают sql формат запросов?