The era of “batch processing” is facing a silent execution. In the modern AI stack, the gap between a data point being written to a transactional database and that same data being available for a Retrieval-Augmented Generation (RAG) query is the difference between a cutting-edge user experience and a hallucinating legacy system.
For years, we’ve relied on brittle ETL (Extract, Transform, Load) pipelines—fragile Python scripts scheduled by Airflow that wake up every hour to shove data into a warehouse. But as we move toward Real-Time AI, the latency of the hourly batch is unacceptable. We need Zero-ETL.
Specifically, we need a way to take high-velocity writes occurring on Distributed Log-Structured Merge-Trees (LSM-trees) and instantly transform them into high-dimensional vector embeddings without ever touching a staging table. This is the story of how we build a streaming bridge between the raw transactional layer and the semantic search layer.
The Hype and the Reality: What is Zero-ETL?
In the last 18 months, “Zero-ETL” has become the industry’s favorite buzzword, championed by the likes of AWS, Snowflake, and Google Cloud. The marketing pitch is simple: “Your data is just there.”
However, as engineers, we know “Zero-ETL” doesn’t mean the work disappears. It means the integration has moved from the application layer to the infrastructure layer. Instead of writing custom connectors, the database engine itself handles the replication.
In the context of AI, Zero-ETL is about the Streaming Vector Pipeline. When a user updates their profile or a new product is added to a catalog, we don’t want to wait for a nightly job to vectorize that text. We want the Change Data Capture (CDC) event to trigger an inference call to a model (like CLIP or Text-Embedding-3) and sink that vector into a specialized store immediately.
The Foundation: Why Distributed LSM-Trees?
To understand why this is a hard engineering problem, we have to look at the storage engine. Most high-scale distributed databases—think ScyllaDB, Cassandra, TiDB, or RocksDB-based systems—utilize the Log-Structured Merge-Tree (LSM-tree).
Why LSM-Trees Win at Scale
Unlike B-Trees (used by Postgres or MySQL), which update data in place, LSM-trees are designed for high write throughput.
- Immutable SSTables: Writes are first stored in an in-memory Memtable.
- Sequential I/O: Once the Memtable is full, it’s flushed to disk as a sorted, immutable SSTable.
- Compaction: Background processes merge these tables to reclaim space.
This architecture is the “secret sauce” for distributed systems because it avoids the locking overhead of B-Trees. However, it creates a unique challenge for CDC: How do you track changes in a system that is constantly merging and deleting files in the background?
The CDC Bridge
To implement Zero-ETL, we tap into the Write-Ahead Log (WAL) or the database’s commit log. Every write—even before it hits the Memtable—is appended to this log. By tailing the WAL, we get a serialized stream of every insert, update, and delete.
This is where the magic happens. We aren’t querying the database; we are listening to its heartbeat.
The Architecture: From Commit Log to Vector Store
Building a real-time vector pipeline requires a highly resilient, four-tier architecture. Here is the blueprint for a system capable of handling 100k+ events per second with sub-second latency.
1. The Source: Distributed Database (LSM-Tree)
The source must support “CDC-on-Read” or have a dedicated changefeed. Systems like ScyllaDB or CockroachDB provide a stream of changes that can be pushed directly to a message broker.
2. The Transport: The Event Backbone
You need a buffer. If your embedding model’s API (like OpenAI or HuggingFace) goes down, you cannot afford to lose the data from the WAL.
- Redpanda/Kafka: The industry standard. We use a partitioned topic strategy where the partition key is the primary key of the database row. This ensures that updates to the same record are processed in the correct order.
3. The Compute: The Embedding Transformer
This is the “Transform” in the new ETL. This service (likely written in Go or Rust for performance) consumes the CDC stream.
- Payload Extraction: It parses the JSON/Protobuf from the WAL.
- Selective Vectorization: Not every column needs an embedding. The transformer selects the “content” fields (e.g.,
product_description). - Inference Call: It sends the text to the embedding model.
4. The Sink: The Vector Database
The final destination is a vector store like Pinecone, Milvus, or Weaviate. The transformer writes the original metadata + the generated 1536-dimensional vector.
Engineering Deep-Dive: Overcoming the Throughput Bottleneck
It sounds simple on paper, but at a scale of 50,000 writes per second, the “naive” implementation will melt. Here is how we optimize the pipeline.
Micro-Batching for Inference
Calling an embedding API for every single row update is a recipe for a massive cloud bill and extreme latency.
- The Strategy: Implement a sliding window buffer in your consumer.
- The Logic: Collect 100 records or wait 50ms, whichever comes first. Send these as a single batch request to the inference engine. This reduces HTTP overhead and allows GPUs to utilize SIMD (Single Instruction, Multiple Data) parallelism.
Handling the “Compaction Storm”
In LSM-trees, background compaction can sometimes cause spikes in CDC latency. If the database is busy merging SSTables, the WAL tailing might slow down.
- Solution: Decouple the CDC reader from the Embedding Transformer. The reader should do nothing but “dump” raw WAL bits into Redpanda. Any “thinking” (parsing, filtering) should happen downstream in the consumer.
Schema Evolution and Versioning
What happens when you switch from text-embedding-ada-002 to text-embedding-3-small?
Your vector store now contains two different dimensions or “semantic spaces.”
- The Zero-ETL Approach: Include a
model_versionfield in your vector metadata. Your application’s query logic must check this version. To migrate, you trigger a “re-scan” of the LSM-tree, which pumps all historical data back through the CDC pipe with the new model ID.
Technical Snippet: The CDC Consumer (Go)
Here’s a simplified look at how a high-performance consumer might handle the bridge between a Kafka-based CDC stream and an embedding provider.
type CDCPayload struct {
Before map[string]interface{} `json:"before"`
After map[string]interface{} `json:"after"`
Op string `json:"op"` // c=create, u=update, d=delete
}
func processEvents(ctx context.Context, events <-chan CDCPayload) {
batch := make([]string, 0, 100)
metadata := make([]map[string]interface{}, 0, 100)
for {
select {
case event := <-events:
// Only vectorize on Create or Update
if event.Op == "c" || event.Op == "u" {
content := event.After["description"].(string)
batch = append(batch, content)
metadata = append(metadata, event.After)
}
// If batch is full, ship it
if len(batch) >= 100 {
vectors := GetEmbeddings(batch) // External Inference Call
UpsertToVectorDB(vectors, metadata)
batch = batch[:0]
metadata = metadata[:0]
}
case <-time.After(50 * time.Millisecond):
// Flush on timeout to maintain real-time feel
if len(batch) > 0 {
// ... same flush logic ...
}
}
}
}
Addressing the Consistency Problem: “Eventual” is the Keyword
In a Zero-ETL architecture, we trade Strong Consistency for Availability and Speed.
When a user updates their “Bio” in your app, the update hits the LSM-tree immediately (millisecond latency). The CDC event then travels through Kafka, goes to the embedding model, and finally hits the vector store. This process usually takes 100ms to 500ms.
The “Race Condition”: If a user updates their bio and immediately performs a semantic search, they might see results based on their old bio.
How to solve this?
- Version Tracking: Return the
lsn(Log Sequence Number) from the database write to the frontend. - Wait-for-Sync: The frontend can pass this
lsnto the Search API. The Search API checks if the Vector Store has indexed up to thatlsn. If not, it can either wait briefly or flag the result as “potentially stale.”
Why Distributed LSM-Trees make this Scalable
The reason we focus on Distributed LSM-trees (like ScyllaDB) rather than standard B-Trees is Horizontal Scalability.
A B-Tree database usually has a “Primary” that handles all writes. This creates a single point of failure and a bottleneck for CDC. In a distributed LSM-tree system:
- The data is sharded across N nodes.
- Each node manages its own WAL and its own CDC stream.
- We can deploy N consumers in our Zero-ETL pipeline, each handling a specific shard.
This allows the vector pipeline to scale linearly. If your traffic doubles, you double your database nodes and your embedding consumer pods. There is no central orchestrator to choke on the volume.
The Performance Cost of Modernity
Every engineering decision is a trade-off. What is the “tax” of Zero-ETL on Distributed LSM-trees?
1. Write Amplification
CDC increases the IOPS on your database. Not only is the database writing to the SSTables and the WAL, but it is also now serving the “Read” requests from the CDC agent or pushing the data out via a network socket. At extreme scale, this can reduce the maximum write throughput of the DB by 10-15%.
2. The “Small Update” Problem
If you update a single 10-character field in a 5,000-character document, the CDC log will often capture the entire document to provide context for the embedding model. This generates significant network egress. We mitigate this by using Projected Changefeeds, where only the specific columns needed for the embedding are emitted.
3. Cost Management
Vectorizing every change is expensive. If you have a “hot” record that is updated 100 times a minute, you don’t want to call OpenAI 100 times.
- Debouncing: Implement a per-key debounce in your streaming layer. If
user_123has three updates in 2 seconds, only process the final state.
The Road Ahead: Native Vector LSM-Trees?
We are currently in the “Bridge” phase of AI infrastructure—using CDC to connect two different worlds (Transactional and Analytical). However, the next evolution is already emerging: Native Vector Integration within the LSM-tree itself.
Imagine an SSTable that doesn’t just store key:value pairs, but also maintains an HNSW (Hierarchical Navigable Small World) index as a sidecar file, built during the compaction process. When the database merges two SSTables, it also merges their vector indices.
Until that becomes the industry standard, the Zero-ETL CDC Pipeline remains the most robust, scalable way to power production AI applications. It respects the “Source of Truth” in your transactional database while providing the “Speed of Thought” required by modern LLMs.
Final Thoughts for the Engineering Lead
If you are building this today:
- Don’t write your own CDC connector. Use established tools like Debezium or the native changefeeds provided by your DB vendor.
- Monitor your “Vector Lag.” This is the new “Replication Lag.” Create an alert for when the time between
db_commit_timestampandvector_indexed_timestampexceeds 1 second. - Optimize your Embedding Compute. Whether you use a managed service or self-host vLLM, the embedding generation will be your primary bottleneck. Batch aggressively.
Zero-ETL isn’t just about removing Airflow jobs; it’s about building a living data nervous system. By tapping into the raw power of distributed LSM-trees and streaming changes directly into the vector space, we are closing the loop between data and intelligence.