Irbisa · Шпаргалка
Redis и кэширование
Типы данных, однопоточное исполнение, персистентность, вытеснение и TTL, pipelining и Lua, cache penetration и stampede, Sentinel и Cluster
Почему Redis такой быстрый, хотя выполняет команды в одном потоке, и что изменили I/O-потоки в Redis 6?
Какие типы данных Redis вы знаете и для какой задачи бэкенда подходит каждый?
// Errors are ignored for brevity; check them in real code.func dataTypes(ctx context.Context, rdb *redis.Client) error { // String: a cached value with a TTL, or an atomic counter rdb.Set(ctx, "product:42", `{"name":"Desk lamp"}`, 10*time.Minute) rdb.Incr(ctx, "views:product:42")…Чем Redis отличается от Memcached и когда всё же стоит выбрать Memcached?
Как поставить кэш Redis перед Postgres в Go-сервисе и как не допустить cache stampede (лавину промахов), когда истекает горячий ключ?
type ProductCache struct { rdb *redis.Client repo *Repo group singleflight.Group}…Найдите баг: каждый промах кэша пишется в лог как ошибка, и хендлер отвечает
500вместо того, чтобы загрузить товар из Postgres.func (c *Cache) Product(ctx context.Context, id int64) (Product, error) { key := fmt.Sprintf("product:%d", id) b, err := c.rdb.Get(ctx, key).Bytes() if err != nil { c.log.ErrorContext(ctx, "cache get", "key", key, "err", err) return Product{}, err } if b == nil { // miss: load from Postgres and fill the cache return c.load(ctx, key, id) } var p Product err = json.Unmarshal(b, &p) return p, err}Запросы по несуществующим id проходят сквозь кэш прямо в Postgres. Что такое cache penetration и как его останавливают негативное кэширование и фильтр Блума?
const notFound = "\x00none" // a marker no real JSON value can equal func (c *Cache) Product(ctx context.Context, id int64) (Product, error) { key := fmt.Sprintf("product:%d", id) b, err := c.rdb.Get(ctx, key).Bytes() if err == nil && string(b) == notFound {…Этот цикл читает 500 цен, по одному
GETна каждую, и занимает около 250 мс, а CPU у Redis почти простаивает. Почему, и что меняютMGETи пайплайнинг?func (s *Store) Prices(ctx context.Context, ids []string) (map[string]string, error) { out := make(map[string]string, len(ids)) for _, id := range ids { v, err := s.rdb.Get(ctx, "price:"+id).Result() if errors.Is(err, redis.Nil) { continue // no price cached for this id } if err != nil { return nil, err } out[id] = v } return out, nil}Этот запрос даёт Redis 50 мс, но когда Redis подвисает, хендлер висит секундами. Почему дедлайн не срабатывает и как настроить клиент?
var rdb = redis.NewClient(&redis.Options{Addr: "cache:6379"}) func price(ctx context.Context, sku string) (string, error) { // Redis gets 50 ms; past that the caller reads Postgres instead. ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) defer cancel() return rdb.Get(ctx, "price:"+sku).Result()}Как Redis удаляет ключи с истёкшим TTL и почему истёкшие ключи всё ещё могут занимать память?
func ttlRules(ctx context.Context, rdb *redis.Client) { rdb.Set(ctx, "views", 0, time.Hour) rdb.Incr(ctx, "views") // changes the value, keeps the TTL fmt.Println(rdb.TTL(ctx, "views").Val()) // 1h0m0s rdb.Set(ctx, "views", 5, 0) // replaces the key: the TTL is gone…Что происходит, когда Redis достигает
maxmemory, и какая политика вытеснения (allkeys-lru,allkeys-lfu,volatile-ttl,noeviction) подходит для кэша?Как Redis сохраняет данные через RDB-снапшоты и AOF и сколько записей можно потерять при падении в каждом случае?
# RDB: snapshot after 3600 s if 1 key changed, 300 s if 100, 60 s if 10000save 3600 1 300 100 60 10000 # AOF: log every write, fsync once a second (a crash loses about 1 s)appendonly yesappendfsync everysec…Что в Redis атомарно: чем отличаются
MULTI/EXEC,WATCHи Lua-скрипты и почему транзакция Redis никогда не откатывается?// WATCH: read, decide in Go, write in MULTI/EXEC; EXEC fails if the key changed meanwhile.func withdraw(ctx context.Context, rdb *redis.Client, key string, amount int64) error { for range 5 { err := rdb.Watch(ctx, func(tx *redis.Tx) error { balance, err := tx.Get(ctx, key).Int64() if err != nil {…Почему один
KEYS *илиDELмножества из двух миллионов элементов может затормозить Redis для всех клиентов и что вместо этого делаютSCANиUNLINK?// wrong: rdb.Keys(ctx, "session:*") walks every key in one blocking command.// right: SCAN in small steps, and UNLINK so the memory is freed off the main thread.func deleteByPrefix(ctx context.Context, rdb *redis.Client, prefix string) (int, error) { iter := rdb.Scan(ctx, 0, prefix+"*", 500).Iterator() // 500 is a hint, not a limit batch := make([]string, 0, 500) deleted := 0…Как использовать Redis как очередь (список с
BLMOVE, Pub/Sub или стрим с группой консьюмеров) и что теряет каждый вариант, когда консьюмер падает?func consume(ctx context.Context, rdb *redis.Client, consumer string) error { for ctx.Err() == nil { res, err := rdb.XReadGroup(ctx, &redis.XReadGroupArgs{ Group: "billing", Consumer: consumer, Streams: []string{"orders", ">"}, // ">": entries not yet given to anyone in the group…Чем отличаются Redis Sentinel и Redis Cluster, как Cluster распределяет ключи по 16384 hash slot и почему некоторые команды с несколькими ключами падают с
CROSSSLOT?// Sentinel: one primary; the client asks the sentinels where it is now.func newSentinelClient() *redis.Client { return redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: "orders", SentinelAddrs: []string{"sentinel-1:26379", "sentinel-2:26379", "sentinel-3:26379"}, })…Репликация в Redis асинхронная. Какие подтверждённые записи может потерять failover и что меняют
WAITиmin-replicas-to-write?Redis падает, все чтения проваливаются в Postgres, и Postgres тоже ложится. Что должно было быть сделано до аварии?
var ErrOverloaded = errors.New("overloaded") // the handler answers 503 with Retry-After // DBLoader caps how many cache misses one pod may send to Postgres at once.// Size the cap from what Postgres can serve, divided by the number of pods.type DBLoader struct { slots chan struct{}…