Engineering Blog
Company Stefan Braun

Feldera Open Beta: Lessons from Building a Streaming Compute Engine

After building Feldera's incremental SQL engine from first principles and running it with real production workloads in beta through public launch, we share what surprised us and what we rethought.

Feldera Open Beta: What We Learned Building a Streaming Compute Engine

Building an incremental SQL engine differs fundamentally from building a batch query engine. A batch engine must produce correct results once from static input. An incremental engine must produce correct results continuously as inputs change, preserving enough state to calculate each new result from the previous one without starting over. Database research has established the algorithmic foundations. Making those algorithms reliable at production scale took us longer to engineer than we expected.

This post looks back on what we learned during Feldera's open beta, which ran from early this year through the public launch. We ran production workloads with beta customers, extensively instrumented each pipeline, and gathered substantial operational data. Some challenges we expected were easier than anticipated. Others that seemed straightforward had more depth than we had accounted for.

Query compilation was harder than the literature suggested

The database literature has described incremental view maintenance since the 1980s. Its basic delta rules for COUNT, SUM, and JOIN are well documented. It gives less attention to arbitrary SQL written by practitioners who are not considering incrementalizability. Users write correlated subqueries, nested aggregations, and multi-way joins that are technically incrementalizable but need significant rewriting before compilation into an efficient incremental operator graph.

Our query rewriter handles eighty percent of these cases automatically. It identifies patterns with known incremental equivalents and rewrites them before compilation. The remaining twenty percent required users to rewrite their SQL manually. We built an error messaging layer that clearly explains what the rewriter could not handle and proposes alternative phrasings. This saved more user time than the rewriter itself because the suggestions were specific enough to use without understanding the theory behind incremental computation.

SQL
-- Correlated subquery pattern: common in analyst-written features
SELECT
  user_id,
  (
    SELECT COUNT(*)
    FROM transactions t2
    WHERE t2.user_id = t1.user_id
      AND t2.ts >= t1.ts - INTERVAL '1 hour'
  ) AS velocity_1h
FROM transactions t1;

-- Rewritten to a join for O(1) incremental cost per new event
SELECT
  t1.user_id,
  COUNT(t2.txn_id) AS velocity_1h
FROM transactions t1
LEFT JOIN transactions t2
  ON t2.user_id = t1.user_id
  AND t2.ts BETWEEN t1.ts - INTERVAL '1 hour' AND t1.ts
GROUP BY t1.user_id, t1.txn_id;

The incremental execution cost for correlated subqueries is quadratic in the worst case. We added a query rewriter that converts the most common correlated subquery shapes to join-based equivalents automatically. That resolved around eighty percent of cases in beta. The remaining twenty percent, mostly non-equijoin correlations and non-linear temporal predicates, required query rewrites from the user. This was more friction than we expected. We are still working on expanding the rewriter's coverage.

State management turned out to be the operational bottleneck

Feature pipelines for fraud and recommendation typically maintain months of behavioral history per entity. An incremental engine that holds all of that state in memory runs into practical limits on workloads with tens of millions of distinct entities. During beta, the most common operational issue we saw was not correctness failures but memory pressure on pipelines with high entity cardinality and long retention windows.

We spent substantial time on spill-to-disk strategies that let the engine maintain correctness while keeping the hot working set in memory. The basic idea is that entities with no recent activity can have their state serialized to disk and evicted from memory, with a read-back on the next event for that entity. The tricky part is doing this without introducing correctness issues for windowed aggregations when state is recalled.

The lesson is that incremental computation shifts the resource bottleneck from CPU to memory. Batch recompute is expensive in CPU cycles. Incremental computation is cheap in CPU but requires memory proportional to the state that needs to be maintained across ticks. For pipelines with unbounded entity cardinality, you need explicit state size controls: time-bounded windows that allow old state to be freed, LRU eviction for cold entities, or explicit compaction steps for long-tail keys.

Users wanted operational tooling more than SQL features

Going into beta, we assumed the primary feedback would be about SQL compatibility. Which standard SQL constructs did we support, which were missing, and where did our behavior diverge from PostgreSQL semantics. That feedback came in, but it was not the dominant theme.

The dominant theme was operational experience. How do you debug a pipeline that is producing unexpected output? How do you handle a source system that replays events after a backpressure incident? How do you migrate a pipeline to a new SQL version without dropping state? How do you tell whether a lag in feature availability is caused by the compute layer or the Kafka consumer?

These are the same questions that come up in any production streaming deployment, whether Flink or Kafka Streams or Spark Structured Streaming. What is different with Feldera is that pipelines are expressed as SQL, so the debugging experience can be built on familiar tooling. You can query the engine's current output state with a SQL SELECT and compare it to what you expected. We invested heavily in this observability layer based on beta feedback: query-level output inspection, per-operator throughput metrics, and a replayable event log for diagnosing unexpected state transitions.

Exactly-once semantics are often not what users actually need

One of the design decisions we revisited during beta was our approach to exactly-once processing guarantees. Feldera's engine uses a transactional commit protocol to ensure that each input event is reflected exactly once in the output, even across restarts and failures. This is the right guarantee for certain workloads, particularly fraud scores where double-counting a transaction could produce an artificially elevated velocity feature.

For recommendation features, the cost of maintaining exactly-once guarantees, checkpoint overhead and recovery latency, was frequently higher than the cost of tolerating occasional duplicate processing. A session affinity feature that double-counts one page view in a thousand is not meaningfully less accurate than a perfectly deduplicated one. We added a processing mode that relaxes the guarantee to at-least-once, with significant checkpoint overhead reduction. It is not appropriate for all workloads, but for latency-sensitive recommendation features it is often the right tradeoff.

What we did not anticipate: SQL as the onboarding bottleneck

We expected SQL to lower the barrier to entry. In practice, writing correct incremental SQL requires understanding which constructs the engine handles efficiently and which do not incrementalize well. That knowledge is not intuitive from the SQL syntax itself. A query that looks simple, like a multi-window aggregation over a self-join, can have surprising state growth in an incremental execution model.

We have addressed part of this with static analysis warnings during query compilation: if the planner detects a pattern likely to produce large state, it surfaces a warning before the pipeline starts. But there is no substitute for documentation that explains the cost model clearly. We have been rewriting our docs to focus on the incremental execution model, not just the SQL syntax, and early feedback suggests this is the right investment.

The open beta was a useful forcing function for understanding which parts of our original design held up and which needed rethinking. The core bet, that incremental SQL is the right abstraction for streaming feature computation, held up well. The state management and operational tooling work was larger than we budgeted, which is about as honest as a post-beta reflection gets. More engineering capacity going into year two is going to operational quality and pipeline debuggability, less on expanding the SQL feature surface.

Continue reading

Incremental SQL: Computing Aggregations Without Reprocessing History Kafka to Feature Store: Bridging the Stream-Batch Gap The Case for Incremental Computation in Modern ML Stacks

Try Feldera

Build your first incremental pipeline today

Feldera is free to start. Connect your data source, write a SQL view, and watch features update in milliseconds as events arrive.

Read the quickstart Create free account