Почти каждому веб-проекту рано или поздно нужен Redis: кэшировать тяжёлые запросы, хранить сессии, ограничивать частоту запросов к API. Поднять его на том же сервере несложно, но потом приходится следить за памятью, обновлениями и тем, чтобы порт 6379 не оказался открыт в интернет. А облачные Redis-сервисы вроде Upstash и Redis Cloud из России не оплатить.
Что такое managed Redis
Managed Redis — это Redis, который обслуживает провайдер: память, обновления, реплики, TLS и сетевой доступ. Приложение получает адрес и пароль, а следить за сервером под кэшем больше не нужно.
На Татнет эту роль выполняет Managed Valkey. Valkey — это форк Redis,
который сообщество начало под эгидой Linux Foundation в 2024 году, после того как
Redis сменил лицензию. Протокол и команды те же, поэтому redis-py, ioredis,
node-redis и go-redis работают с ним без изменений: меняется только адрес
подключения.
Что получится в конце:
- кластер Valkey в приватной сети (VPC), куда пускают только адреса вашей сети;
- приложение подключается к нему по TLS и проверяет сертификат сервера;
- готовые примеры кэша, сессий и rate limit для Django, Node.js и Go.

Если вы уже читали гайд по Managed PostgreSQL, первые шаги покажутся знакомыми: сеть та же, а принцип подключения похож.
Шаг 0. CLI и сеть
Всё делаем из терминала через CLI Татнет. То же самое доступно в панели управления.
npx tatnet@latest --help # или npm install -g tatnet
tatnet auth login
export TATNET_PROJECT=my-saas
Регионы, версии Valkey и тарифы:
tatnet valkey regions -o json
Нужна приватная сеть в том же регионе, что и приложение. Если она уже есть (например, вы создавали её для PostgreSQL), используйте её. Если нет:
REGION=<id региона>
tatnet vpc create --name app-net --cluster $REGION --subnet 10.20.0.0/24
Подсеть может быть любой приватной, кроме 10.0.0.0/12: этот диапазон занят
инфраструктурой платформы. Приватные сети бесплатны. Зачем они вообще нужны,
мы разбирали в статье «Что такое VPC».
Шаг 1. Выбираем тариф по памяти для данных
У Valkey есть одна особенность, которую часто упускают. Вся память узла не
отдаётся под ключи: какая-то её часть всегда нужна на соединения, буферы
и служебные процессы. Объём, который разрешено занять данным, задаётся
параметром maxmemory, и он всегда меньше памяти узла.
Поэтому тарифы Татнет называются по памяти для данных, а не по объёму RAM:
| Тариф | Память узла | Для данных |
|---|---|---|
| vk1.1g | 1,5 ГБ | 1 ГБ |
| vk1.2g | 2,5 ГБ | 2 ГБ |
| vk1.4g | 5 ГБ | 4 ГБ |
Остальные тарифы и актуальные цены — на странице тарифов
и в выводе tatnet valkey regions.
Как прикинуть объём: сессия занимает от одного до нескольких килобайт, закэшированный JSON-ответ занимает столько же, сколько сам ответ, а счётчик rate limit — меньше сотни байт. Сто тысяч активных сессий по 2 КБ — это около 200 МБ. Большинству проектов на старте хватает гигабайта, а тариф потом можно поднять без пересоздания кластера (шаг 5).
Шаг 2. Кластер в приватной сети
VPC=<id сети app-net>
tatnet valkey create \
--name app-cache \
--region $REGION \
--plan vk1.1g \
--topology single \
--vpc $VPC \
--allow 10.20.0.0/24
Самый важный флаг здесь — --allow. У PostgreSQL есть ещё одна линия защиты,
pg_hba, а у Valkey её нет: список разрешённых адресов — единственный сетевой
фильтр перед портом. Поэтому разрешаем только подсеть нашей VPC и никогда не
пишем 0.0.0.0/0 «на время отладки».
Если позже понадобится добавить адрес, учтите, что команда allowlist заменяет
список целиком. Адреса, которые вы не перечислили, сразу теряют доступ:
tatnet valkey allowlist app-cache 10.20.0.0/24 10.30.0.0/24
Адрес и порт кластера покажет команда:
tatnet valkey get app-cache -o json
Пароль сохраните сразу, как только платформа его покажет. Если он потерялся или утёк, его можно сбросить:
tatnet valkey reset-password app-cache
Новый пароль показывается один раз, а старый перестаёт работать сразу. Поэтому сначала обновите секрет у приложения, а потом перезапустите его.
Шаг 3. Вытеснение и сохранение на диск
Что делать, когда память закончилась, определяет политика вытеснения. От неё зависит, как ведёт себя кэш под нагрузкой.
- Чистый кэш. Подходит вытеснение любых ключей по давности использования
(в терминах Valkey —
allkeys-lru). Когда память кончается, Valkey сам удаляет то, к чему дольше всего не обращались, а приложение при промахе просто идёт в базу. - Сессии и счётчики rate limit. Их потеря заметна пользователю: его разлогинит или ему сбросится лимит. Для них нужен запас памяти и политика, которая не трогает такие ключи без необходимости.
Самый простой способ не выбирать — завести два кластера: один под кэш с агрессивным вытеснением, второй под сессии и лимиты. Второй кластер на младшем тарифе обойдётся дешевле, чем разбираться, почему пользователей разлогинило в час пик.
Политику и режим сохранения на диск можно задать при создании (--eviction,
--persistence) или поменять позже:
tatnet valkey params app-cache --eviction <политика> --persistence <режим>
Параметры применяются к работающему серверу без перезапуска, так что политику можно спокойно поменять, когда станет понятно, как ведёт себя нагрузка.
Для чистого кэша сохранение на диск обычно не нужно: после перезапуска он прогреется заново. Для сессий его лучше включить.
Шаг 4. Подключение по TLS
Valkey на Татнет принимает только зашифрованные соединения. Схема адреса — rediss://
(с двумя s), а сертификат сервера клиент проверяет по корневому сертификату (CA)
кластера:
tatnet valkey ca app-cache --out valkey-ca.pem
CA-сертификат не секрет: его можно положить в репозиторий рядом с кодом. Секрет — это пароль, и он хранится в секретах приложения:
tatnet app env set api REDIS_URL "rediss://:<пароль>@<адрес>:<порт>/0" --secret
Приложение должно быть в той же сети. Для существующего приложения это делается
командой tatnet app update api --vpc $VPC. При смене сети платформа пересоздаёт
реплики приложения, так что возможен короткий перерыв.
Дальше приведены примеры для трёх стеков. Во всех них клиент проверяет и подпись сертификата, и имя сервера, а не просто шифрует трафик.
Django
В Django 4.0+ поддержка Redis встроена, отдельный пакет не нужен. Опции из OPTIONS
передаются в redis-py:
# settings.py
import os
VALKEY_TLS = {
"ssl_cert_reqs": "required",
"ssl_ca_certs": os.path.join(BASE_DIR, "valkey-ca.pem"),
}
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": os.environ["REDIS_URL"],
"OPTIONS": VALKEY_TLS,
"KEY_PREFIX": "cache",
"TIMEOUT": 300,
},
"sessions": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": os.environ.get("SESSIONS_REDIS_URL", os.environ["REDIS_URL"]),
"OPTIONS": VALKEY_TLS,
"KEY_PREFIX": "sess",
"TIMEOUT": None, # срок жизни сессии задаёт SESSION_COOKIE_AGE
},
}
# Сессии в Valkey, без запросов к базе на каждый хит
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "sessions"
Если вы завели второй кластер под сессии, передайте его адрес в SESSIONS_REDIS_URL.
Если терять сессии нельзя совсем, используйте backends.cached_db: тогда Valkey
работает как кэш, а источником правды остаётся база.
Кэш используется как обычно:
from django.core.cache import cache
def dashboard_stats(user_id):
return cache.get_or_set(f"stats:{user_id}", lambda: compute_stats(user_id), 60)
Rate limit. Пакет django-ratelimit хранит счётчики в кэше Django, поэтому
отдельная настройка ему не нужна:
from django_ratelimit.decorators import ratelimit
@ratelimit(key="ip", rate="10/m", method="POST", block=True)
def login_view(request):
...
Node.js
Пример на официальном клиенте redis (node-redis v4+) вместе с Express. Схема
rediss:// сама включает TLS, а CA мы передаём явно:
import fs from "node:fs";
import express from "express";
import session from "express-session";
import { createClient } from "redis";
import { RedisStore as SessionStore } from "connect-redis";
import { rateLimit } from "express-rate-limit";
import { RedisStore as LimitStore } from "rate-limit-redis";
const valkey = createClient({
url: process.env.REDIS_URL,
socket: {
ca: fs.readFileSync("./valkey-ca.pem"), // проверка сертификата и имени сервера
},
});
valkey.on("error", (err) => console.error("valkey:", err));
await valkey.connect();
const app = express();
// Сессии
app.use(
session({
store: new SessionStore({ client: valkey, prefix: "sess:" }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true, maxAge: 7 * 24 * 3600 * 1000 },
}),
);
// Rate limit: 100 запросов в минуту на IP для всего API
app.use(
"/api",
rateLimit({
windowMs: 60_000,
limit: 100,
standardHeaders: "draft-7",
legacyHeaders: false,
store: new LimitStore({
prefix: "rl:",
sendCommand: (...args) => valkey.sendCommand(args),
}),
}),
);
// Кэш
app.get("/api/products", async (req, res) => {
const cached = await valkey.get("cache:products");
if (cached) return res.type("json").send(cached);
const products = JSON.stringify(await db.products.findAll());
await valkey.set("cache:products", products, { EX: 300 });
res.type("json").send(products);
});
Если в проекте уже используется ioredis, менять его не нужно:
new Redis(process.env.REDIS_URL, { tls: { ca: fs.readFileSync("./valkey-ca.pem") } }).
Go
go-redis v9 по схеме rediss:// сам включает TLS и проверку имени сервера.
Нам остаётся только добавить CA кластера:
package cache
import (
"crypto/x509"
"os"
"github.com/redis/go-redis/v9"
)
func New() (*redis.Client, error) {
opt, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil {
return nil, err
}
pem, err := os.ReadFile("valkey-ca.pem")
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(pem)
opt.TLSConfig.RootCAs = pool // проверяем сертификат кластера, а не системные CA
return redis.NewClient(opt), nil
}
Кэш с промахом в базу:
func Product(ctx context.Context, rdb *redis.Client, id string) ([]byte, error) {
key := "cache:product:" + id
b, err := rdb.Get(ctx, key).Bytes()
if err == nil {
return b, nil
}
if err != redis.Nil {
return nil, err
}
b, err = loadProductJSON(ctx, id) // запрос в базу
if err != nil {
return nil, err
}
rdb.Set(ctx, key, b, 5*time.Minute)
return b, nil
}
Сессии в Go обычно хранят по шаблону «случайный ID в cookie, данные в Valkey
с TTL». Это rdb.Set(ctx, "sess:"+id, data, 7*24*time.Hour) при входе
и rdb.Get на каждом запросе.
Rate limit — пакет github.com/go-redis/redis_rate/v10. Он реализует
алгоритм GCRA одним Lua-скриптом, поэтому лимит считается атомарно, даже если
реплик приложения несколько:
limiter := redis_rate.NewLimiter(rdb)
func limit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res, err := limiter.Allow(r.Context(), "rl:"+clientIP(r), redis_rate.PerMinute(100))
if err == nil && res.Allowed == 0 {
w.Header().Set("Retry-After", strconv.Itoa(int(res.RetryAfter.Seconds())+1))
http.Error(w, "too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
Обратите внимание: если Valkey недоступен (err != nil), запрос пропускается.
Для большинства API лучше ненадолго пропустить лимит, чем уронить весь сервис.
Тот же принцип относится и к кэшу: ошибка Valkey должна превращаться в промах,
а не в ответ 500.
Шаг 5. Рост: тариф и HA
Тариф меняется одной командой:
tatnet valkey plan app-cache vk1.2g
Тариф можно только повышать: память, vCPU и диск нового тарифа должны быть не меньше
текущих. Узлы перезагружаются по одному, а новый лимит maxmemory начинает
действовать, когда все узлы уже работают на новых ресурсах. В топологии single
узел один, поэтому на время его перезагрузки кэш недоступен. Именно здесь
пригодится правило «ошибка Valkey — это промах».
Отказоустойчивость. Когда кэш или сессии становятся критичными, кластер переводится в HA:
tatnet valkey topology app-cache ha
Переход проходит без простоя: к работающему серверу присоединяется реплика,
и у кластера появляется отдельный адрес для чтения. Его можно отдать тяжёлым
читающим запросам, а основной адрес оставить для записи. При необходимости
кластер можно вернуть в single.
Переезд с Upstash и Redis Cloud
Для кэша переносить данные не нужно. Меняете REDIS_URL, добавляете CA,
выкатываете приложение, и кэш прогревается сам за счёт промахов в первые минуты.
Для сессий при простом переключении пользователей разлогинит один раз.
Если это критично, переключайтесь в спокойное время или сначала перейдите
на cached_db (Django) или аналог: тогда сессии переживут смену хранилища.
Код при переезде менять не нужно: Valkey понимает те же команды, что и Redis.
Сколько это стоит
Как считается стоимость:
- За узел. У кластера
singleодин узел. В HA узлов больше, и стоимость растёт кратно их числу. Итоговую сумму панель показывает до создания. - Почасово. Тестовый кластер, поднятый на вечер, обойдётся в рубли, а не в месячную подписку.
- Сеть бесплатна. Приватная сеть ничего не стоит на любом тарифе, а публичный адрес в этой схеме не нужен.
- В рублях, без иностранной карты и пересчёта по курсу.
Начинать стоит с младшего тарифа: тариф поднимается одной командой (шаг 5), а опустить его нельзя. Актуальные цены — на странице тарифов.
Итого
- Тариф выбираете по памяти для данных, а не по RAM узла.
- Кластер создаёте в VPC, allowlist ограничиваете подсетью сети: это единственный сетевой фильтр.
- Политику вытеснения подбираете под задачу. Кэш и сессии лучше держать в разных кластерах.
- Подключаетесь по
rediss://с CA кластера, пароль храните в секретах приложения. - Ошибка Valkey в коде должна давать промах, а не 500. Когда простой становится дорогим, переводите кластер в HA.
Полный справочник команд — в документации Managed Valkey. Создать первый кластер можно в панели Татнет.