# What Rindle Is For

An honest account of Rindle Cloud — the problems it's shaped for, the ones it isn't, and where the edges are. Written for someone deciding whether to build on it, not to sell them on it.

> **Updated July 16, 2026.** The original version described Rindle Cloud's single write master in a
> way that could be read as *serialized write execution*. That is no longer accurate. The HCTree
> master admits multiple transactions concurrently and can execute disjoint work in parallel; it
> still commits one ordered replication log. This revision separates those two ideas throughout.

> **This is about Rindle Cloud, not the Rindle engine.** Everything below describes the fit profile of
> a *specific deployment topology*: one authoritative write-master (`rindle-replicator`) fanning an
> ordered change log out to many `rindled` read followers and client replicas. Rindle Cloud is
> the managed embodiment of that topology, and it's the lens this document uses.
>
> **`rindle` itself is just an engine** — an embeddable incremental-view-maintenance library. It
> imposes exactly *one* real constraint: changes must reach each instance of materialized state as a
> single, totally-ordered stream (IVM is defined over an ordered sequence of deltas — there has to be
> *a* sequencer). But that's an **ordering point, not serialized execution.** Rindle Cloud implements
> it as one durable HCTree write master per database: multiple transactions may be open and executing,
> while successful commits receive the single order every replica consumes. The client tier already
> writes **non-durably and optimistically** and later reconciles against that order. What the engine
> forbids is divergent, unsequenced change streams racing into the same materialized state — not
> concurrent transactions. Read this as the canonical worked example of deploying the engine, not the
> boundary of every topology the embeddable engine could support.

## The one-sentence version

Rindle Cloud is a **managed sync platform — built on the Rindle IVM engine — for sync-shaped,
collaborative applications with bounded live working sets**. It keeps each client's queries
continuously and incrementally up to date — and it is honest that **reads scale by adding followers,
while writes run concurrently inside one master and scale out by sharding.**

If that sentence describes your application, Rindle Cloud is likely a very good fit. If your bottleneck is
sustained write throughput beyond one logical master, it is not. More cores, engine threads, and
HCTree connections raise one master's ceiling; another master means another shard. The rest of this
document explains the boundary precisely enough that you can decide for yourself.

## What the engine actually does

At its core Rindle does one thing well: you register a query, you get a materialized view, and that
view is **maintained incrementally** as the underlying data changes. The cost of applying a change is
a function of *how much changed*, not *how much data exists*. A new row flowing into a 6-thousand-row
table and a 120-thousand-row table costs the same to push through a materialized query — single-digit
microseconds, flat as the dataset grows. That scale-invariance is the whole point of an IVM engine and
the property everything else is built to protect.

The IVM engine runs in the two places that maintain live views, with identical operators and semantics:

- **In the client** — a local-first replica that holds the user's queries as materialized views,
  applies optimistic writes immediately, and reconciles against the server's authoritative log.
- **In a `rindled` read follower** — a server-side daemon that holds shared materializations,
  serves many clients over websockets, and applies a replicated change stream.

The data tier adds a third component with a different job: **the write master**
(`rindle-replicator`) is the HCTree authority, not another query-serving IVM replica. It accepts
concurrent SQL transactions, commits them into one order, and fans that **ordered, durable change
log** out to the followers that maintain views.

The deployment we scale on is the natural consequence: **one write-master fans out to many read
followers, each serving many client replicas.** Writes converge on the master; reads and live
subscriptions fan out across the followers and down to the clients.

## The scaling model, and the two laws

Everything about where Rindle fits follows from two architectural laws. They are not marketing
simplifications, but the capacity of a particular deployment still has to be measured.

### Law 1 — Reads scale horizontally.

A read follower does three things that compound well:

- **It deduplicates work.** Subscribers asking for the same query share **one** pipeline and one
  materialization (`MaterializationManager` keys by query identity). A thousand users watching the
  same feed cost one feed's worth of computation, not a thousand.
- **It stays flat under load.** Hydration latency holds constant as the number of distinct
  materializations grows — the worker pool absorbs the concurrency rather than degrading.
- **Its memory is bounded by working set, not by row count.** Materializations keep only primary keys
  resident and fault the rest of each row from disk on demand, so a broad view costs roughly
  `pk_size × rows` of RAM with the body disk-backed — not the full row times the row count.

Need to serve more readers, more subscriptions, or more distinct live queries? **Add followers.** They
each take a copy of the same change log and serve their share of clients. The read plane is
horizontal.

