Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
# 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/)
|
||||
Reference in New Issue
Block a user