В комментарии к прошлому посту, попросили поделиться подробнее про pipline.
Часто кэш читают/пишут «в лоб» — по одному запросу. Но основная цена Redis в проде — это
RTT (туда-обратно по сети).
Pipeline позволяет отправить пачку команд за один сетевой раунд — сервер выполнит их по порядку, а ответы придут одной очередью.
Что такое pipeline (и чем он НЕ является)
- Pipeline
не делает операции атомарными. Это просто «упаковка» команд в один раунд-трип.
- Для атомарности нужен
MULTI/EXEC (**TxPipeline** в go-redis) или Lua-скрипт, либо
WATCH +
TxPipelined.
- Команды в pipeline
выполняются по очереди на сервере, но клиент не ждёт ответ после каждой — экономим RTT.
---
Когда использовать
- Серии однотипных чтений/записей (например, 50 ключей
GET / `HGETALL`).
- Смешанные операции, где
ответы предыдущих команд не нужны для формирования следующих.
- Интеграции «на пути» запроса (критично снижать задержку).
Когда
не стоит:
- Когда следующая команда зависит от результата предыдущей → делайте двумя фазами.
- Огромные пакеты (тысячи команд) — растёт задержка одной пачки и риск таймаутов. Лучше
батчить.
---
Простой пример на Go.
package main
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type Post struct {
ID, Author, Title, Content string
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
// 1) Запись: тело поста + счётчики одним pipeline
post := Post{ID: "42", Author: "alice", Title: "Pipeline 101", Content: "…"}
body, _ := json.Marshal(post)
_, err := rdb.Pipelined(ctx, func(p redis.Pipeliner) error {
p.Set(ctx, "post:42:body", body, 10*time.Minute) // TTL у тела
p.HSet(ctx, "post:42:counters", "views", 0, "likes", 0) // счётчики отдельно
p.Persist(ctx, "post:42:counters") // живут дольше тела
return nil
})
if err != nil {
panic(err)
}
// 2) Чтение: тело + счётчики за 1 RTT
var (
bodyCmd = rdb.Get(ctx, "post:42:body")
ctrCmd = rdb.HGetAll(ctx, "post:42:counters")
)
// Внимание: без Pipelined() мы сделали 2 команды, но они уйдут подряд на одном соединении.
// Для явного пайплайна:
pipe := rdb.Pipeline()
bodyCmd = pipe.Get(ctx, "post:42:body")
ctrCmd = pipe.HGetAll(ctx, "post:42:counters")
if _, err := pipe.Exec(ctx); err != nil && err != redis.Nil {
panic(err)
}
var p Post
if b, err := bodyCmd.Bytes(); err == nil && len(b) > 0 {
_ = json.Unmarshal(b, &p)
}
counters := ctrCmd.Val()
fmt.Println("title:", p.Title, "views:", counters["views"])
}