Збереження стану онлайну користувача в Redis
Привіт! У цій статті ми протестуємо різні типи даних (Hash, Set та Sorted set), які доступні в Redis, для задачі збереження онлайну користувачів.
Якщо ви одразу хочете глянути результати, то ось репозиторій.
Передісторія. Перше найпростіше рішення
Років п’ять тому я займався розробкою інтернет-магазину на PHP. З відділу маркетингу прийшла задача покращити рекламні розсилки, враховуючи онлайн-користувачів.
Спершу потрібно було додати збереження останньої активності користувача. Це збереження додав до API, яке повертає поточну корзину, бо поточна корзина потрібна майже на кожній сторінці інтернет-магазину.
Для збереження онлайну додав таблицю user_online:
CREATE TABLE user_online ( user_id INT NOT NULL PRIMARY KEY, online INT NOT NULL, FOREIGN KEY (user_id) REFERENCES users (id) ) ENGINE = InnoDB;
А саме збереження викликалось простою вставкою:
INSERT INTO user_online (user_id, online) VALUES (?, ?) ON DUPLICATE KEY UPDATE online = VALUES(online);
Хоча, зараз би заповнив всю таблицю user_online даними (час останнього замовлення або ж нулем) й потім оновлював через UPDATE:
UPDATE user_online SET online = ? WHERE user_id = ?;
Рішення пройшло code review, протестували на staging, протестували на rc (release candidate), задеплоїли в production, перше найпростіше рішення працює.
Передісторія. Поверніть як було раніше
Вже за п’ятнадцять хвилин Артем (DevOps) піднявся з-за свого стола й зайшов запитати PHP-розробників, чому навантаження на MySQL відчутно зросло, що ми такого задеплоїли. На думку спадала лише задача онлайну користувачів.
Задача повернулась в роботу, а Артем зменшив навантаження на MySQL, переправивши вставки в чорну діру:
CREATE TABLE user_online_blackhole ( user_id INT NOT NULL, online INT NOT NULL ) ENGINE = BLACKHOLE; RENAME TABLE user_online TO user_online_innodb, user_online_blackhole TO user_online;
Принаймні, я це саме так запам’ятав. А стаття могла мати назву «Збереження онлайну користувачів в чорну діру».
Передісторія. Краще рішення
Redis в інтернет-магазині використовується в багатьох місцях, а тому очевидне рішення — використовувати Redis як буфер для збереження онлайну.
Тепер, після виклику API, отримання корзини, активність користувача має зберігатись в Redis, й кожні 10 хвилин має запускатись консольна команда, яка переносить онлайн десятки тисяч користувачів з Redis-а в MySQL та очищати буфер в Redis.
В Redis будуть зберігатись пари ключ-значення, user_id та timestamp відповідно.
Типи даних для збереження онлайну
Отже, потрібно вибрати типи даних для збереження пар user_id та timestamp. Найочевиднішим рішенням є словник, який представлений в Redis типом Hash.
Тип Sorted set також можна використовувати як словник. Тип Set буде мати меншу точність, але достатню для наших потреб.
Методика вибору оптимального типу даних для збереження онлайну
Для кожного типу даних Redis (Hash, Sorted set та Set) я напишу сервіс-обгортку на Go, який буде вміти працювати з цим типом даних, зберігати пару user_id та timestamp в Redis та читати список пар.
Кожен сервіс на Go покрию тестом, а також напишу бенчмарки. Порівняю, скільки пам’яті використовує Redis для збереження онлайну мільйона користувачів, 10 та 25 мільйонів.
У Redis є альтернативи: KeyDB та DragonflyDB, їх також протестую.
Налаштування проєкту
Всі три бази Redis, KeyDB, DragonflyDB, а також тести на Go будуть запускатись в Docker-контейнерах, щоб ви могли перевірити.
Код доступний в репозиторії на GitHub.
go mod init github.com/doutivity/research-online-redis-go
cat docker-compose.yml
version: "3.7" services: app: container_name: "research-online-redis-go-app" image: golang:1.20.0-alpine working_dir: /go/src/github.com/doutivity/research-online-redis-go volumes: - .:/go/src/github.com/doutivity/research-online-redis-go command: "sleep 1d" depends_on: - redis1 - keydb1 - dragonflydb1 redis1: container_name: "research-online-redis-1" image: "redis:latest" keydb1: container_name: "research-online-keydb-1" image: "eqalpha/keydb:latest" dragonflydb1: container_name: "research-online-dragonflydb-1" image: "docker.dragonflydb.io/dragonflydb/dragonfly"
Для роботи з Redis в Go потрібно підключити сторонню бібліотеку, в попередній статті про Redis, я використовував «github.com/go-redis/redis», тепер ця бібліотека стала офіційною:
// old, v8 import "github.com/go-redis/redis/v8" // new, v9 import "github.com/redis/go-redis/v9"
А також підключу бібліотеку, яка спрощує мені тестування.
go get github.com/redis/go-redis/v9
go get github.com/stretchr/testify/require
Для перевірки, що налаштував успішно, напишу й запущу простий тест PING:
package research_online_redis_go import ( "context" "testing" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) func TestRedisPing(t *testing.T) { client := redis.NewClient(&redis.Options{ Addr: "redis1:6379", }) result, err := client.Ping(context.Background()).Result() require.NoError(t, err) require.Equal(t, "PONG", result) } func TestKeydbPing(t *testing.T) { client := redis.NewClient(&redis.Options{ Addr: "keydb1:6379", }) result, err := client.Ping(context.Background()).Result() require.NoError(t, err) require.Equal(t, "PONG", result) } func TestDragonflydbPing(t *testing.T) { client := redis.NewClient(&redis.Options{ Addr: "dragonflydb1:6379", }) result, err := client.Ping(context.Background()).Result() require.NoError(t, err) require.Equal(t, "PONG", result) }
docker-compose up -d
[+] Running 5/5 ⠿ Network research-online-redis-go_default Cre... 0.0s ⠿ Container research-online-dragonflydb-1 Star... 0.9s ⠿ Container research-online-redis-1 Started 1.1s ⠿ Container research-online-keydb-1 Started 1.0s ⠿ Container research-online-redis-go-app Start... 1.5s
docker exec research-online-redis-go-app go test ./... -v -run=Ping -count=1
=== RUN TestRedisPing --- PASS: TestRedisPing (0.00s) === RUN TestKeydbPing --- PASS: TestKeydbPing (0.00s) === RUN TestDragonflydbPing --- PASS: TestDragonflydbPing (0.00s) PASS ok github.com/doutivity/research-online-redis-go 0.004s
Тести пройшли, проєкт налаштований успішно.
Збереження онлайну за допомогою типу даних Hash
Для збереження онлайну я буду використовувати команду HSET:
HSET key field value [field value ...]
Sets the specified fields to their respective values in the hash stored at key.This command overwrites the values of specified fields that exist in the hash. If key doesn’t exist, a new key holding a hash is created.
А для отримання всіх збережених пар буду використовувати команду HGETALL:
HGETALL key
Returns all fields and values of the hash stored at key.
Як воно все працює буде зрозуміло на практиці:
docker exec research-online-redis-1 redis-cli HSET "h:online:main" "user_id:100" "timestamp:1680136500"
1 # The number of fields that were added.
docker exec research-online-redis-1 redis-cli HSET "h:online:main" "user_id:100" "timestamp:1680136500"
0 # The number of fields that were added.
docker exec research-online-redis-1 redis-cli HSET "h:online:main" "user_id:101" "timestamp:1680136501"
1 # The number of fields that were added.
docker exec research-online-redis-1 redis-cli HSET "h:online:main" "user_id:102" "timestamp:1680136502"
1 # The number of fields that were added.
docker exec research-online-redis-1 redis-cli HLEN "h:online:main"
3 # number of fields in the hash, or 0 when key does not exist.
docker exec research-online-redis-1 redis-cli HGETALL "h:online:main"
user_id:100 timestamp:1680136500 user_id:101 timestamp:1680136501 user_id:102 timestamp:1680136502
Префікси user_id та timestamp додав для кращого розуміння, але краще зберігати без них:
docker exec -it research-online-redis-1 redis-cli
DEL "h:online:main"
HSET "h:online:main" "100" "1680136500"
HSET "h:online:main" "101" "1680136501"
HSET "h:online:main" "102" "1680136502"
HGETALL "h:online:main"
100 1680136500 101 1680136501 102 1680136502
Згідно попереднього опису задачі, ми маємо зберігати онлайн в Redis буфер (HSET), переносити дані з Redis-у в MySQL (HGETALL) та очищати буфер.
Ми маємо очищати буфер, щоб уникнути повторного перенесення онлайну, а ще без очищення, hash «h:online:main» може розростись й містити онлайн для мільйонів користувачів й відповідно кожні 10 хвилин буде значне навантаження на MySQL.
Для очищення буферу ми можемо просто видаляти ключ «h:online:main» командою DEL, але у такому випадку ми можемо випадково втратити трохи даних, бо команда HSET викликається на сервері, а команди HGETALL та DEL в команді по крону:
HSET "h:online:main" "100" "1680136500"
HSET "h:online:main" "101" "1680136501"
HGETALL "h:online:main"
HSET "h:online:main" "102" "1680136502" # ми втратимо ці дані
DEL "h:online:main"
Правильним рішення буде використовувати команду RENAME:
RENAME key newkey
RENAME "h:online:main" "h:online:tmp"
HSET "h:online:main" "100" "1680136500"
HSET "h:online:main" "101" "1680136501"
RENAME "h:online:main" "h:online:tmp" # DEL "h:online:tmp" IF EXISTS BEFORE RENAME
HGETALL "h:online:tmp"
HSET "h:online:main" "102" "1680136502"
Обгортка на Go для тестування збереження онлайну за допомогою типу даних Hash
Тепер, коли ми знаємо, як працює тип даних Hash в Redis, то можемо написати обгортку на Go разом з тестами:
package research_online_redis_go type UserOnlinePair struct { UserID int64 Timestamp int64 }
package research_online_redis_go import ( "context" "strconv" "github.com/redis/go-redis/v9" ) type HashOnlineStorage struct { client *redis.Client } func NewHashOnlineStorage(client *redis.Client) *HashOnlineStorage { return &HashOnlineStorage{client: client} } func (s *HashOnlineStorage) Store(ctx context.Context, pair UserOnlinePair) error { return s.client.HSet( ctx, "h:online:main", strconv.FormatInt(pair.UserID, 10), pair.Timestamp, ).Err() } func (s *HashOnlineStorage) Count(ctx context.Context) (int64, error) { return s.client.HLen(ctx, "h:online:main").Result() } func (s *HashOnlineStorage) GetAndClear(ctx context.Context) ([]UserOnlinePair, error) { var ( oldKey = "h:online:main" newKey = "h:online:tmp" ) err := s.client.Rename(ctx, oldKey, newKey).Err() if err != nil { return nil, err } userOnlineMap, err := s.client.HGetAll(ctx, newKey).Result() if err != nil { return nil, err } result := make([]UserOnlinePair, 0, len(userOnlineMap)) for stringUserID, stringTimestamp := range userOnlineMap { userID, err := strconv.ParseInt(stringUserID, 10, 64) if err != nil { // unreachable, ignore for article // logging or use https://github.com/hashicorp/go-multierror // just in case continue } timestamp, err := strconv.ParseInt(stringTimestamp, 10, 64) if err != nil { // unreachable, ignore for article // logging or use https://github.com/hashicorp/go-multierror // just in case continue } result = append(result, UserOnlinePair{ UserID: userID, Timestamp: timestamp, }) } return result, nil }
Тести будуть враховувати Redis, KeyDB та DragonflyDB:
package research_online_redis_go import ( "context" "testing" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) func TestRedisHashOnlineStorage(t *testing.T) { testHashOnlineStorage(t, "redis1:6379") } func TestKeydbHashOnlineStorage(t *testing.T) { testHashOnlineStorage(t, "keydb1:6379") } func TestDragonflydbHashOnlineStorage(t *testing.T) { testHashOnlineStorage(t, "dragonflydb1:6379") } func testHashOnlineStorage(t *testing.T, addr string) { t.Helper() ctx := context.Background() client, err := Client(ctx, addr) require.NoError(t, err) require.NoError(t, client.FlushAll(ctx).Err()) storage := NewHashOnlineStorage(client) expected := []UserOnlinePair{ { UserID: 10000001, Timestamp: 1679800725, }, { UserID: 10000002, Timestamp: 1679800730, }, { UserID: 10000003, Timestamp: 1679800735, }, } for _, pair := range expected { err := storage.Store(ctx, pair) require.NoError(t, err) } actualCount, err := storage.Count(ctx) require.NoError(t, err) require.Equal(t, int64(len(expected)), int64(actualCount)) actual, err := storage.GetAndClear(ctx) require.NoError(t, err) requireUserOnlinePairsEqual(t, expected, actual) }
В терміналі 1 я запускаю команду MONITOR, щоб бачити команди, які виконуються в Redis:
docker exec research-online-redis-1 redis-cli monitor
OK
Переходжу в термінал 2 та запускаю тести:
docker exec research-online-redis-go-app go test ./... -v -run='Test(Redis|Keydb|Dragonflydb)HashOnlineStorage' -count=1
=== RUN TestRedisHashOnlineStorage --- PASS: TestRedisHashOnlineStorage (0.08s) === RUN TestKeydbHashOnlineStorage --- PASS: TestKeydbHashOnlineStorage (0.01s) === RUN TestDragonflydbHashOnlineStorage --- PASS: TestDragonflydbHashOnlineStorage (0.01s) PASS ok github.com/doutivity/research-online-redis-go 0.098s
Повертаюсь в термінал 1 та бачу команди, виконані в Redis:
1680140476.951838 [0 172.30.0.5:38208] "hello" "3" 1680140476.951908 [0 172.30.0.5:38208] "ping" 1680140476.954159 [0 172.30.0.5:38208] "flushall" 1680140476.954233 [0 172.30.0.5:38208] "hset" "h:online:main" "10000001" "1679800725" 1680140476.954303 [0 172.30.0.5:38208] "hset" "h:online:main" "10000002" "1679800730" 1680140476.954371 [0 172.30.0.5:38208] "hset" "h:online:main" "10000003" "1679800735" 1680140476.954429 [0 172.30.0.5:38208] "hlen" "h:online:main" 1680140476.954482 [0 172.30.0.5:38208] "rename" "h:online:main" "h:online:tmp" 1680140476.954542 [0 172.30.0.5:38208] "hgetall" "h:online:tmp"
Тести пройшли, збереження в Hash працює успішно.
Обгортка на Go для тестування збереження онлайну за допомогою типу даних Sorted set
Ми будемо використовувати команди ZADD та ZRANGE, але вже без такого детального розборку з прикладами, як то було з типом Hash, бо ви все одно побачите приклади виконання Redis-команд в терміналі 1.
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]
ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
Напишемо обгортку на Go для роботи з типом Sorted set:
package research_online_redis_go import ( "context" "strconv" "github.com/redis/go-redis/v9" ) type SortedSetOnlineStorage struct { client *redis.Client } func NewSortedSetOnlineStorage(client *redis.Client) *SortedSetOnlineStorage { return &SortedSetOnlineStorage{client: client} } func (s *SortedSetOnlineStorage) Store(ctx context.Context, pair UserOnlinePair) error { return s.client.ZAdd(ctx, "z:online:main", redis.Z{ Score: float64(pair.Timestamp), Member: strconv.FormatInt(pair.UserID, 10), }).Err() } func (s *SortedSetOnlineStorage) Count(ctx context.Context) (int64, error) { return s.client.ZCard(ctx, "z:online:main").Result() } func (s *SortedSetOnlineStorage) GetAndClear(ctx context.Context) ([]UserOnlinePair, error) { var ( oldKey = "z:online:main" newKey = "z:online:tmp" ) err := s.client.Rename(ctx, oldKey, newKey).Err() if err != nil { return nil, err } members, err := s.client.ZRangeWithScores(ctx, newKey, 0, -1).Result() if err != nil { return nil, err } result := make([]UserOnlinePair, 0, len(members)) for _, member := range members { stringUserID, ok := member.Member.(string) if !ok { // unreachable, ignore for article // just in case continue } userID, err := strconv.ParseInt(stringUserID, 10, 64) if err != nil { // unreachable, ignore for article // logging or use https://github.com/hashicorp/go-multierror // just in case continue } result = append(result, UserOnlinePair{ UserID: userID, Timestamp: int64(member.Score), }) } return result, nil }
Сервіс SortedSetOnlineStorage дуже схожий на HashOnlineStorage.
Я відрефакторю попередні тести Test(Redis|Keydb|Dragonflydb)HashOnlineStorage, щоб перевикористати їх:
package research_online_redis_go import ( "context" ) type OnlineStorage interface { Store(ctx context.Context, pair UserOnlinePair) error Count(ctx context.Context) (int64, error) GetAndClear(ctx context.Context) ([]UserOnlinePair, error) }
package research_online_redis_go import ( "context" "testing" "time" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) type onlineStorageConstructor func(client *redis.Client) OnlineStorage func testOnlineStorage(t *testing.T, addr string, newStorage onlineStorageConstructor) { t.Helper() ctx := context.Background() client, err := Client(ctx, addr) require.NoError(t, err) require.NoError(t, client.FlushAll(ctx).Err()) storage := newStorage(client) expected := []UserOnlinePair{ { UserID: 10000001, Timestamp: 1679800725, }, { UserID: 10000002, Timestamp: 1679800730, }, { UserID: 10000003, Timestamp: 1679800735, }, } for _, pair := range expected { err := storage.Store(ctx, pair) require.NoError(t, err) } actualCount, err := storage.Count(ctx) require.NoError(t, err) require.Equal(t, int64(len(expected)), int64(actualCount)) actual, err := storage.GetAndClear(ctx) require.NoError(t, err) requireUserOnlinePairsEqual(t, expected, actual) }
package research_online_redis_go import ( "testing" "github.com/redis/go-redis/v9" ) var sortedSetOnlineStorageConstructor onlineStorageConstructor = func(client *redis.Client) OnlineStorage { return NewSortedSetOnlineStorage(client) } func TestRedisSortedSetOnlineStorage(t *testing.T) { testOnlineStorage(t, "redis1:6379", sortedSetOnlineStorageConstructor) } func TestKeydbSortedSetOnlineStorage(t *testing.T) { testOnlineStorage(t, "keydb1:6379", sortedSetOnlineStorageConstructor) } func TestDragonflydbSortedSetOnlineStorage(t *testing.T) { testOnlineStorage(t, "dragonflydb1:6379", sortedSetOnlineStorageConstructor) }
В терміналі 1 я запускаю команду MONITOR, щоб бачити команди, які виконуються в KeyDB:
docker exec research-online-keydb-1 keydb-cli monitor
OK
Переходжу в термінал 2 та запускаю тести:
docker exec research-online-redis-go-app go test ./... -v -run='Test(Redis|Keydb|Dragonflydb)SortedSetOnlineStorage' -count=1
=== RUN TestRedisSortedSetOnlineStorage --- PASS: TestRedisSortedSetOnlineStorage (0.01s) === RUN TestKeydbSortedSetOnlineStorage --- PASS: TestKeydbSortedSetOnlineStorage (0.00s) === RUN TestDragonflydbSortedSetOnlineStorage --- PASS: TestDragonflydbSortedSetOnlineStorage (0.05s) PASS ok github.com/doutivity/research-online-redis-go 0.067s
Повертаюсь в термінал 1 та бачу команди, виконані в KeyDB:
1680178291.343241 [0 172.23.0.5:35168] "hello" "3" 1680178291.343337 [0 172.23.0.5:35168] "ping" 1680178291.345644 [0 172.23.0.5:35168] "flushall" 1680178291.345795 [0 172.23.0.5:35168] "zadd" "z:online:main" "1679800725" "10000001" 1680178291.345997 [0 172.23.0.5:35168] "zadd" "z:online:main" "1679800730" "10000002" 1680178291.346151 [0 172.23.0.5:35168] "zadd" "z:online:main" "1679800735" "10000003" 1680178291.346239 [0 172.23.0.5:35168] "zcard" "z:online:main" 1680178291.346316 [0 172.23.0.5:35168] "rename" "z:online:main" "z:online:tmp" 1680178291.346399 [0 172.23.0.5:35168] "zrange" "z:online:tmp" "0" "-1" "withscores"
Тести пройшли, збереження в Sorted set працює успішно.
Порівняння виконаних Redis-команд для типів Hash та Sorted set
Просто виніс порівняльну таблицю для наглядності:
Hash | Sorted set |
---|---|
HSET "h:online:main" "10000001" "1679800725" HSET "h:online:main" "10000002" "1679800730" HSET "h:online:main" "10000003" "1679800735" HLEN "h:online:main" RENAME "h:online:main" "h:online:tmp" HGETALL "h:online:tmp" | ZADD "z:online:main" "1679800725" "10000001" ZADD "z:online:main" "1679800730" "10000002" ZADD "z:online:main" "1679800735" "10000003" ZCARD "z:online:main" RENAME "z:online:main" "z:online:tmp" ZRANGE "z:online:tmp" "0" "-1" "WITHSCORES" |
Збереження онлайну за допомогою типу даних Set
У типу даних Set відсутня можливість зберігати пари user_id та timestamp у форматі ключ та значення, як це вдавалось для типу Hash та Sorted set.
Але timestamp можна засунути в назву Redis-ключа.
Для прикладу, в нас є користувач user_id = 10000001, який був онлайн timestamp = 1679800725, тоді ми можемо зберегти онлайн у Set командою SADD:
SADD "s:online:main:1679800725" "10000001" SADD "s:online:main:1679800730" "10000002" SADD "s:online:main:1679800735" "10000003"
У такому рішенні буде забагато Redis-ключів. Щоб це виправити, то потрібно групувати в один ключ (округлювати на початок часу), ми втрачаємо точність, але це прийнятно для нашого рішення:
SADD "s:online:main:1679800500" "10000001" SADD "s:online:main:1679800500" "10000002" SADD "s:online:main:1679800500" "10000003"
Обгортка SetOnlineStorage доступна в репозиторії, за бажанням можете поставити зірочку на GitHub.
docker exec research-online-dragonflydb-1 redis-cli monitor
docker exec research-online-redis-go-app go test ./... -v -run='Test(Redis|Keydb|Dragonflydb)SetOnlineStorage' -count=1
=== RUN TestRedisSetOnlineStorage --- PASS: TestRedisSetOnlineStorage (0.03s) === RUN TestKeydbSetOnlineStorage --- PASS: TestKeydbSetOnlineStorage (0.01s) === RUN TestDragonflydbSetOnlineStorage --- PASS: TestDragonflydbSetOnlineStorage (0.11s) PASS ok github.com/doutivity/research-online-redis-go 0.157s
1680181635.714046 [0 172.23.0.5:58334] "HELLO" "3" 1680181635.719216 [0 172.23.0.5:58334] "PING" 1680181635.738626 [0 172.23.0.5:58334] "FLUSHALL" 1680181635.770106 [0 172.23.0.5:58334] "SADD" "s:online:main:1679800725" "10000001" 1680181635.775436 [0 172.23.0.5:58334] "SADD" "s:online:main:1679800730" "10000002" 1680181635.780776 [0 172.23.0.5:58334] "SADD" "s:online:main:1679800735" "10000003" 1680181635.785386 [0 172.23.0.5:58334] "KEYS" "s:online:main:*" 1680181635.1244336 [0 172.23.0.5:58334] "SCARD" "s:online:main:1679800735" 1680181635.1249376 [0 172.23.0.5:58334] "SCARD" "s:online:main:1679800730" 1680181635.1256206 [0 172.23.0.5:58334] "SCARD" "s:online:main:1679800725" 1680181635.1261996 [0 172.23.0.5:58334] "KEYS" "s:online:main:*" 1680181635.1724486 [0 172.23.0.5:58334] "RENAME" "s:online:main:1679800735" "s:online:tmp" 1680181635.1736686 [0 172.23.0.5:58334] "SMEMBERS" "s:online:tmp" 1680181635.1743606 [0 172.23.0.5:58334] "RENAME" "s:online:main:1679800730" "s:online:tmp" 1680181635.1753966 [0 172.23.0.5:58334] "SMEMBERS" "s:online:tmp" 1680181635.1759236 [0 172.23.0.5:58334] "RENAME" "s:online:main:1679800725" "s:online:tmp" 1680181635.1768146 [0 172.23.0.5:58334] "SMEMBERS" "s:online:tmp
Вибір найкращого рішення
Потрібно вибрати одну з трьох баз Redis, KeyDB або DragonflyDB.
Database | Stars | Language |
---|---|---|
Redis | 59100+ | C |
KeyDB | 7100+ | C++ |
DragonflyDB | 18300+ | C++ |
А також потрібно вибрати один з типів даних Hash, Sorted set або Set.
Загалом 10 варіантів вибору.
Вибір бази очевидний, Redis має найбільше зірочок на GitHub.
Вибір типу даних також очевидний, Hash простіше пояснити колегам, а обгортка на Go HashOnlineStorage простіша та має менше коду, ніж SortedSetOnlineStorage та SetOnlineStorage.
Спіймав? Ця стаття вже занадто перевантажена, тому буде продовження з бенчмарками, порівнянням використаної пам’яті для кожного типу даних, а також стрес-тестами з падінням баз DragonflyDB та KeyDB.
Епілог
Дякую вам, що дочитали статтю, якщо сподобалась, то підтримайте, будь ласка, фінансово збір для 65 бригади від Go-розробника.
10-й варіант вибору
Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті
49 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів