← Назад к вопросам

Что используем как значение в Redis?

1.7 Middle🔥 201 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Redis: типы данных и использование

Redis — это in-memory хранилище данных. Использую его почти в каждом проекте для кэширования, очередей и sessions.

Основные типы значений в Redis

1. String (Строка)

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Простая строка
r.set('user:1:name', 'John Doe')
name = r.get('user:1:name')
# 'John Doe'

# С expiration (важно!)
r.setex('session:abc123', 3600, 'user_token')  # Истечёт через час

# Числовые операции
r.set('page_views', 0)
r.incr('page_views')  # +1
r.incrby('page_views', 5)  # +5
r.get('page_views')
# '6'

# Битовые операции
r.setbit('users_online', 0, 1)  # User 0 online
r.getbit('users_online', 0)
# 1

2. List (Список)

Ордерированный список элементов. Эффективен для очередей.

# Добавление
r.rpush('tasks', 'task1', 'task2', 'task3')
# [0, 2, 4, 6, 8]

# FIFO очередь
r.lpop('tasks')  # 'task1' - берём слева
r.lpush('tasks', 'task0')  # Добавляем слева

# LIFO (Stack)
r.rpush('stack', 'a', 'b', 'c')
r.rpop('stack')  # 'c' - берём справа (последний)

# Получить диапазон
r.lrange('tasks', 0, -1)  # Все элементы
r.lrange('tasks', 0, 2)   # Первые 3

# Длина
r.llen('tasks')

# Практический пример: job queue
def add_job(job_type, data):
    job = json.dumps({"type": job_type, "data": data})
    r.rpush('job_queue', job)

def process_job():
    job_json = r.blpop('job_queue', timeout=0)  # Блокирующее, ждёт
    if job_json:
        job = json.loads(job_json[1])
        # Обработать

3. Hash (Хеш)

Меп ключ-значение. Идеален для объектов.

# Сохранить объект
r.hset('user:1', mapping={
    'name': 'John',
    'email': 'john@example.com',
    'age': '30'
})

# Получить конкретное поле
r.hget('user:1', 'name')  # 'John'

# Получить все
r.hgetall('user:1')
# {'name': 'John', 'email': 'john@example.com', 'age': '30'}

# Инкремент
r.hincrbyfloat('user:1:stats', 'rating', 0.5)

# Проверить существование поля
r.hexists('user:1', 'name')  # True

# Удалить поле
r.hdel('user:1', 'age')

# Практический пример: кэш сессии
def save_session(session_id, user_data):
    r.hset(f'session:{session_id}', mapping=user_data)
    r.expire(f'session:{session_id}', 3600)  # 1 час

def get_session(session_id):
    return r.hgetall(f'session:{session_id}')

4. Set (Множество)

Неупорядоченное множество уникальных элементов.

# Добавить элементы
r.sadd('tags:python', 'django', 'fastapi', 'celery')
r.sadd('tags:javascript', 'react', 'vue', 'angular')

# Получить все элементы
r.smembers('tags:python')
# {'django', 'fastapi', 'celery'}

# Проверить принадлежность
r.sismember('tags:python', 'django')  # True

# Размер
r.scard('tags:python')  # 3

# Операции над множествами
r.sinter('tags:python', 'tags:javascript')  # Пересечение
r.sunion('tags:python', 'tags:javascript')   # Объединение
r.sdiff('tags:python', 'tags:javascript')    # Разность

# Практический пример: followers
def follow_user(user_id, follower_id):
    r.sadd(f'user:{user_id}:followers', follower_id)

def get_followers(user_id):
    return r.smembers(f'user:{user_id}:followers')

def common_followers(user1_id, user2_id):
    return r.sinter(
        f'user:{user1_id}:followers',
        f'user:{user2_id}:followers'
    )

5. Sorted Set (Упорядоченное множество)

Множество с score для сортировки. Самое мощное!

# Добавить элементы со score
r.zadd('leaderboard', {
    'player1': 100,
    'player2': 150,
    'player3': 120
})

# Получить по рангу (от лучших)
r.zrevrange('leaderboard', 0, 2)  # Top 3
# ['player2', 'player1', 'player3']

