Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Поиск по значениям в Redis
Да, я работал с поиском по значениям в Redis. Это важная задача, потому что Redis примитивно хранит ключ-значение, и поиск по значениям становится вызовом.
Проблема: Redis не предназначен для поиска по значениям
В классической Redis архитектуре поиск выглядит так:
import redis
r = redis.Redis()
# ЛЕГКО: поиск по ключу
value = r.get("user:123") # O(1)
# СЛОЖНО: поиск по значению
# Нужно отсканировать все ключи
for key in r.keys("*"):
if r.get(key) == "искомое значение":
print(f"Найдено в {key}")
# Это O(N) — очень медленно на больших объёмах!
Решение 1: Использовать Redis Sets для индексирования
Создаём обратный индекс: значение -> ключи, которые его содержат.
class UserCache:
def __init__(self, redis_client):
self.r = redis_client
def set_user(self, user_id: str, username: str, email: str):
"""Сохранить юзера и создать индекс для поиска"""
user_key = f"user:{user_id}"
# Сохранить юзера
self.r.hset(user_key, mapping={
"username": username,
"email": email
})
# Создать индекс: username -> user_id
index_key = f"index:username:{username}"
self.r.sadd(index_key, user_id)
# Создать индекс: email -> user_id
email_index_key = f"index:email:{email}"
self.r.sadd(email_index_key, user_id)
# Установить TTL для индексов (если нужна автоочистка)
self.r.expire(index_key, 86400) # 24 часа
self.r.expire(email_index_key, 86400)
def find_by_username(self, username: str):
"""Быстрый поиск по username"""
index_key = f"index:username:{username}"
user_ids = self.r.smembers(index_key)
results = []
for user_id in user_ids:
user_data = self.r.hgetall(f"user:{user_id}")
results.append({
"id": user_id,
**user_data
})
return results
def find_by_email(self, email: str):
"""Быстрый поиск по email"""
index_key = f"index:email:{email}"
user_ids = self.r.smembers(index_key)
results = []
for user_id in user_ids:
user_data = self.r.hgetall(f"user:{user_id}")
results.append({
"id": user_id,
**user_data
})
return results
# Использование
from redis import Redis
cache = UserCache(Redis())
# Сохранить
cache.set_user("123", "john_doe", "john@example.com")
cache.set_user("124", "jane_doe", "jane@example.com")
# Найти
results = cache.find_by_username("john_doe")
print(results) # [{'id': '123', 'username': 'john_doe', 'email': 'john@example.com'}]
Решение 2: Redis Sorted Sets для диапазонного поиска
Если нужно искать в диапазоне (например, цены), используй Sorted Sets:
class ProductCache:
def __init__(self, redis_client):
self.r = redis_client
def add_product(self, product_id: str, name: str, price: float):
"""Добавить товар с индексом по цене"""
product_key = f"product:{product_id}"
# Сохранить товар
self.r.hset(product_key, mapping={
"name": name,
"price": price
})
# Добавить в Sorted Set по цене
# score = цена (для сортировки)
# member = product_id
self.r.zadd("products:by_price", {product_id: price})
def find_by_price_range(self, min_price: float, max_price: float):
"""Найти товары в диапазоне цен"""
# ZRANGEBYSCORE с диапазоном
product_ids = self.r.zrangebyscore(
"products:by_price",
min_price,
max_price
)
results = []
for product_id in product_ids:
product_data = self.r.hgetall(f"product:{product_id}")
results.append({
"id": product_id,
**product_data
})
return results
def find_top_expensive(self, limit: int = 10):
"""Найти самые дорогие товары"""
# ZREVRANGE — в обратном порядке (от большего к меньшему)
product_ids = self.r.zrevrange("products:by_price", 0, limit - 1)
results = []
for product_id in product_ids:
product_data = self.r.hgetall(f"product:{product_id}")
results.append({
"id": product_id,
**product_data
})
return results
# Использование
cache = ProductCache(Redis())
cache.add_product("p1", "Laptop", 999.99)
cache.add_product("p2", "Phone", 499.99)
cache.add_product("p3", "Cable", 9.99)
# Найти товары от 100 до 700
results = cache.find_by_price_range(100, 700)
print(results) # Phone и Cable
# Найти топ 2 самых дорогих
results = cache.find_top_expensive(2)
print(results) # Laptop, Phone
Решение 3: Redis Streams для полнотекстового поиска (кейс-сенситивный)
Для более сложного поиска (например, частичное совпадение), используй полнотекстовый индекс:
class DocumentCache:
def __init__(self, redis_client):
self.r = redis_client
def index_document(self, doc_id: str, title: str, content: str):
"""Индексировать документ по словам"""
doc_key = f"doc:{doc_id}"
# Сохранить документ
self.r.hset(doc_key, mapping={
"title": title,
"content": content
})
# Индексировать по словам
all_text = f"{title} {content}".lower()
words = all_text.split()
for word in words:
# Каждое слово -> набор doc_id
index_key = f"index:word:{word}"
self.r.sadd(index_key, doc_id)
def search(self, query: str):
"""Поиск документов по словам"""
query_words = query.lower().split()
# Получить doc_id для каждого слова
doc_sets = []
for word in query_words:
index_key = f"index:word:{word}"
doc_ids = self.r.smembers(index_key)
if doc_ids:
doc_sets.append(doc_ids)
if not doc_sets:
return []
# Пересечение (AND) — документы со всеми словами
matching_docs = set.intersection(*doc_sets)
results = []
for doc_id in matching_docs:
doc_data = self.r.hgetall(f"doc:{doc_id}")
results.append({
"id": doc_id,
**doc_data
})
return results
# Использование
cache = DocumentCache(Redis())
cache.index_document("doc1", "Python Tutorial", "Learn Python programming")
cache.index_document("doc2", "Java Guide", "Introduction to Java")
cache.index_document("doc3", "Python Advanced", "Advanced Python techniques")
# Поиск документов со словами "Python" И "programming"
results = cache.search("Python programming")
print(results) # [doc1]
Решение 4: Redis Search (Redis 6+, модуль RediSearch)
Для современных версий Redis есть встроенный модуль RediSearch:
from redis.commands.search.field import TextField, NumericField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis import Redis
class RediSearchCache:
def __init__(self, redis_client: Redis, index_name: str):
self.r = redis_client
self.index_name = index_name
def create_index(self):
"""Создать индекс для полнотекстового поиска"""
index_def = IndexDefinition(index_type=IndexType.HASH, prefix=["product:"])
# Определить схему
fields = [
TextField("name"),
TextField("description"),
NumericField("price")
]
self.r.ft(self.index_name).create_index(fields, definition=index_def)
def add_product(self, product_id: str, name: str, description: str, price: float):
"""Добавить товар"""
self.r.hset(f"product:{product_id}", mapping={
"name": name,
"description": description,
"price": price
})
def search(self, query: str):
"""Полнотекстовый поиск"""
results = self.r.ft(self.index_name).search(query)
return results.docs
def search_by_price(self, min_price: float, max_price: float):
"""Поиск по цене"""
query = f"@price:[{min_price} {max_price}]"
results = self.r.ft(self.index_name).search(query)
return results.docs
# Использование
cache = RediSearchCache(Redis(), "products_index")
cache.create_index()
cache.add_product("p1", "Laptop Pro", "High performance laptop", 1499.99)
cache.add_product("p2", "Laptop Air", "Lightweight laptop", 999.99)
# Полнотекстовый поиск
results = cache.search("laptop")
print(f"Найдено: {len(results)} товаров")
# Поиск по диапазону цен
results = cache.search_by_price(500, 1200)
print(f"Товары в диапазоне 500-1200: {len(results)}")
Сравнение подходов
| Подход | Скорость | Сложность | Когда использовать |
|---|---|---|---|
| Sets (обратный индекс) | O(1) | Низкая | Точное совпадение, фиксированные поля |
| Sorted Sets | O(log N) | Низкая | Диапазонный поиск, сортировка |
| Полнотекстовый на Sets | O(N) | Средняя | Частичное совпадение слов |
| RediSearch | O(1) | Высокая | Сложный полнотекстовый поиск |
Практические советы
1. Избегай сканирования всех ключей
# ПЛОХО
for key in r.keys("*"):
...
# ХОРОШО
for key in r.scan_iter(match="user:*", count=1000):
...
2. Используй сложные ключи для индексирования
# Вместо index:word:python, используй хеш для интернационализации
index_key = f"index:{field}:{value}:{lang}"
3. Управляй TTL индексов
r.setex(f"index:email:{email}", 86400, user_id)
4. Для больших объёмов используй RediSearch
Если ты работаешь с миллионами записей, встроенный модуль RediSearch даст лучшую производительность.
Итоговые рекомендации
Да, я работал с поиском по значениям в Redis. Ключевой подход — создание обратных индексов (Sets, Sorted Sets или RediSearch). Это требует дополнительной памяти, но даёт O(1) или O(log N) скорость поиска вместо O(N) сканирования. Для production используй RediSearch, если версия Redis это поддерживает.