When building data processing systems, it’s easy to think all pipelines are similar: they take data in, transform it, and produce outputs. However, indexing pipelines have unique characteristics that set them apart from traditional ETL, analytics, or transactional systems. Let’s explore what makes indexing special.
The short version: an index is a derived view of source content that has to stay correct as that content changes underneath it. That one property — derived data with a long, unpredictable lifecycle — drives almost every design decision below.
The nature of data: new vs derived
First, let’s understand a fundamental difference in how data is created:
Transactional systems: creating new data
In a typical application:
- A user creates a post
- The post is stored in a database
- This is new, original data being created
Indexing systems: building derived data
In contrast, indexing:
- Takes existing content
- Processes and transforms it
- Creates derived data structures (like vector embeddings or knowledge graphs)
- Maintains these structures over time
The distinction matters because it tells you where the source of truth lives. In a transactional system, the database row a user wrote is the truth. In an indexing system, the truth lives upstream — in the documents, files, or rows being indexed — and the index is just a projection of it. CocoIndex makes this explicit with a simple relationship: the target is a pure function of the source, TargetState = Transform(SourceState). There are no other side effects hiding in the pipeline, which is exactly what makes it safe to recompute a slice of the index without recomputing the whole thing. The core concepts guide builds the rest of the system on top of this one equation.
How do indexing pipelines compare with other data pipelines?
Analytics ETL
Analytics pipelines often:
- Process data in time-bounded windows
- Generate aggregated metrics
- May be run as one-off or scheduled jobs
- Focus on historical analysis
Time series / streaming
Streaming systems:
- Handle continuous flow of events
- Process data in real-time windows
- Today’s events are distinct from tomorrow’s
- Data naturally flows in and out of the system
Indexing pipelines
Indexing is different because:
- Content is persistent and long-lived
- Same content may need reprocessing
- Updates can happen at any time
- Must maintain consistency over long periods
The time dimension
The relationship with time is a key differentiator:
Streaming/time series
- Data is inherently time-bound
- Events belong to specific time windows
- Processing is forward-moving
- Historical data rarely changes
Indexing
- Data lifecycle isn’t tied to time
- Content can remain unchanged for long periods
- Updates are unpredictable
- Must handle both fresh and historical content
Why incremental processing matters
This persistence and longevity make incremental processing crucial for indexing:
-
Efficiency
- Reprocessing everything is costly
- Need to identify and process only what changed
- Must maintain consistency with unchanged content
-
Consistency
- Updates should preserve existing relationships
- Need to handle partial updates gracefully
- Must maintain referential integrity
-
Resource Usage
- Processing cost should scale with change size
- Avoid redundant computation
- Optimize storage and compute resources
This last point is worth dwelling on, because indexing usually wraps expensive work: LLM extraction, embedding model inference, OCR, large parses. Reprocessing a whole corpus to absorb a one-paragraph edit is not just slow, it can be the dominant cost of running the system. The goal is for processing cost to track the size of the change, not the size of the corpus.
How the mechanism actually works
The hard part of incremental processing is not the idea — it’s the bookkeeping. Doing it by hand means you have to figure out what changed, compute the delta of inserts, updates, and deletes for the target, preserve intermediate results so you don’t recompute everything, and evolve the target schema when your own code changes. With that many moving parts, debugging a stale or missing index row is miserable.
A declarative engine like CocoIndex takes over that bookkeeping. You describe the target as a function of the source; the engine diffs the new declared state against the previous run and applies only the difference. Two ideas make this concrete:
- Per-item processing components. The source is broken into independently processable items — a file, a row, an entity. Each item’s work plus its outputs form a unit that applies to the target as a batch when it completes (atomically, within a transaction where the backend supports it). Add a file and a new unit appears and inserts its rows; shrink a file from two chunks to one and the engine deletes the orphaned row and inserts the new one in the same transaction; delete the file and its rows are removed. Partial updates stay consistent because the unit is the boundary.
- Function memoization. If an item’s input and the code that processes it are both unchanged since last run, the whole unit is skipped. Memoization can also apply at the transform level: when you edit one chunk of a document, the unchanged chunks reuse their cached embeddings and only the edited chunk is re-embedded. This is what lets cost scale with change size instead of corpus size — even when your logic changes, cached results are reused wherever the intermediate values come out the same.
Catch-up vs. live: two ways to stay fresh
Because the index is derived, “staying fresh” is its own design axis. There are two natural modes:
- Catch-up mode scans the sources, processes whatever changed since the last run, syncs the targets, and returns. It is already incremental — unchanged work is skipped — but it only picks up changes when you trigger a run.
- Live mode keeps the app running and lets components stream changes continuously from their sources — a filesystem watcher, a database change feed, a Kafka topic — applying them to the index with low latency. Reach for it when you want near-real-time reactions, or when the source can push changes more cheaply than a full rescan can discover them.
The useful property is that the same pipeline definition serves both: you choose catch-up or live at run time, and the change-detection, memoization, and reconciliation logic is identical underneath.
Practical implications
These characteristics influence how we build indexing systems:
-
Change Detection
- Must track content versions
- Need efficient diff mechanisms
- Handle various update patterns
-
State Management
- Maintain persistent state
- Track processing history
- Handle interrupted operations
-
Update Strategies
- Balance freshness vs efficiency
- Handle out-of-order updates
- Manage concurrent modifications
-
Clear Ownership
- Every piece of data needs clear provenance
- Schema-level ownership through pipeline definitions
- Row-level ownership traced to source data
Understanding these unique aspects of indexing pipelines is crucial for building effective systems. While other data processing patterns might seem similar, indexing’s combination of persistence, long-lived data, and need for incremental processing creates distinct challenges and requirements.
Understanding these differences helps build more effective and efficient indexing systems that can maintain high-quality derived data structures over time.
Frequently asked questions.
What makes an indexing pipeline different from a normal ETL pipeline?
The core difference is that indexing produces derived data rather than new, original data. A transactional system creates new data (a user writes a post that becomes the source of truth), while an indexing system takes existing content and transforms it into derived structures like vector embeddings or knowledge graphs, then maintains those structures over time. Analytics ETL, by contrast, tends to run on time-bounded windows and produce aggregated metrics, often as one-off or scheduled jobs.
How do indexing pipelines differ from streaming or time-series systems?
Streaming and time-series data is inherently time-bound: events belong to specific time windows, processing is forward-moving, and historical data rarely changes. Indexing is different because its data lifecycle isn't tied to time. Content can stay unchanged for long periods, updates arrive unpredictably, and the system must handle both fresh and historical content while maintaining consistency.
Why does incremental processing matter for indexing pipelines?
Because indexed content is persistent and long-lived, the same content may need reprocessing and updates can happen at any time. Incremental processing matters for three reasons: efficiency (reprocessing everything is costly, so you process only what changed), consistency (updates should preserve existing relationships and referential integrity), and resource usage (processing cost should scale with the size of the change, not the size of the dataset).
Why is data ownership and provenance important in indexing systems?
Because every row in an index is derived from source content, each piece of data needs clear provenance so it can be traced back and kept consistent. The post describes schema-level ownership through the pipeline definition and row-level ownership traced to the source data, so derived structures stay correct as sources change.
What capabilities does an indexing system need to handle updates correctly?
The post identifies several: change detection (tracking content versions with efficient diff mechanisms), state management (maintaining persistent state, processing history, and handling interrupted operations), and update strategies (balancing freshness against efficiency, handling out-of-order updates, and managing concurrent modifications). These follow directly from content being persistent and updated at unpredictable times.