# С scores
r.zrevrange('leaderboard', 0, 2, withscores=True)
# [('player2', 150), ('player1', 100), ('player3', 120)]

# Ранг игрока
r.zrevrank('leaderboard', 'player1')  # 1 (второе место, 0-indexed)

# Обновить score
r.zincrby('leaderboard', 50, 'player1')  # +50

# Количество элементов в диапазоне score
r.zcount('leaderboard', 100, 150)  # Элементы с score от 100 до 150

# Практический пример: рейтинг
def record_event(event_name, value=1):
    today = datetime.now().strftime('%Y-%m-%d')
    r.zincrby(f'events:{event_name}:{today}', value, event_name)

def get_top_events(event_name, top=10):
    today = datetime.now().strftime('%Y-%m-%d')
    return r.zrevrange(
        f'events:{event_name}:{today}',
        0, top-1,
        withscores=True
    )

Когда использовать какой тип

use_cases = {
    "String": [
        "Кэш значений (user profile)",
        "Счётчики (page views, likes)",
        "Sessions (с expiration)",
        "Rate limiting (с INCR)"
    ],
    "List": [
        "Очереди работ (job queue)",
        "Timeline событий (newest first)",
        "Stack для undo/redo",
        "Message queue между сервисами"
    ],
    "Hash": [
        "Кэш объектов (user data, product info)",
        "Сессии (всё в одном ключе)",
        "Tracking данные (metrics per user)",
        "Конфигурация (key-value pairs)"
    ],
    "Set": [
        "Теги (unique values)",
        "Followers/Following",
        "Membership (is user in group?)",
        "Уникальные значения (unique IPs, user IDs)"
    ],
    "Sorted Set": [
        "Leaderboards и рейтинги",
        "Time-based data (с timestamp как score)",
        "Priority queues",
        "Range queries (между min и max)"
    ]
}

Best Practices

1. Правильное именование ключей

# Плохо
r.set('u1', 'john')
r.set('pd', 'secret')

# Хорошо: namespace:object:id:field
r.set('user:1:name', 'john')
r.set('user:1:password_hash', 'secret')
r.hset('user:1', mapping={'name': 'john', 'email': 'j@e.com'})

2. Expiration для избежания утечки памяти

# ВСЕГДА для кэша и sessions
r.setex('cache:user:1', 3600, user_data)  # 1 час
r.expire('session:abc', 86400)  # 1 день

# Проверить TTL
r.ttl('session:abc')  # Секунд до expiration (-1 = никогда)

3. Pipeline для множественных операций

# Вместо 100 запросов
pipe = r.pipeline()
for i in range(100):
    pipe.incr('counter')
pipe.execute()  # Один network roundtrip!

4. Transactions при необходимости

pipe = r.pipeline(transaction=True)
pipe.watch('balance:user:1')
balance = r.get('balance:user:1')
if int(balance) >= 100:
    pipe.multi()
    pipe.decrby('balance:user:1', 100)
    pipe.incrby('balance:user:2', 100)
    pipe.execute()

Сложность операций

time_complexity = {
    "String": "O(1) get/set",
    "List": "O(1) push/pop, O(n) range",
    "Hash": "O(1) get/set field, O(n) hgetall",
    "Set": "O(1) add/remove, O(n) operations",
    "Sorted Set": "O(log n) add/remove, O(log n + m) range"
}

Частые ошибки

# ❌ Забыли expiration
r.set('cache:key', data)  # Никогда не удалится!

# ❌ Сохранили сложный объект как string
r.set('user:data', user_obj)  # Сериализируется как repr()

# ✅ Используй JSON
import json
r.set('user:data', json.dumps(user_obj))

# ✅ Или Hash
r.hset('user:1', mapping=user_dict)

Итог

Redis — это не БД, это кэш и message queue. Правила:

  1. String для простых значений и счётчиков
  2. Hash для объектов
  3. List для очередей
  4. Set для уникальных значений и членства
  5. Sorted Set для рейтингов и временных рядов
  6. Всегда используй expiration кроме постоянных данных
Что используем как значение в Redis? | PrepBro