Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
480 lines
12 KiB
Markdown
480 lines
12 KiB
Markdown
# Redis Stack: In-Memory Vector Database für AI
|
||
|
||
**Meta-Description:** Redis als Vector Database nutzen. RediSearch, RedisJSON, Vector Set und High-Performance AI-Anwendungen.
|
||
|
||
**Keywords:** Redis Stack, Vector Database, RediSearch, RedisJSON, In-Memory Database, AI Cache, Semantic Search
|
||
|
||
---
|
||
|
||
## Einführung
|
||
|
||
Redis 8 vereint alle Module in einem Package: **Vector Search, JSON, Full-Text Search** und mehr. Als In-Memory Database bietet Redis ultra-niedrige Latenz für AI-Anwendungen – ideal für Caching, Session Management und Echtzeit-Suche.
|
||
|
||
---
|
||
|
||
## Redis 8 Stack
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ REDIS 8 STACK │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Core Data Structures: │
|
||
│ ├── Strings, Lists, Sets, Hashes, Sorted Sets │
|
||
│ ├── Streams (Event Streaming) │
|
||
│ └── HyperLogLog, Bitmaps │
|
||
│ │
|
||
│ New in Redis 8: │
|
||
│ ├── Vector Set (Beta) - Similarity Search │
|
||
│ ├── JSON - Native JSON Document Store │
|
||
│ ├── Time Series - Metrics & Monitoring │
|
||
│ └── Probabilistic Structures │
|
||
│ ├── Bloom Filter │
|
||
│ ├── Cuckoo Filter │
|
||
│ ├── Count-Min Sketch │
|
||
│ ├── Top-K │
|
||
│ └── T-Digest │
|
||
│ │
|
||
│ Search Capabilities: │
|
||
│ ├── Full-Text Search │
|
||
│ ├── Vector Search (FLAT, HNSW, SVS-VAMANA) │
|
||
│ ├── Numeric/Tag Filtering │
|
||
│ └── Geospatial Queries │
|
||
│ │
|
||
│ Performance: │
|
||
│ └── Sub-Millisecond Latency (In-Memory) │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Setup
|
||
|
||
```bash
|
||
# Docker
|
||
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
|
||
|
||
# Redis Cloud (Managed)
|
||
# https://redis.com/try-free/
|
||
```
|
||
|
||
```typescript
|
||
// lib/redis.ts
|
||
import { createClient } from 'redis';
|
||
|
||
const redis = createClient({
|
||
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
||
});
|
||
|
||
redis.on('error', (err) => console.error('Redis Client Error', err));
|
||
|
||
await redis.connect();
|
||
|
||
export default redis;
|
||
```
|
||
|
||
---
|
||
|
||
## Vector Search mit Hash
|
||
|
||
```typescript
|
||
import redis from '@/lib/redis';
|
||
import { SchemaFieldTypes, VectorAlgorithms } from 'redis';
|
||
|
||
// 1. Index erstellen
|
||
async function createVectorIndex() {
|
||
try {
|
||
await redis.ft.create('idx:docs', {
|
||
'$.title': {
|
||
type: SchemaFieldTypes.TEXT,
|
||
AS: 'title'
|
||
},
|
||
'$.content': {
|
||
type: SchemaFieldTypes.TEXT,
|
||
AS: 'content'
|
||
},
|
||
'$.category': {
|
||
type: SchemaFieldTypes.TAG,
|
||
AS: 'category'
|
||
},
|
||
'$.embedding': {
|
||
type: SchemaFieldTypes.VECTOR,
|
||
AS: 'embedding',
|
||
ALGORITHM: VectorAlgorithms.HNSW,
|
||
TYPE: 'FLOAT32',
|
||
DIM: 1536,
|
||
DISTANCE_METRIC: 'COSINE'
|
||
}
|
||
}, {
|
||
ON: 'JSON',
|
||
PREFIX: 'doc:'
|
||
});
|
||
console.log('Index created');
|
||
} catch (e) {
|
||
if ((e as Error).message.includes('Index already exists')) {
|
||
console.log('Index already exists');
|
||
} else {
|
||
throw e;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. Document speichern (JSON)
|
||
async function saveDocument(
|
||
id: string,
|
||
title: string,
|
||
content: string,
|
||
category: string,
|
||
embedding: number[]
|
||
) {
|
||
await redis.json.set(`doc:${id}`, '$', {
|
||
title,
|
||
content,
|
||
category,
|
||
embedding
|
||
});
|
||
}
|
||
|
||
// 3. Vector Search
|
||
async function searchSimilar(
|
||
queryEmbedding: number[],
|
||
limit: number = 5
|
||
) {
|
||
const results = await redis.ft.search('idx:docs', '*=>[KNN $K @embedding $BLOB AS score]', {
|
||
PARAMS: {
|
||
K: limit.toString(),
|
||
BLOB: Buffer.from(new Float32Array(queryEmbedding).buffer)
|
||
},
|
||
RETURN: ['title', 'content', 'score'],
|
||
SORTBY: {
|
||
BY: 'score',
|
||
DIRECTION: 'ASC' // Lower = more similar for COSINE
|
||
},
|
||
DIALECT: 2
|
||
});
|
||
|
||
return results.documents.map(doc => ({
|
||
id: doc.id,
|
||
title: doc.value.title,
|
||
content: doc.value.content,
|
||
score: 1 - parseFloat(doc.value.score as string) // Convert to similarity
|
||
}));
|
||
}
|
||
|
||
// 4. Hybrid Search (Vector + Filter)
|
||
async function searchWithFilter(
|
||
queryEmbedding: number[],
|
||
category: string,
|
||
limit: number = 5
|
||
) {
|
||
const results = await redis.ft.search(
|
||
'idx:docs',
|
||
`(@category:{${category}})=>[KNN $K @embedding $BLOB AS score]`,
|
||
{
|
||
PARAMS: {
|
||
K: limit.toString(),
|
||
BLOB: Buffer.from(new Float32Array(queryEmbedding).buffer)
|
||
},
|
||
RETURN: ['title', 'content', 'category', 'score'],
|
||
DIALECT: 2
|
||
}
|
||
);
|
||
|
||
return results.documents;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Vector Set (Redis 8 Beta)
|
||
|
||
```typescript
|
||
// Neuer Datentyp in Redis 8 - Inspiriert von Sorted Sets
|
||
|
||
// Vector hinzufügen
|
||
await redis.sendCommand([
|
||
'VADD', 'products',
|
||
'VECTOR', ...embedding.map(v => v.toString()),
|
||
'product:123'
|
||
]);
|
||
|
||
// Ähnliche Vektoren finden
|
||
const similar = await redis.sendCommand([
|
||
'VSIM', 'products',
|
||
'VECTOR', ...queryEmbedding.map(v => v.toString()),
|
||
'COUNT', '10'
|
||
]);
|
||
|
||
// Vorteile von Vector Set:
|
||
// - Einfachere API als RediSearch
|
||
// - Optimiert für Similarity Search
|
||
// - Von Salvatore Sanfilippo (Redis Creator) entwickelt
|
||
```
|
||
|
||
---
|
||
|
||
## Session Cache mit Vector Search
|
||
|
||
```typescript
|
||
// Kombination: Session Management + Semantic Search
|
||
|
||
interface UserSession {
|
||
userId: string;
|
||
lastQuery: string;
|
||
queryEmbedding: number[];
|
||
searchHistory: string[];
|
||
createdAt: number;
|
||
expiresAt: number;
|
||
}
|
||
|
||
// Session speichern
|
||
async function saveSession(session: UserSession) {
|
||
const key = `session:${session.userId}`;
|
||
|
||
await redis.json.set(key, '$', session);
|
||
await redis.expireAt(key, session.expiresAt);
|
||
}
|
||
|
||
// Ähnliche Queries aus History finden
|
||
async function findSimilarPastQueries(
|
||
userId: string,
|
||
currentQueryEmbedding: number[]
|
||
) {
|
||
// Alle Sessions mit Query-History durchsuchen
|
||
const results = await redis.ft.search(
|
||
'idx:sessions',
|
||
'*=>[KNN 5 @queryEmbedding $BLOB AS score]',
|
||
{
|
||
PARAMS: {
|
||
BLOB: Buffer.from(new Float32Array(currentQueryEmbedding).buffer)
|
||
},
|
||
RETURN: ['userId', 'lastQuery', 'score'],
|
||
DIALECT: 2
|
||
}
|
||
);
|
||
|
||
return results.documents;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Full-Text + Vector Hybrid Search
|
||
|
||
```typescript
|
||
// Index mit Text + Vector
|
||
await redis.ft.create('idx:articles', {
|
||
'$.title': {
|
||
type: SchemaFieldTypes.TEXT,
|
||
AS: 'title',
|
||
WEIGHT: 2.0
|
||
},
|
||
'$.body': {
|
||
type: SchemaFieldTypes.TEXT,
|
||
AS: 'body'
|
||
},
|
||
'$.tags': {
|
||
type: SchemaFieldTypes.TAG,
|
||
AS: 'tags'
|
||
},
|
||
'$.embedding': {
|
||
type: SchemaFieldTypes.VECTOR,
|
||
AS: 'embedding',
|
||
ALGORITHM: VectorAlgorithms.HNSW,
|
||
TYPE: 'FLOAT32',
|
||
DIM: 1536,
|
||
DISTANCE_METRIC: 'COSINE'
|
||
}
|
||
}, {
|
||
ON: 'JSON',
|
||
PREFIX: 'article:'
|
||
});
|
||
|
||
// Hybrid Search: Text + Vector
|
||
async function hybridSearch(
|
||
textQuery: string,
|
||
queryEmbedding: number[],
|
||
limit: number = 10
|
||
) {
|
||
// Full-Text Search
|
||
const textResults = await redis.ft.search(
|
||
'idx:articles',
|
||
`@title|body:(${textQuery})`,
|
||
{
|
||
RETURN: ['title', 'body'],
|
||
LIMIT: { from: 0, size: limit }
|
||
}
|
||
);
|
||
|
||
// Vector Search
|
||
const vectorResults = await redis.ft.search(
|
||
'idx:articles',
|
||
'*=>[KNN $K @embedding $BLOB AS vector_score]',
|
||
{
|
||
PARAMS: {
|
||
K: limit.toString(),
|
||
BLOB: Buffer.from(new Float32Array(queryEmbedding).buffer)
|
||
},
|
||
RETURN: ['title', 'body', 'vector_score'],
|
||
DIALECT: 2
|
||
}
|
||
);
|
||
|
||
// Scores kombinieren (RRF - Reciprocal Rank Fusion)
|
||
const combined = new Map<string, { doc: any; score: number }>();
|
||
const k = 60; // RRF constant
|
||
|
||
textResults.documents.forEach((doc, rank) => {
|
||
const score = 1 / (k + rank + 1);
|
||
combined.set(doc.id, {
|
||
doc: doc.value,
|
||
score: score
|
||
});
|
||
});
|
||
|
||
vectorResults.documents.forEach((doc, rank) => {
|
||
const vectorScore = 1 / (k + rank + 1);
|
||
const existing = combined.get(doc.id);
|
||
|
||
if (existing) {
|
||
existing.score += vectorScore;
|
||
} else {
|
||
combined.set(doc.id, {
|
||
doc: doc.value,
|
||
score: vectorScore
|
||
});
|
||
}
|
||
});
|
||
|
||
return Array.from(combined.values())
|
||
.sort((a, b) => b.score - a.score)
|
||
.slice(0, limit);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Caching für AI Embeddings
|
||
|
||
```typescript
|
||
// Embedding Cache - vermeidet wiederholte API-Calls
|
||
|
||
async function getOrCreateEmbedding(
|
||
text: string,
|
||
generateFn: (text: string) => Promise<number[]>
|
||
): Promise<number[]> {
|
||
// Hash als Cache Key
|
||
const hash = await crypto.subtle.digest(
|
||
'SHA-256',
|
||
new TextEncoder().encode(text)
|
||
);
|
||
const cacheKey = `emb:${Buffer.from(hash).toString('hex').slice(0, 16)}`;
|
||
|
||
// Cache Check
|
||
const cached = await redis.get(cacheKey);
|
||
if (cached) {
|
||
return JSON.parse(cached);
|
||
}
|
||
|
||
// Generate & Cache
|
||
const embedding = await generateFn(text);
|
||
|
||
await redis.set(cacheKey, JSON.stringify(embedding), {
|
||
EX: 60 * 60 * 24 * 7 // 7 Tage TTL
|
||
});
|
||
|
||
return embedding;
|
||
}
|
||
|
||
// RAG Response Cache
|
||
async function getCachedRAGResponse(
|
||
queryEmbedding: number[],
|
||
threshold: number = 0.95
|
||
) {
|
||
// Suche nach sehr ähnlichen vorherigen Queries
|
||
const results = await redis.ft.search(
|
||
'idx:rag_cache',
|
||
'*=>[KNN 1 @queryEmbedding $BLOB AS score]',
|
||
{
|
||
PARAMS: {
|
||
BLOB: Buffer.from(new Float32Array(queryEmbedding).buffer)
|
||
},
|
||
RETURN: ['query', 'response', 'score'],
|
||
DIALECT: 2
|
||
}
|
||
);
|
||
|
||
if (results.documents.length > 0) {
|
||
const similarity = 1 - parseFloat(results.documents[0].value.score as string);
|
||
if (similarity >= threshold) {
|
||
return results.documents[0].value.response;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Pub/Sub für Real-Time Updates
|
||
|
||
```typescript
|
||
// Vector Search + Real-Time Updates
|
||
|
||
// Publisher
|
||
async function publishNewDocument(doc: Document) {
|
||
// In Redis speichern
|
||
await saveDocument(doc.id, doc.title, doc.content, doc.category, doc.embedding);
|
||
|
||
// Event publishen
|
||
await redis.publish('documents:new', JSON.stringify({
|
||
id: doc.id,
|
||
title: doc.title,
|
||
category: doc.category
|
||
}));
|
||
}
|
||
|
||
// Subscriber
|
||
const subscriber = redis.duplicate();
|
||
await subscriber.connect();
|
||
|
||
await subscriber.subscribe('documents:new', (message) => {
|
||
const doc = JSON.parse(message);
|
||
console.log('New document:', doc.title);
|
||
|
||
// UI Update, Cache Invalidation, etc.
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Fazit
|
||
|
||
Redis Stack bietet:
|
||
|
||
1. **Sub-Millisecond Latency**: In-Memory für Echtzeit-AI
|
||
2. **Unified Platform**: Vector, JSON, Full-Text in einem System
|
||
3. **Vector Set**: Neuer Datentyp für einfache Similarity Search
|
||
4. **Caching Layer**: Ideal für Embedding-Caches
|
||
|
||
Perfekt als High-Performance Layer vor AI-Anwendungen.
|
||
|
||
---
|
||
|
||
## Bildprompts
|
||
|
||
1. "In-memory database with vectors flowing at high speed, performance concept"
|
||
2. "Redis logo with vector arrows and AI neural network, modern database"
|
||
3. "Cache layer between AI model and application, latency optimization"
|
||
|
||
---
|
||
|
||
## Quellen
|
||
|
||
- [Redis Vector Database](https://redis.io/solutions/vector-database/)
|
||
- [Redis 8 GA Announcement](https://redis.io/blog/redis-8-ga/)
|
||
- [RediSearch Documentation](https://redis.io/docs/latest/develop/ai/search-and-query/)
|
||
- [Redis Vector Search Concepts](https://redis.io/docs/latest/develop/ai/search-and-query/vectors/)
|