### Law 2 — Writes scale up within one master, then out by sharding.

There is exactly **one write master per logical database**. This is deliberate: one commit order is
what makes every follower and every client converge without a conflict-resolution protocol. That is
an ordering constraint, not a one-request-at-a-time constraint. Followers add no write capacity, but
the master uses a pool of HCTree connections and can use multiple execution threads on one machine.

There are three distinct layers:

- **Admission.** `RINDLE_WRITE_CONNECTIONS` controls how many write sessions may be open at once;
  the default pool has eight connections. An interactive session stays on one connection for life.
- **Execution.** Every connection uses `BEGIN CONCURRENT`. `RINDLE_WRITE_THREADS` controls how many
  engine threads execute transaction work (default `1`). Raise it with available CPU; disjoint
  transactions can then execute in parallel.
- **Commit.** HCTree validates optimistic transactions and assigns each successful commit the single
  `cid` that orders the replication journal. Concurrency before commit does not weaken ordering after it.

Write-only transactions that lose optimistic validation are replayed mechanically, up to a bounded
retry limit. A session that already returned read results cannot be safely replayed from captured SQL
alone, so the API server re-drives its authoritative mutator. Keyed work uses rendezvous hashing across
engine threads to preserve per-key FIFO arrival order; keyless work round-robins. Batching remains an
important application-side lever.

The gain is workload-sensitive. Transactions over different tenants, documents, or keys can use the
pool well. Transactions that all update the same hot rows will conflict at validation, so increasing
thread count eventually adds retries rather than throughput. "Concurrent writes" removes the old
global serialization ceiling; it does not repeal contention.

When you outgrow one master's write rate, the answer is **shard by tenant**: independent
write-masters, each with their own followers. This works beautifully when your data partitions
cleanly (per-organization, per-workspace, per-document) and fights you when it doesn't. That tradeoff
is the single most important thing to understand before adopting Rindle Cloud.

## What Rindle Cloud is well-formed for

These are the problem classes the architecture is *shaped* for — where the two laws are advantages,
not constraints.

**1. Collaborative SaaS over tenant-scoped data.** Issue trackers, docs and wikis, project and CRM
tools, design and planning apps. Reads and live subscriptions dominate; writes are modest and
naturally partition by organization or workspace; every user watches a bounded set of queries over
their tenant's data. This is the bullseye: reads fan out across followers, writes stay within one
tenant's master, and the per-tenant working set fits comfortably in a follower's deduplicated,
disk-backed materializations.

**2. Local-first and offline-capable applications.** The client *is* an instance of the engine. It
holds the user's queries as live local views, applies writes optimistically with no round-trip, works
offline, and reconciles against the master's log on reconnect via a monotonic per-client sequence.
Apps that want "instant UI, eventual server truth, no manual cache invalidation" get it as a property
of the model rather than as glue code they write.

**3. Live dashboards and feeds over a bounded dataset.** Anything that is "this query, kept fresh on
screen" — activity feeds, leaderboards over a scoped set, operational dashboards, presence-adjacent
views — is exactly what incremental maintenance is for. The first render hydrates; every subsequent
update is a small delta, not a re-query.

**4. Replacing a hand-rolled sync stack.** Teams that have accreted websockets + polling + a cache +
manual invalidation + a reconnect-and-refetch dance are reimplementing a worse version of what Rindle
does as one coherent thing. The honest pitch here is *consolidation and correctness*, not raw speed.

## What Rindle Cloud is not for

Equally important, and stated plainly so nobody discovers it in production.

**1. Unbounded ingest firehoses.** Concurrent writes mean a high request rate is no longer an automatic
disqualifier: batched, mostly-disjoint transactions can use multiple HCTree connections and threads.
But one master still validates every transaction, assigns every commit order, journals it, and feeds
the affected live-query work. Telemetry, clickstream, or IoT workloads whose defining requirement is
unbounded sustained ingest into one logical store should use a log or column store designed for that
shape — unless a measured Rindle deployment comfortably clears the required rate.

**2. Ephemeral high-churn state on the durable path.** Cursor positions, "user is typing," live
presence, per-keystroke collaboration. These are high-rate writes whose value is immediacy, not
durability. More write concurrency does not make durable journaling useful for them. Route
them over a side channel (or a separate, disposable Rindle table) and keep the durable log for state
that matters.

