Jon Gjengset
Allowing materialized views to hold partial state and resolve missing records on demand eliminates ad hoc application caching while preventing the memory exhaustion typical of traditional stream processors.
AI-assisted summary of Thesis: Partial State in Dataflow-Based Materialized Views — original source. Check the original for full context.
Source published:
Key ideas
- Jon Gjengset introduces partial state in dataflow materialized views, allowing entries to be missing and reconstructed on demand to act as an automatic, transparent database cache. (0:00)
- Noria resolves missing view state via backward-propagating 'upqueries' that elicit normal forward delta responses, eliminating redundant backward-execution query engines. (15:52)
- Incongruent join deadlocks and staleness hazards caused by missing upstream state are safely resolved by pushing evictions downstream instead of stalling the pipeline on synchronous queries. (32:50)
- Evaluated against a synthetic Lobste.rs benchmark, partial-state Noria achieved ~7,500 pages/second—an 18x throughput increase over MySQL and a 67% improvement over fully stateful Noria while reducing memory usage by roughly two-thirds. (36:01)
Detailed notes
## Background This video was published on YouTube by Jon Gjengset on October 23, 2020. It captures his virtual PhD dissertation defense at MIT from the previous day, presenting his research on using partially stateful materialized views in the Noria database system to transparently cache read-heavy application workloads.
## TL;DR In read-heavy web applications, traditional relational databases waste significant compute re-executing identical queries, forcing engineers to build brittle, ad hoc caching layers. Jon Gjengset introduces partially stateful materialization in Noria, an architecture that treats materialized views as dynamic caches by deliberately allowing state to be missing or evicted. When cache misses occur, backward-propagating 'upqueries' reconstruct required data on demand through dataflow paths, while tricky edge cases like incongruent joins are resolved by downstream evictions rather than blocking data pipelines. Benchmarks using the Lobste.rs workload demonstrate that partial state achieves up to an 18x throughput increase over MySQL, uses two-thirds less memory than fully stateful views, and performs within a factor of two of an idealized Redis cache while retaining a SQL interface.
## Atmosphere & style The presentation carries the polished, scholarly weight of an MIT doctoral defense balanced by Gjengset's approachable, systems-hacker charisma. Following a warm, heartfelt introduction from his advisor Robert Morris, Gjengset delivers a lucid, fast-paced technical narrative that methodically unpacks complex concurrency hazards with dry wit and clarity. The session transitions from rigorous database theory and benchmark graphs to sincere personal vulnerability during the closing acknowledgments, evoking the relief and profound gratitude of culminating six years of intense academic labor.
## Bulletin - [00:00](https://www.youtube.com/watch?v=GctxvSPIfr8&t=0s) Jon Gjengset introduces partial state in dataflow materialized views, allowing entries to be missing and reconstructed on demand to act as an automatic, transparent database cache. - [15:52](https://www.youtube.com/watch?v=GctxvSPIfr8&t=952s) Noria resolves missing view state via backward-propagating 'upqueries' that elicit normal forward delta responses, eliminating redundant backward-execution query engines. - [32:50](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1970s) Incongruent join deadlocks and staleness hazards caused by missing upstream state are safely resolved by pushing evictions downstream instead of stalling the pipeline on synchronous queries. - [36:01](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2161s) Evaluated against a synthetic Lobste.rs benchmark, partial-state Noria achieved ~7,500 pages/second—an 18x throughput increase over MySQL and a 67% improvement over fully stateful Noria while reducing memory usage by roughly two-thirds.
## Key takeaways - [02:06](https://www.youtube.com/watch?v=GctxvSPIfr8&t=126s) Modern web applications suffer from a structural read-write asymmetry where read queries repeatedly execute expensive aggregations and joins over identical data. - [04:02](https://www.youtube.com/watch?v=GctxvSPIfr8&t=242s) Manual application caching (e.g., Redis or denormalization) introduces severe failure modes, including cache invalidation bugs, thundering herds, and complex eviction logic. - [07:05](https://www.youtube.com/watch?v=GctxvSPIfr8&t=425s) Materialized views maintain query results incrementally via deltas, but traditional systems hold full state and cater to low-frequency analytical workloads rather than high-throughput web reads. - [08:25](https://www.youtube.com/watch?v=GctxvSPIfr8&t=505s) Noria structures computation as a push-based dataflow graph where updates propagate along edges as positive and negative relational deltas. - [14:08](https://www.youtube.com/watch?v=GctxvSPIfr8&t=848s) Real-world web access patterns are heavily skewed, making the complete materialization of traditional views wasteful in terms of both memory and maintenance computation. - [15:52](https://www.youtube.com/watch?v=GctxvSPIfr8&t=952s) Parameterized view definitions allow Noria to explicitly identify missing entries and trigger targeted upqueries along existing dataflow edges on cache misses. - [15:52](https://www.youtube.com/watch?v=GctxvSPIfr8&t=952s) Upquery responses travel forward as regular dataflow deltas, unifying on-demand cache population with streaming view maintenance. - [20:07](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1207s) Partial state enables aggressive compute pruning: intermediate operators discard incoming delta updates for keys that are not currently materialized downstream. - [24:42](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1482s) Noria relies on eventual consistency to decouple fast reads from write pipelines, avoiding the heavy coordination overhead present in traditional stream processors. - [25:48](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1548s) Partially stateful graphs introduce correctness risks such as concurrent upquery-delta races and incongruent join staleness. - [28:01](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1681s) Incongruent joins—where the join key differs from the downstream query key—can deadlock streaming pipelines if forced to block on missing attribute lookups. - [32:50](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1970s) Noria escapes incongruent join deadlocks by emitting downstream evictions, letting future reads safely re-materialize data on demand via standard upquery pathways. - [36:01](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2161s) On a synthetic Lobste.rs benchmark, partially stateful Noria sustained ~7,500 pages per second, outperforming denormalized MySQL (400 pages/sec) and fully stateful Noria (4,500 pages/sec). - [36:01](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2161s) Partial materialization lowered Noria's active memory footprint from over 100 GB down to approximately 34 GB under comparable benchmark workloads. - [39:13](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2353s) In an idealized single-query benchmark, Noria achieved read throughput within a factor of two of a theoretical 16-core Redis ceiling while preserving full SQL abstractions. - [41:18](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2478s) Future research directions include supporting range queries and streaming windows, integrating Noria as an external cache in front of existing relational databases, and utilizing partial state for fast node recovery.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=GctxvSPIfr8&t=0s) Advisor Introduction and Thesis Overview
Robert Morris introduces Jon Gjengset for his virtual doctoral dissertation defense at MIT, praising Jon's research vision for how high-traffic web applications should manage their data. Beyond his technical contributions, Morris highlights Jon's active involvement in the broader community—ranging from his infinite patience as a teaching assistant, to organizing events for graduate students, to live-streaming systems programming tutorials. Noting that Jon will be deeply missed across MIT if the committee approves his graduation, Morris remarks:
> "Jon's sure to be valuable and valued whatever he sets his hand to. And assuming his committee lets him go, many people at MIT are going to miss him."
Jon takes over and frames his talk on *Partial State in Dataflow-Based Materialized Views*, setting the ambitious goal of condensing roughly six years of PhD work into 45 minutes so that attendees understand what the title means and why the work matters. He acknowledges his supervisory committee—Robert Morris, Frans Kaashoek, Sam Madden, and Malte Schwarzkopf—who worked alongside him to ensure the research was both rigorous and practically valuable. Addressing the foundational question of why he is giving the talk, Jon states:
> "I am here because I want to make databases better."
[02:06](https://www.youtube.com/watch?v=GctxvSPIfr8&t=126s) The Read-Heavy Web and the Problem with Traditional Databases
To explain how databases can be improved, Jon reviews basic relational database mechanics using a toy schema with two tables: `stories` and `votes`. Applications interact with the database by submitting read queries—operations that aggregate, filter, or join tables together to produce data the application cares about—or by submitting writes like inserts, updates, and deletes directly into the underlying tables. Although SQL technically categorizes all statements as queries, Jon clarifies that throughout the talk, "queries" refers strictly to read operations like `SELECT`, whereas inserts, updates, and deletes are termed "writes" or "updates."
Jon points out a structural imbalance in database architectures: a simple write modifies a single table directly, whereas a read query often triggers substantial computational work across multiple relational operators. This mismatch is especially painful for modern web applications, which are overwhelmingly read-heavy. Because applications execute thousands of reads for every write, the most frequent operation is also the most expensive. Applications routinely submit identical or near-identical queries over and over again, forcing the database engine to waste compute cycles repeatedly calculating the same results.
```mermaid flowchart TD accTitle: Traditional Database Read vs Write Asymmetry accDescr: Visual comparison showing writes executing directly on tables while reads pass through multiple relational operators.
subgraph Reads ["Read Query (Expensive)"] Q["SELECT Query"] --> AGG["Aggregation (Count)"] AGG --> JOIN["Join Engine"] end
subgraph Storage ["Base Tables"] JOIN --> T1["stories Table"] JOIN --> T2["votes Table"] W["INSERT / UPDATE / DELETE"] -->|"Direct Write"| T2 end ```
[04:02](https://www.youtube.com/watch?v=GctxvSPIfr8&t=242s) The Pitfalls of Ad Hoc Application Caching
A standard remedy to reduce database read pressure is placing an external cache, such as an in-memory key-value store, in front of the database. When an application queries the system, it first inspects the cache; on a hit, it returns immediately with low latency, hitting the backend database only on a cache miss. Other developers achieve similar benefits via denormalization—such as storing a precomputed vote tally directly within the `stories` table. However, Jon emphasizes that while caching sounds straightforward in theory, delegating cache management to application code creates immense operational complexity.
Whenever underlying data changes—such as when a user casts a new vote—the cached count becomes stale. The specific application path executing that write is now obligated to invalidate or refresh that cache entry. Furthermore, coordinating cache misses introduces severe failure modes. If 100,000 concurrent users request a story that is not cached, naive application logic can trigger a thundering herd, where all 100,000 requests swarm the backend database simultaneously to compute the identical query. Developers must also orchestrate which worker writes the computed result back into the cache and determine eviction policies to purge old or unpopular entries before memory runs out. Jon summarizes the burden placed on software engineers:
> "And hopefully this image gives you an idea of just all the stuff that needs to be figured out by the application developers in order to make databases serve these kinds of read-heavy workloads."
Rather than forcing developers to invent custom, brittle caching layers, Jon's research focuses on automatic database caching: moving all eviction, miss handling, and invalidation logic out of the application and embedding it transparently inside the database itself.
[07:05](https://www.youtube.com/watch?v=GctxvSPIfr8&t=425s) Materialized Views and Incremental Maintenance
To explain how automatic caching can be implemented, Jon deconstructs his thesis title from right to left, beginning with *materialized views*. First introduced by database researchers in the early 1980s, a materialized view simply executes a query and stores the evaluated result for future retrieval, behaving functionally like a cache. The central engineering challenge is view maintenance: how the stored materialization stays accurate when underlying base tables change.
Ideally, materialized view maintenance must be incremental. If a story already has 100,000 votes and a single new vote arrives, the database should not scan all 100,001 rows to recompute the count from scratch. Instead, it should simply take the existing count and increment it by one. Systems also face an architectural design choice between eager maintenance on write (proactively updating the view as soon as a mutation occurs) or lazy maintenance on demand (deferring the update until a subsequent read query requests the data).
- Materialized views date back to the early 1980s in database literature. - Incremental maintenance computes deltas instead of re-executing entire queries from scratch. - Systems choose between write-time proactive maintenance and read-time lazy maintenance.
[08:25](https://www.youtube.com/watch?v=GctxvSPIfr8&t=505s) Dataflow-Based Computation and the Noria Model
Jon moves to the next phrase in the title: *dataflow-based*. Within computer systems, dataflow represents an architectural style where data moves directly to compute, rather than having compute routines reach out and pull data from storage tables. It functions as a push-based processing model where underlying database modifications propagate through a directed acyclic graph of relational operators, including joins, aggregations, and filters.
In this dataflow graph, edges represent data dependencies. A join operator depends on its incoming input streams, and an aggregation depends on the upstream data being grouped. The messages exchanged between operators along these edges are *deltas*. A delta is structured like a full database row with typed columns, accompanied by an explicit positive (`+`) or negative (`-`) sign. A positive delta signals the arrival of a new or updated row that should be added to the result, whereas a negative delta represents a retraction or deletion of a previously produced row. Jon explains that this dataflow model serves as the foundation for Noria, an eventually consistent materialized view system.
Jon presents a concrete example of how Noria transforms a SQL query into an incremental dataflow graph. Using a query that constructs a materialized view named `StoryWithVC` (Story With Vote Count), the query joins the `stories` and `votes` tables on story ID and aggregates the total votes per story using `GROUP BY stories.id`. As changes arrive at the base tables, Noria pushes delta records through the aggregation and join operators, keeping the final view updated in real time.
[11:02](https://www.youtube.com/watch?v=GctxvSPIfr8&t=662s) Incremental Maintenance and Delta Propagation in Noria
When mapping a relational query into a dataflow graph, the individual relational operators appear directly as graph nodes. For example, an aggregation such as a count on votes grouped by `story_id` is represented by an aggregation operator, while the combination of stories and votes is handled by a join operator. In a naive translation, one might expect the join to sit directly between the base `stories` and `votes` tables, but Noria’s query optimizer can reorder these operations for efficiency. As Jon Gjengset points out regarding the aggregation running before the join: “Noria has decided that it's going to do the aggregation first for optimization purposes.” When an application reads from the final materialized view at the bottom of the graph—named `StoryWithVC` (stories with vote count)—it issues a parameterized query such as `SELECT * FROM StoryWithVC WHERE id =?`, using the runtime parameter as a lookup key to retrieve only the specific record it needs.
Whenever underlying base tables mutate, Noria incrementally updates the downstream materialized view via streaming deltas. If a new vote arrives for a given story (say, story 7), the change is injected into the dataflow graph at the `votes` table node and flows along the graph edges toward the view. As deltas pass through intermediate operators, they are transformed. The vote count operator converts the incoming `+1` increment into a negative delta for the old count alongside a positive delta for the new count: `- (7, 42)` and `+ (7, 43)`. Jon explains that one can think of this emitted message as communicating: “It used to be that the count for story 7 was 42. Forget that, it is now 43.” When these deltas arrive at the join operator, the join reads the `stories` table to stitch on the missing story columns, producing combined output deltas that update the `StoryWithVC` materialized view in place so subsequent application reads immediately observe the new count.
- Base table changes enter the dataflow graph as delta records at table root nodes. - Aggregation operators replace raw increments with explicit retraction and insertion pairs (e.g., subtracting old count 42 and adding new count 43 for key 7). - Join operators enrich streaming deltas by looking up required attribute columns from sibling table state before forwarding updates downstream.
---
[14:08](https://www.youtube.com/watch?v=GctxvSPIfr8&t=848s) The Case for Partial State: Learning to Forget
Traditional materialized view systems compute and maintain complete answers for every possible key, an approach that wastes massive amounts of memory and CPU cycles on data that may never be accessed again. Jon introduces the core thesis contribution—partial state—summarizing the fundamental philosophy as “learning to forget.” In typical real-world workloads, access distributions are heavily skewed: unpopular or ancient stories linger indefinitely in view memory without serving read traffic. Jon highlights this inefficiency directly: “The observation here is that the chances are most entries in any given view are not accessed.” Furthermore, repeatedly recalculating and maintaining up-to-date counts for stories no reader cares about squanders processing capacity that could otherwise serve active queries.
Under memory pressure, a system must be able to retain only the most popular, frequently accessed subset of records rather than keeping the entire universe of data hot. Standard materialized views offer no mechanism to evict stale entries and re-materialize them on demand because views are defined to materialize full relations. To resolve this tension, the dissertation articulates three principal contributions:
1. **The Notion of Missing State:** Explicitly allowing materialized views and intermediate operators to mark specific entries as absent, avoiding allocating memory to unread keys. 2. **The Mechanism of Upqueries:** An on-demand recursive query mechanism that traverses backward through the dataflow graph to backfill missing state over existing dataflow paths. 3. **Implementation and Evaluation in Noria:** Realizing partially stateful materialized views within a high-performance streaming database system to prove latency, throughput, and memory benefits.
---
[15:52](https://www.youtube.com/watch?v=GctxvSPIfr8&t=952s) Parameterized Views and Upquery Resolution
To support partial state, the database must know which key the application uses to partition and query the view. In traditional relational systems, the view definition and the application’s query over that view are decoupled; the view definition does not reveal what specific key the application will query at runtime. Jon resolves this by folding the query parameter directly into the view definition itself (e.g., `WHERE id =?`). Because the parameter is baked into the view schema, Noria knows exactly which key defines presence or absence. When an application queries `StoryWithVC` for story 7, Noria inspects the materialized view index. If story 7 is not present, it is represented as a missing entry—visualized as a hollow box.
```mermaid flowchart TD accTitle: Upquery and Delta Flow in Noria accDescr: An upquery travels backward from missing view state to stateful ancestors, which respond via regular forward dataflow.
subgraph Base["Base Tables"] Stories["stories Table"] Votes["votes Table"] end
subgraph Pipeline["Dataflow Pipeline"] Count["Count Operator<br/>(holds partial aggregation state)"] Join["Join Operator<br/>(stateless)"] end
subgraph View["Client Interface"] MV["StoryWithVC View<br/>(Missing entry: Story 7)"] App["Application Client"] end
App -->|"1. Query (id = 7)"| MV MV -.->|"2. Upquery(7)"| Join Join -.->|"3. Forward Upquery(7)"| Count Count -->|"4. Delta (+43 for 7)"| Join Stories -.->|"5. Lookup attributes"| Join Join -->|"6. Enriched Delta"| MV MV -->|"7. Query Response"| App ```
To resolve a cache miss, the view initiates an “upquery” up the dataflow graph, requesting that upstream operators re-transmit the historical deltas necessary to construct the state for that key. Jon describes the intuition behind this exchange: “You can think of it sort of as the downstream saying: ‘tell me about 7 again, because I forgot all about it’.” If an intermediate operator like the join is stateless, the upquery recurses further upstream. In this scenario, the join forwards the upquery to the count operator. Because the count operator maintains its own internal materialized view of past counts to calculate deltas, it can satisfy the upquery directly if story 7 is present in its internal state, avoiding recursing all the way back to the raw `votes` table.
Crucially, upqueries do not require dedicated backward-processing execution engines. When an upstream operator satisfies an upquery, it emits a standard delta message downstream over the normal forward dataflow graph. The message represents the current state of the requested key and is processed by the join exactly like any live update: the join looks up story 7 in the `stories` table, stitches the title and metadata to the vote count, and pushes the consolidated record to the materialized view. The materialized view fills in its missing entry and immediately returns the result to the client.
- View definitions incorporate parameter placeholders (`?`), tying partial indexing directly to runtime query access patterns. - Upqueries travel upward through graph edges until encountering an operator holding sufficient state to reconstruct the key. - Upquery responses travel forward along normal dataflow edges as standard dataflow deltas, avoiding redundant processing pipelines. - Once populated, subsequent reads for the same key hit the materialized view directly with zero upstream coordination.
---
[20:07](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1207s) Cache Eviction and Avoiding Unnecessary Computation
Because Noria can materialize state on demand, it can also safely evict state whenever memory is needed or entries fall out of use. If story 7 ceases to receive traffic, the system can reclaim memory by evicting story 7 from the downstream materialized view, from internal aggregation operator caches, or from both. Eviction transforms a populated entry back into missing state without breaking correctness: if a client ever requests story 7 months or years later, a new upquery will seamlessly re-materialize the row.
Beyond memory reduction, partial state yields dramatic compute savings by pruning updates for cold data. If the author of story 7 updates the story's title long after story 7 has been evicted from the views, the update delta propagates to the join operator. When the join attempts to look up the corresponding vote count in the aggregation operator's state, it discovers that story 7 is missing. Because the count state is missing, Noria knows that no downstream view is currently materializing story 7. The join safely drops the delta entirely instead of completing the join and computing updates for an unread view. Jon summarizes the power of this pruning rule: “And therefore there is nothing to update — there is no state for 7 downstream — and so don't need to update it and can discard this update entirely.”
- Inactive keys can be evicted at any layer of the dataflow graph to free memory for hot items. - Upstream delta updates that encounter missing sibling state in intermediate joins are discarded immediately, eliminating wasted CPU cycles on cold data. - The system achieves the dual benefits of dynamic caching and incremental view maintenance without requiring custom application-level cache invalidation code.
[22:07](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1327s) Traditional Materialized Views and Automated Caching
Materialized view systems have traditionally targeted workloads fundamentally different from high-throughput, read-heavy web applications. In typical deployments, materialized views exist to serve occasional queries run by human analysts. Because an analyst might want to run an intensely complex computation only periodically, running that query directly on raw data every time would take far too long. A materialized view maintains the result continuously behind the scenes so that when the analyst sits down, the report opens immediately. Because writes are frequent and read queries are rare, database research has overwhelmingly prioritized maintenance efficiency over read latency. It is completely acceptable in those systems if a read query performs a moderate amount of work at retrieval time, provided that work remains vastly smaller than executing the entire query from scratch.
Consequently, traditional systems rarely incorporate mechanisms for on-demand query evaluation or dynamic cache eviction. Their view definitions are usually pre-compiled into static query plans or standalone programs. As Gjengset observes: “And for similar reasons these systems rarely have support for eviction, because it's not really needed; the analyst's query is what it is.” There is no designated subset of an analytical view that takes precedence over any other portion of the dataset.
Automated caching systems developed in industry and academia similarly fall short of providing a drop-in database solution. Industrial caching systems are notoriously tailored to the hyper-specific architectural needs of the companies that invent them. A developer cannot easily take an internal infrastructure tool built by a large tech company and integrate it into a standard Ruby on Rails application. Furthermore, these systems rely primarily on cache invalidation rather than incremental maintenance: when an underlying row changes, entries are evicted instead of updated in place. That invalidation pattern leads to frequent cache misses and forces unnecessary trips back to the primary database. Most of these caching tools also operate above the storage layer, requiring tight coupling with a language-specific Object-Relational Mapping (ORM) framework rather than exposing an ergonomic, standard SQL interface featuring parameterized prepared statements.
- Traditional materialized views prioritize write-maintenance performance over read speed because reads are infrequent. - Analytical materialized views lack eviction mechanisms because all rows in the view are assumed to be equally relevant. - Industry automated caches rely on cache invalidation rather than incremental updates, leading to higher backend miss rates. - Caching layers typically require application-level ORM integrations rather than exposing a drop-in SQL view abstraction.
---
[24:42](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1482s) Stream Processing Systems and Consistency Trade-Offs
Dataflow and stream processing platforms share many design assumptions with traditional materialized views, concentrating heavily on ingestion throughput across sustained data pipelines. These stream engines tend to enforce strong consistency across their pipelines. However, guaranteeing strong consistency introduces a steep cost in read latency: read operations frequently have to coordinate directly with the write dataflow, in many cases traversing the exact same processing path as mutating writes to ensure uncorrupted views of the world.
Noria takes an alternative approach by taking advantage of eventual consistency. Because web applications tolerate brief moments of staleness, Noria bypasses strict stream-write coordination, serving instantaneous reads directly from its partially materialized views. Furthermore, existing stream processing platforms provide very limited support for on-demand computations or partial evictions. Once a stream query is initialized, it runs continuously across all data; evicting a specific subset of state from an intermediate operator requires tearing down and restarting the entire processing topology.
> “Whereas with Noria we can leverage the eventual consistency to give much faster results by leveraging the fact that the results are allowed to be stale.” — Jon Gjengset
---
[25:48](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1548s) Practical Challenges: Concurrency, Missing State, and Exactly-Once Updates
While the high-level concept of partial materialization is conceptually straightforward, executing it reliably in a production system reveals significant technical hazards. Gjengset notes with dry humor that while these hurdles complicate matters, “if it were, this thesis wouldn't be very valuable, and so I'm kind of glad that there are more challenges.” The fundamental requirement is ensuring that all data mutations to underlying base tables take effect exactly once in downstream views. A row inserted into a base table must neither vanish silently nor be applied multiple times to duplicate records. While traditional databases avoid this naturally by traversing indexes and physical disk pages once per query, partially stateful dataflow engines face continuous race conditions.
```mermaid flowchart TD accTitle: Concurrency Hazard Between Upqueries and Dataflow Updates accDescr: Diagram showing how an on-demand upquery and an in-flight delta update can conflict over the same record ID.
subgraph DataflowPaths["Concurrent Paths for Story 7"] UQ["Upquery: Fetches historical state for Story 7"] DF["Dataflow Update: Streaming delta for Story 7"] end
JoinOp["Join Operator / Downstream View"]
UQ -->|"Summarizes past state"| JoinOp DF -->|"Concurrently mutates state"| JoinOp
JoinOp --> Res["Risk: Exactly-once violations or permanent staleness"] ```
These hazards emerge from two distinct properties of the architecture:
1. **Concurrent upqueries and delta updates:** An upquery represents a snapshot summary of past historical state and preceding deltas for a specific key (such as `story 7`). However, an upquery may run concurrently with a live update flowing down through the dataflow graph for that exact same key. If the system fails to coordinate these overlapping operations, mutations may be double-counted or overwritten, violating exactly-once semantics. 2. **Premature update discarding:** In a partially stateful graph, operators discard updates when they encounter missing state in siblings to prevent storing unnecessary records. However, if an operator misidentifies missing state and discards an update that downstream views actually rely upon, those downstream materialized views become permanently stale and never heal.
The thesis catalogs numerous consistency hazards, with the presentation focusing specifically on the intricacies of incongruent join evictions.
---
[28:01](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1681s) The Mechanics of Incongruent Joins
The term "incongruent join" describes a join operator where the downstream query key differs from the internal join key. In a sample web scenario, an application maintains a `stories` base table with an `author_id` column, alongside a `users` table storing user metadata such as the author's display name. To present an article, the UI queries a view called `StoriesWithAuthor`, which joins `stories` and `users` to replace the raw numerical `author_id` with a textual `name`.
```mermaid flowchart LR accTitle: Incongruent Join Dataflow Architecture accDescr: Shows the stories table and users table feeding into a join operator, which outputs to a materialized view keyed differently.
subgraph Inputs["Upstream Tables"] S["Stories Table\nKey: story_id\nValue: author_id"] U["Users Table\nKey: author_id\nValue: name"] end
J["Join Operator\nJoin Key: author_id\nQuery Key: story_id"] MV["Materialized View\nStoriesWithAuthor\nIndexed on story_id"]
S -->|"Deltas / Upquery responses"| J U -->|"Author metadata"| J J -->|"Combined rows (id, author name)"| MV ```
In this query, the view is indexed and fetched by `story_id` (the query key), but the join operator correlates rows using `author_id` (the join key). When state is fully populated, handling an upquery is clean:
1. A client queries `StoriesWithAuthor` for `story 7`. 2. The read misses, emitting an upquery upstream to the stateless join operator. 3. The join passes the upquery upstream to the `stories` table. 4. The `stories` table resolves `story 7`, noting its `author_id` is `42`, and pushes a response delta downstream into the join. 5. The join uses `42` to query the `users` table, successfully retrieving the author's name (`Lena`). 6. The join stitches the story attributes and the author name together, forwarding the fully populated row downstream to fill the empty entry in the materialized view.
> “What makes this join incongruent is the fact that query key is different from the join key. The query key here is the story id, whereas the join key is the author id of the story.” — Jon Gjengset
---
[30:03](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1803s) Hazards of Incongruent Join Updates and Missing State
The system encounters severe correctness hazards when an incongruent join processes streaming updates alongside missing state. When an update alters a story's author, the base table emits two corresponding deltas into the dataflow graph: a negative delta removing the story under the old author ID, and a positive delta inserting the story under the new author ID.
```mermaid flowchart TD accTitle: The Incongruent Join Dilemma on Missing State accDescr: Flowchart demonstrating the dilemma when a positive delta misses in the users table during an author update.
Delta["Author Update for Story 7\n- (Story 7, Author 42)\n+ (Story 7, Author 43)"] JoinOp["Incongruent Join Operator"]
LookupOld["Lookup Author 42: Hits ('Lena')"] LookupNew["Lookup Author 43: Misses in Users state!"]
Delta --> JoinOp JoinOp --> LookupOld JoinOp --> LookupNew
LookupNew --> BadOptions{"Available Fallback Actions"} BadOptions -->|"Drop positive, send negative"| EmptyState["Materialized view has no rows for Story 7"] BadOptions -->|"Drop entire update"| StaleState["Materialized view permanently displays old author Lena"] BadOptions -->|"Block dataflow on upquery"| Stall["Dataflow pipeline stalls behind expensive view queries"] ```
When these two deltas reach the join operator: - The join operator resolves author `42` in the `users` table, successfully matching `Lena` and preparing the negative delta for output. - The join operator attempts to look up author `43` in the `users` table to complete the positive delta, but the lookup misses.
A cache miss can happen if base tables manage memory by keeping cold rows on disk, or more commonly, if `users` is not a raw base table at all, but rather an upstream view with partial state and its own active evictions. Regardless of the underlying cause, the join operator is trapped with incomplete data: - **Forwarding only the negative delta:** If the join emits the negative delta and drops the unresolvable positive delta, the downstream materialized view purges `story 7` entirely. Any subsequent read for `story 7` returns no rows, falsely indicating the story no longer exists. - **Dropping the update entirely:** If the join silently discards both deltas, the materialized view retains the old author `Lena`. Any subsequent lookup sees out-of-date information, rendering the cached entry permanently stale. - **Pausing the pipeline for an on-demand upquery:** The obvious instinct is to trigger an immediate upquery from the join to fetch user `43` before processing the delta. However, blocking the join operator stalls the entire streaming pipeline while waiting for an upstream view query to complete, degrading write performance across the whole database.
> “We cannot forward just the negative, and just sort of discard the positive, because if we did, the downstream materialized view would now have no rows for story 7. And any subsequent query for story 7 would get no results, which is obviously not correct.” — Jon Gjengset
[32:50](https://www.youtube.com/watch?v=GctxvSPIfr8&t=1970s) Breaking Deadlocks with Downstream Evictions
```mermaid flowchart TD accTitle: Downstream Eviction Strategy accDescr: Diagram showing how an incongruent join handles missing state by pushing an eviction downstream instead of issuing a blocking upquery.
subgraph Dataflow ["Dataflow Engine"] Join["Incongruent Join Operator"] MatView["Downstream Materialized View"] end
Update["Incoming Update<br/>(e.g., Story 7)"] --> Join Join -- "Cannot resolve update immediately<br/>(Emits Eviction)" --> MatView MatView -- "Evicts Record 7" --> State["State Marked Missing"] ClientReq["Later Read Request (Story 7)"] --> MatView MatView -. "Triggers normal Upquery".-> Join ```
Issuing an upquery directly from an active join operator creates severe operational risks. If a join sits beneath a massive dataflow graph, resolving an upquery upward can take a very long time, during which the join is completely blocked from processing further updates. Gjengset explains that the situation is actually far worse: in certain configurations, circular dependencies cause absolute deadlocks. “Where the join can't process more updates until the upquery finishes, but the upquery cannot finish until the join processes more updates. And now we're stuck.” Processing subsequent updates out of order is impossible because later updates may depend directly on the update currently being stalled.
Partial state itself provides the elegant escape hatch from this conundrum: evictions. Rather than blocking the dataflow to resolve unknown fields (such as identifying the author for an updated story), the join emits an eviction downstream. By evicting story 7 from the downstream materialized view, the database simply forgets the state. When an application subsequently issues a read query for story 7, the missing entry triggers the standard upquery flow on demand, which is already designed to operate safely without deadlocking. Furthermore, Noria statically detects incongruent joins and selectively issues evictions only when strictly necessary, rather than penalizing every join in the system.
[34:26](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2066s) Evaluation Setup: Benchmarking Against Lobste.rs
To prove whether partial state provides real-world value without crushing performance under internal mechanism overhead, Gjengset evaluated Noria against Lobste.rs, an open-source, Hacker News–style news aggregator. Lobste.rs was selected for two primary reasons: its codebase is open-source (making all queries issued across distinct web pages transparent), and its traffic statistics are publicly available. These statistics capture aggregate requests per second, page distributions, active users, and story popularity.
Using these statistics, Gjengset built a workload generator that generates synthetic Lobste.rs traffic, allowing artificial load modulation to determine when systems reach their breaking point. The generator includes a pluggable backend compatible with both MySQL and Noria, and crucially bypasses the Ruby on Rails application tier. This isolation ensures the benchmarks evaluate pure database backend performance rather than the overhead of front-end scripting runtimes.
- **Workload:** Synthetic Lobste.rs interactions (story submissions, upvotes, comments, front-page views). - **Target environment:** 16-core Amazon EC2 instances with 128 GB of RAM. - **Metric evaluated:** Maximum sustainable throughput in pages per second before core saturation or latency spikes.
[36:01](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2161s) Throughput Benchmarks: MySQL vs. Stateful vs. Partial Noria
```mermaid flowchart LR accTitle: Throughput Comparison Across Configurations accDescr: Bar-style chart comparison of system throughput across MySQL, Noria without partial state, and Noria with partial state.
subgraph Throughput ["Throughput (Pages / Second)"] direction TB M["MySQL (Denormalized): ~400"] NW["Noria without Partial State: ~4,500 (OOM Bottleneck)"] NP["Noria with Partial State: ~7,500 (18x MySQL)"] end
M -->|11.25x| NW NW -->|1.67x| NP ```
On a 16-core EC2 instance, MySQL topped out at roughly 400 pages per second before saturating all CPU cores and suffering latency spikes. Gjengset emphasized that this baseline reflects an already heavily optimized, denormalized schema where the Lobste.rs developers had deliberately made story "hotness" (vote score) an explicit column on the stories table to dodge real-time joins and aggregations. “If you don't include this kind of manual denormalization the throughput for MySQL… it can't even keep up with a single page request per second.”
Standard Noria (without partial state) improved performance by an order of magnitude, sustaining roughly 4,500 pages per second due to precomputed materialized views converting complex joins into simple key-value lookups. However, standard Noria hit a hard memory wall: it exhausted the entire 128 GB of RAM. Because memory usage increases continuously over time in fully stateful dataflow, it could not sustain this load over longer runs, despite internal optimizations to materialize only actively queried views.
Noria equipped with partial state achieved roughly 7,500 pages per second—a 67% improvement over fully stateful Noria and an 18x leap over MySQL. Rather than hitting a CPU or memory bottleneck, partial Noria’s performance limit was dictated by an update-heavy dataflow branch processing upqueries. In memory consumption tests run just below standard Noria's failure threshold: - Noria without partial state consumed over 100 GB of memory. - Noria with partial state consumed approximately one-third of that footprint (~34 GB).
[39:13](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2353s) Evaluating Against an Idealized Redis Cache
Anticipating the pragmatic objection that production architectures deploy dedicated key-value caches rather than plain relational databases, Gjengset tested Noria against Redis. Because real-world cache implementations vary wildly, he constructed an idealized, best-case caching benchmark: a single query (stories combined with vote counts), almost all reads, zero misses, zero evictions, and pure single-key lookups.
Under this idealized workload, single-threaded Redis processed slightly below 1,000,000 requests per second on a single core. To construct a fair hardware comparison against multi-core Noria, Gjengset multiplied this figure by 16 to represent a theoretical upper bound of an optimally scaled, multi-core Redis deployment. When Noria ran the identical workload, it scaled reads across available cores and performed within a factor of two of this theoretical Redis ceiling. Gjengset highlighted that Noria achieves this competitive performance while eliminating manual application caching entirely: “Noria provides SQL, automatic maintenance of this cache, and it doesn't have these requirements of everything has to be implemented in the application logic.”
[41:18](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2478s) Future Work and Acknowledgments
Reflecting on the limitations of the current implementation, Gjengset outlined key directions for future research: - **Expanded query primitives:** Noria currently lacks native support for range queries, cursors, and time-windowed streaming operators. - **Upstream database integration:** Adapting Noria to sit directly in front of external sources of truth (such as an existing PostgreSQL or MySQL instance) rather than acting as the primary storage layer. - **End-to-end reactive views:** Extending dataflow streams beyond server-side materialized views directly into client-side devices, enabling live UI updates via reactive streams. - **Fault tolerance via partial state:** Leveraging partial materialization for node recovery; if a dataflow node crashes, its lost records could simply be marked as missing and restored lazily via upqueries.
Concluding his presentation, Gjengset expressed gratitude to his academic committee and collaborators. He thanked his advisor Robert Morris and Frans Kaashoek for their relentless pressure to sharpen the dissertation's focus, noting: “I think initially I had these sort of hazy ideas for something that might be cool, and I think the two of them have really helped hone the story, hone the argument, for why this is useful and what the work should focus on.” He also credited post-doc Malte Schwarzkopf, emphasizing that without Malte's contributions, Noria would lack SQL support and query optimization and remain a bare dataflow runtime.
[44:07](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2647s) Committee, PDOS Group, and Personal Acknowledgments
Jon Gjengset continues his acknowledgments by recognizing the members of his dissertation committee, highlighting Sam Madden's deep database expertise. Gjengset notes how valuable that perspective was to draw upon, joking that database knowledge is “something that he has a lot more of us than us mere systems people.” He also expresses his gratitude to his colleagues in the Parallel and Distributed Operating Systems (PDOS) group at MIT. He credits the lab's collaborative culture, boundless curiosity, technical insight, and ongoing support with shaping him as a scholar, remarking: “And I don't think I would be here without having them surround me throughout the years.”
Turning to his personal life, Gjengset thanks his parents and step-parents for championing his interests from an early age, regardless of how esoteric or nerdy those pursuits appeared. He reflects warmly that their constant encouragement steadily nudged him along until he reached this milestone, giving what he readily acknowledges is a very geeky presentation that he hopes makes them proud. Finally, he shares an emotional thank-you to his girlfriend, Talia, praising her patience and understanding throughout the grueling phases of graduate school. As Gjengset admits: “I am amazed that she's tolerated me just locking myself in my room for hours on end working on this work, on the evaluation, on the thesis, on this presentation.” He concludes his acknowledgments by expressing his love and deep appreciation for her.
[45:24](https://www.youtube.com/watch?v=GctxvSPIfr8&t=2724s) Summary and Conclusion: Materialized Views as Transparent Caches
Gjengset brings the defense to a close by synthesizing the primary contributions of his thesis: enabling dataflow-based materialized views to function effectively as dynamic query caches. Traditional materialized views require holding full state across all materializations and intermediate nodes, making them prohibitively memory-intensive and inflexible for evolving workloads. By introducing partially stateful materialization, the system allows arbitrary state entries to be absent or evicted, resolving cache misses on demand through backward-propagating upqueries that traverse the dataflow graph.
This architecture enables Noria to provide transparent, automated caching for complex SQL queries directly within the database infrastructure. Application developers gain the high throughput and low read latency of specialized key-value caches without having to construct, maintain, or debug error-prone, ad hoc application-level caching logic. Gjengset summarizes the central value proposition in his concluding statement: “The resulting system provides automated caching for SQL queries, and reduces the need for complex, ad hoc caching logic.” He finishes by thanking the virtual audience and inviting questions from the committee and attendees.
```mermaid flowchart TD accTitle: Summary of Partially Materialized Views Architecture accDescr: Diagram showing how partial state and upqueries allow materialized views to act as an automated database cache. subgraph Client Layer Q["Read Query"] end subgraph Noria Materialized View Engine MV{"Partial State<br/>Materialized View"} UQ["Upquery<br/>(Demand-Driven)"] DF["Dataflow Operators<br/>(Partially Stateful)"] end subgraph Storage Layer Base["Base Tables"] end
Q --> MV MV -->|"Hit: Cached Result"| Q MV -->|"Miss: Missing Entry"| UQ UQ --> DF DF --> Base Base -->|"Forward Maintenance"| DF DF -->|"Populate View"| MV ```
- **Committee Perspective**: Sam Madden provided essential database domain knowledge to complement the research group's systems-focused approach. - **PDOS Culture**: The MIT Parallel and Distributed Operating Systems group's community and intellectual curiosity served as the foundation for the doctoral work. - **Core Takeaway**: Allowing missing state and filling it dynamically via upqueries lets materialized views behave like adaptive caches, reducing memory footprints and eliminating manual cache management in read-heavy applications.
## Notable quotes > [14:08](https://www.youtube.com/watch?v=GctxvSPIfr8&t=848s) The observation here is that the chances are most entries in any given view are not accessed. — Jon Gjengset
## Mentioned Jon Gjengset, Robert Morris, Frans Kaashoek, Sam Madden, Malte Schwarzkopf, Talia, MIT, PDOS, Noria, MySQL, Redis, PostgreSQL, Lobste.rs, Amazon EC2, Ruby on Rails
## Video description This is my PhD dissertation presentation, which I gave at MIT (virtually) on October 22nd, 2020. It was immediately followed by my thesis defense, which I passed subject to the typical bits and pieces of revisions the committee wanted to see.
The person who introduces me in the video is Robert Morris, my thesis advisor. The Q&A has been cut out, as I want to edit it a bit and mix in questions from the public presentation I gave on YouTube the day before. I will link the Q&A video here once it is out. You can find the slides at https://jon.thesquareplanet.com/slides/thesis.pdf and at https://docs.google.com/presentation/d/1w2PlmqUIeue8VcNhBqOC0V3GTKQs9zBccrhd9tfAang/edit?usp=sharing. The thesis is available at https://jon.thesquareplanet.com/papers/phd-thesis.pdf.
What follows is the thesis abstract:
This thesis proposes a practical database system that lowers latency and increases supported load for read-heavy applications by using incrementally-maintained materialized views to cache query results. As opposed to state-of-the-art materialized view systems, the presented system builds the cache on demand, and evicts cache entries in response to a shifting workload.
The enabling technique the thesis introduces is partially stateful materialization, which allows entries in materialized views to be missing. The thesis proposes upqueries as a mechanism to fill such missing state on demand using dataflow, and implements them in the materialized view system Noria. The thesis then discusses additional mechanisms needed to establish eventual consistency for partially stateful dataflow.
Noria with partial materialization saves application developers from implementing their own ad hoc caching mechanisms to speed up their database accesses. Instead, the caching is built into the database, and is transparent to the application. Experimental results suggest that the presented system increases supported application load by up to 20x over MySQL and performs similarly to an optimized key-value store cache. Partial state also reduces memory use by up to 2/3 compared to traditional materialized views.
[Source](https://www.youtube.com/watch?v=GctxvSPIfr8)