Traditional RAG has a freshness problem. You ingest documents, chunk them, embed them, store them in a vector database, and query against them. But between ingestion and query, the world moves. Enterprise data is not static — it is a continuous stream of transactions, events, logs, and signals.
Streaming RAG closes this gap by making the retrieval corpus a living, continuously updated entity rather than a periodically refreshed snapshot.
The Architecture
Live Data Sources Streaming Layer AI Layer
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Transactions │──────▶│ │ │ │
│ Sensor Data │──────▶│ Apache Kafka │────▶│ Embedding │
│ Log Streams │──────▶│ (Event Bus) │ │ Service │
│ API Webhooks │──────▶│ │ │ │
└──────────────┘ └──────────────────┘ └──────┬───────┘
│
▼
┌──────────────────┐ ┌──────────────┐
│ Vector Store │◀────│ Upsert │
│ (Qdrant/Pinecone│ │ Pipeline │
│ /Weaviate) │ │ │
└──────┬───────────┘ └──────────────┘
│
▼
┌──────────────────┐
│ LLM + Query │────▶ Response
│ Orchestrator │
└──────────────────┘
Component Deep-Dive
1. Event Ingestion: Kafka as the Backbone
Apache Kafka serves as the central nervous system. Every data event — a new transaction, a sensor reading, a log entry — flows through Kafka topics.
Why Kafka over alternatives:
- Durability — Events are persisted on disk. Consumers can replay history.
- Ordering guarantees — Partition-level ordering ensures causal consistency.
- Backpressure handling — Slow consumers don't crash the pipeline.
- Ecosystem — Kafka Connect integrates with 200+ data sources out of the box.
Topic design for RAG:
rag.raw.transactions → Raw transaction events
rag.raw.support-tickets → Customer support tickets
rag.processed.chunks → Chunked, cleaned text ready for embedding
rag.embeddings.upserts → Embedding vectors ready for vector store insertion
rag.dlq.failures → Dead letter queue for failed processing
2. Stream Processing: Chunking and Cleaning
A stream processor (Kafka Streams, Flink, or a lightweight consumer) transforms raw events into RAG-ready chunks.
Processing steps:
- Schema validation — Reject malformed events.
- Text extraction — Pull relevant text from structured events (transaction descriptions, ticket content, log messages).
- Contextual enrichment — Append metadata (timestamp, source system, entity IDs).
- Chunking — Split long text into overlapping chunks (512-1024 tokens with 10-15% overlap).
- Deduplication — Hash-based dedup to avoid embedding identical content.
3. Embedding Service: Real-Time Vectorization
A stateless embedding service consumes chunks and produces vectors.
Design considerations:
- Batching — Accumulate chunks over a short window (100ms-500ms) and embed in batches for GPU efficiency.
- Model selection — Use lightweight embedding models (e.g.,
nomic-embed-text,bge-small) for real-time. Reserve larger models for batch re-indexing. - Autoscaling — Scale embedding workers based on Kafka consumer lag.
- Fallback — If the embedding service is down, events accumulate in Kafka (which handles backpressure natively) and are processed when the service recovers.
4. Vector Store: Continuous Upsert
The vector database receives a continuous stream of new embeddings.
Critical capabilities for streaming:
- Low-latency upsert — Sub-100ms insert times for individual vectors.
- Near-real-time index refresh — New vectors should be searchable within seconds, not minutes.
- TTL/Expiration — Automatically evict stale data (yesterday's sensor readings are rarely relevant).
- Metadata filtering — Combine vector similarity with metadata filters (time range, source system, entity type).
Recommended vector stores for streaming RAG:
| Store | Streaming Suitability | Notes | |---|---|---| | Qdrant | Excellent | Real-time indexing, rich filtering, gRPC API | | Weaviate | Good | Module ecosystem, hybrid search | | Milvus | Good | High throughput, GPU-accelerated | | Pinecone | Good | Managed service, serverless option |
5. Query-Time: Freshness-Aware Retrieval
At query time, the retrieval step must be freshness-aware:
# Pseudocode: freshness-weighted retrieval
def retrieve(query: str, freshness_weight: float = 0.3):
query_vector = embed(query)
results = vector_store.search(
vector=query_vector,
top_k=20,
filter={"timestamp": {"$gte": now() - timedelta(hours=24)}}
)
# Re-rank with freshness boost
for result in results:
age_hours = (now() - result.timestamp).total_seconds() / 3600
freshness_score = math.exp(-age_hours / 12) # Exponential decay
result.final_score = (1 - freshness_weight) * result.similarity + freshness_weight * freshness_score
return sorted(results, key=lambda r: r.final_score, reverse=True)[:5]
This ensures that recent data is prioritized without completely ignoring historically relevant context.
Handling the Hard Problems
Exactly-Once Semantics
In a streaming pipeline, events can be duplicated (at-least-once delivery). Without dedup, the same chunk appears multiple times in the vector store.
Solution: Use content hashing + upsert (not insert). If the same hash arrives again, it overwrites the existing entry rather than creating a duplicate.
Schema Evolution
Data sources change their schemas over time. A new field is added, a field type changes.
Solution: Use a schema registry (Confluent Schema Registry) with backward-compatible Avro or Protobuf schemas. The stream processor handles schema evolution transparently.
Cold Start
When the system first starts, the vector store is empty. Queries return no results.
Solution: Run a one-time batch backfill from historical data before enabling the streaming pipeline. Kafka's ability to replay from the beginning of a topic makes this straightforward.
When Streaming RAG Matters
- Financial services — Fraud detection queries need context from transactions that happened minutes ago.
- Customer support — Support agents need to see the customer's most recent interactions, not data from last week's batch refresh.
- IoT and manufacturing — Equipment diagnostics require real-time sensor context.
- Security operations — Threat analysis against log events that are actively flowing.
When Batch RAG Is Still Fine
If your data changes daily or weekly, and query freshness of hours is acceptable, batch RAG is simpler and cheaper. Don't add streaming complexity when batch-refreshed indexes are sufficient.
Our Implementation at ATMA-AI
Our neural pipeline architecture includes a streaming RAG module built on exactly these patterns. We handle the operational complexity — Kafka cluster management, embedding service autoscaling, vector store lifecycle management — so enterprises can focus on building AI applications that respond to reality as it happens.
Need real-time AI over streaming data? Talk to our data engineering team.