**3. Analytical / OLAP queries over large cold datasets.** Rindle maintains *defined* queries
incrementally; it is not a column store and will not win a full-table aggregate scan over hundreds of
millions of cold rows. A bounded, pre-declared aggregate kept live? Yes. Ad-hoc analytics over the
whole warehouse? No.

**4. Unbounded per-client result sets.** The model assumes each client watches *bounded* queries —
paginated, filtered, scoped to a tenant. "Subscribe to every row in the table" is an anti-pattern by
construction: it defeats dedup, balloons footprint, and turns the write fan-out multiplier against
you. Designing for bounded queries is part of using Rindle Cloud well, not a workaround.

## The sharp edges (and what we do about them)

An honest take names the footguns, not just the features.

- **Pagination is the one to respect.** A "latest-N" feed (`ORDER BY … LIMIT N`) is O(1) per write
  *only* when the sort column is indexed **and** the planner has fresh statistics to use that index.
  Miss either and the engine silently degrades to a full re-sort of the base on every insert — a perf
  cliff, not a correctness bug, so it hides until you're under load with a large table. We auto-author
  the covering index and refresh stats, and we are adding registration-time detection and live
  full-scan observability so a regression announces itself instead of lurking
  ([tracked as #78](https://github.com/tantaman/rindle/issues/78)).
- **Correlated `EXISTS` wants statistics.** Without `ANALYZE`, a two-predicate existence probe can
  mis-plan into an O(n²) hydration; with it, it's linear. This is now part of standard schema hygiene,
  applied automatically.
- **The write fan-out multiplier is real.** Every committed row is pushed through every live pipeline
  that reads its table, so a hot table read by many distinct queries pays a multiple on each write.
  Followers spread this across nodes and dedup collapses identical queries, but a single table read by
  many *distinct* live views is genuine work — design your hot paths with that in mind.
- **Concurrency follows the conflict surface.** Eight open sessions do not imply an eightfold speedup.
  Hot keys and overlapping read-modify-write transactions produce OCC retries; disjoint tenant- or
  document-scoped work benefits most. Watch conflict/re-drive rates, batch related statements, and
  partition genuinely hot domains before adding threads blindly.
- **You own the schema, so index it.** Because you author the tables the engine reads, indexing the
  columns it sorts and joins on is your lever and your responsibility. Most pathologies above are an
  un-indexed sort or correlation column.

## What we've measured, modeled, and not yet proven

The most honest section, because maturity is part of the truth.

- **Measured, on real workloads:** scale-invariant incremental push across the query ladder; flat
  hydration as materializations grow; and linear materialization footprint. These are committed as
  regression-gated baselines.
- **Correctness-tested, not yet a production capacity claim:** concurrent HCTree ingress has coverage
  for pooled sessions, OCC retry/re-drive, per-key ordering, and engine-thread failure. Those tests
  establish behavior, not a universal transactions-per-second number.
- **Modeled, not yet run at scale:** the per-customer capacity figures (how many users a blog / forum
  / CMS / chat shape supports) come from measured unit economics solved through a capacity model on a
  single box. They are calibrated, not multi-node production runs.
- **Still in flight:** some of the footprint-reduction and pagination-observability work described
  above is not yet complete.
- **Not yet measured:** the production throughput curve across connection count, execution threads,
  transaction overlap, and storage; the true per-follower subscriber ceiling beyond ~1k connections (our
  single-process test harness hits a file-descriptor wall first, not an engine wall), and end-to-end
  numbers for a large many-follower fleet. We quote the conservative single-node numbers and say so.

We would rather tell you which line an estimate sits on than blur them together.

## Bottom line

Reach for Rindle Cloud when your application is **sync-shaped over a bounded working set** —
collaborative SaaS, local-first tools, live scoped dashboards — and you want every client's view to
stay incrementally fresh without building a sync layer by hand. Read-heavy applications remain the
clearest fit, but authoritative writes no longer queue behind one globally serialized executor:
disjoint work can run concurrently inside the master. The architecture works best when writes have a
natural tenant, document, or key partition and reads scale independently across followers.

Look elsewhere when your defining constraint is **sustained write throughput beyond one tuned
master** that will not shard, a hot shared-key workload whose transactions continually conflict, or
**ad-hoc analytics over large cold data**. Those are real workloads; they are simply not the shape
this engine is for, and we would rather say so up front than have you find the wall yourself.

---

[View this page on Rindle](https://rindle.sh/blog/what-rindle-is-for)
