Збільшення швидкодії Redis-у через розділення на домени

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Redis дуже простий і потужний інструмент, але хоч і має велику швидкодію та все ж однопотоковий.

Та якщо для кожного домену виділити окремий Redis то можна збільшити швидкодію, про це і буде дана стаття, про мікробенчмарки.

Передісторія

Майже в кожній компанії (6 із 7), де працював, використовувався Redis, а коли працював в LeBoutique то для кожного домену використовувався свій окремий Redis, перший для кешування HTML сторінок (@content), другий для збереження онлайну користувачів (@online), і третій для збереження лічильників переглядів продуктів (@views).

Порівняння швидкодії

Підготуємо три Redis-а та напишемо бенчмарки з використанням команди increment.

import (
	"github.com/go-redis/redis"
	"github.com/stretchr/testify/require"
	"sync/atomic"
	"testing"
)

var (
	keys = []string{
		"1",
		"2",
		"3",
		"4",
		"5",
	}
	keyCount = uint32(len(keys))
)

// https://github.com/go-redis/redis#quickstart
func Client(addr string) (*redis.Client, error) {
	client := redis.NewClient(&redis.Options{
		Addr:     addr, // "localhost:6379",
		Password: "",   // no password set
		DB:       0,    // use default DB
	})

	err := client.Ping().Err()
	if err != nil {
		return nil, err
	}

	return client, err
}

func BenchmarkRedis1Increment(b *testing.B) {
	const (
		addr1 = "redis1:6379"
	)

	benchmarkIncrement(b, addr1, addr1, addr1)
}

func BenchmarkRedisAllIncrement(b *testing.B) {
	const (
		addr1 = "redis1:6379"
		addr2 = "redis2:6379"
		addr3 = "redis3:6379"
	)

	benchmarkIncrement(b, addr1, addr2, addr3)
}

func benchmarkIncrement(b *testing.B, addrs ...string) {
	b.Helper()

	var (
		clientCount = uint32(len(addrs))
		clients     = make([]*redis.Client, clientCount)
	)

	for i, addr := range addrs {
		var client, err = Client(addr)
		require.NoError(b, err)
		defer client.Close()

		var flushAllErr = client.FlushAll().Err()
		require.NoError(b, flushAllErr)

		clients[i] = client
	}

	var counter = uint32(0)

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			var index = atomic.AddUint32(&counter, 1)

			var client = clients[index%clientCount]

			var err = client.Incr(keys[index%keyCount]).Err()

			require.NoError(b, err)
		}
	})
}

В BenchmarkRedis1Increment використовується тільки 1 Redis, а в BenchmarkRedisAllIncrement вже 3 різні Redis-и.
Запустимо тести і порівнюємо швидкодію.

go test ./... -v -bench=. -benchmem -count=10 > bench.txt
benchstat bench.txt
name               time/op
Redis1Increment    8.36µs ± 2%
RedisAllIncrement  5.54µs ± 2%

На такому простому прикладі швидкодія збільшилась на 33%.

Багатопотокова альтернатива KeyDB

KeyDB is a high performance fork of Redis with a focus on multithreading.

Заради цікавості дописав тести для KeyDB, ось повна картина:

name                        time/op
Redis1Increment          9.30µs ± 1%
RedisAllIncrement        6.68µs ± 8%

Keydb1Increment          10.8µs ± 1%
KeydbAllIncrement        8.05µs ± 1%

Dragonflydb1Increment    12.3µs ± 3%
DragonflydbAllIncrement  14.6µs ± 3%

Ще одна альтернатива Dragonfly

Dragonfly started as an experiment to see how an in-memory datastore could look like if it was designed in 2022

Після новини на DOU додав тести для Dragonfly в репозиторій.

Висновки

Звісно на інших операціях з Redis збільшення швидкодії буде відрізнятись, але воно буде якщо розділяти на домени.

Мікробенчмарк доступний в репозиторії, можете використовувати як заготовку для ваших досліджень.

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

В статті це не вказано, але, як я розумію, redis процеси запущені на тій самій машині, що і бенчмарк. Цікаво, чи це відповідає вашому продакшн середовищу. Якщо ні, то мережеві виклики можуть суттєво зменшити відносний виграш по latency.

В основному старались щоб Redis знаходився на одній стійці з сервісом який його використовує, в нас в продакшені є N серверів де на кожному мікросервіс з власним Redis-ом.
А в одному з попередніх проектів був моноліт і 3 Redis-а, кожен для свого домену і все на одному потужному сервері

Погоджуюсь що мережеві виклики значно зменшать відносний виграш для простих операцій, але для важких операцій розділення на домени буде актуальним

В будь-якому разі навіть прочитавши статтю краще протестувати рішення для конкретної задачі ніж вірити

1. Чем это отличается от шардирования?
2. Получится использовать KeyDB для локов?

1. Чем это отличается от шардирования?

Механізм однаковий

2. Получится использовать KeyDB для локов?

Distributed locks with Redis
Distributed locks with KeyDB
Статті однакові або дуже схожі

Підписатись на коментарі