How Remy Apps Scale to Millions of Rows on SQLite (Yes, Really)
Remy gives every tenant a serverless SQLite database with atomic deploys, physical isolation, and trivial export — here's where it scales and where to use Postgres instead.
The short answer
Remy gives every tenant their own serverless SQLite database, with WAL mode on by default and atomic per-release snapshots. That sounds quaint until you read the spec sheet: SQLite databases can grow to 281 TB. Per-tenant databases on Remy routinely sit at a gigabyte or more with no operational stress. It is the same pattern Notion uses for per-workspace storage, Tailscale uses for its control plane, Fly.io uses for LiteFS, and Cloudflare uses for D1.
The longer answer is what most people actually want: how does this hold up in production, where does it break, and what should you do when it does? This piece is the architectural walkthrough — limits included.
At a glance
- Architecture: per-tenant SQLite, serverless and durable by default, WAL mode enabled
- Capacity: SQLite’s hard ceiling is 281 TB; per-tenant databases comfortably run at 1 GB+
- Concurrency model: multi-reader, single-writer per tenant (WAL); reads do not block writes
- Deployment: every release ships with its own database clone; rollback restores schema and data together
- Peers in production: Notion (per-workspace), Tailscale (control plane), Fly.io (LiteFS), Cloudflare (D1)
- Where it breaks: bursty event ingestion, real-time multiplayer state, append-only analytics over millions of writes per second, high-frequency financial transactions
- Where it shines: internal tools, vertical SaaS, CRMs, approval workflows — anything where writes correlate with human action
- Portability: any SQLite client can read the file; export is a download and a re-point
How does serverless SQLite work for production apps?
The folk knowledge about SQLite is twenty years out of date. Most engineers still think of it as the embedded database that ships inside their phone or their Firefox bookmarks. That is one valid deployment shape. It is not the only one.
In Remy, every app you build gets its own SQLite database, served behind a managed runtime. The database is durable by default — versioned, snapshotted, restorable — without you running a database server. Reads and writes happen against a hot runtime copy; the platform handles the durability layer underneath. The query engine is still SQLite, running with WAL mode on, so concurrent readers do not block the writer and the writer does not block readers.
This is the same architectural pattern Cloudflare uses for D1, Fly.io uses for LiteFS, Notion uses for per-workspace storage, and Tailscale uses for its control plane. The shape of the bet is consistent: most multi-tenant SaaS workloads do not need a shared, centrally-coordinated relational database. They need isolation, durability, and good-enough write throughput per tenant. Serverless SQLite gives you all three at a fraction of the operational cost of running Postgres or MySQL with the same isolation guarantees.
The trick is that “per-tenant” is doing the load-bearing work here. You are not asking one database to absorb the writes of ten thousand customers. You are asking ten thousand databases to each absorb the writes of one customer.
Can SQLite handle a real production workload?
Yes, depending on your definition of “real.” Three numbers anchor the answer:
-
SQLite’s hard size ceiling is 281 TB. That is not a typo. The official documentation lays out the math: 2^45 bytes maximum database size. Most tenant databases on Remy live somewhere between 10 MB and a few GB. Per-tenant databases routinely run at 1 GB+ without operational stress.
-
WAL mode gives you multi-reader, single-writer concurrency. Reads do not block writes. Writes do not block reads. Writes serialize against each other. In practice, this means a single tenant database can handle hundreds of writes per second of normal application traffic without breaking a sweat.
-
SQLite is the most-deployed database in the world. Every Android phone, every iPhone, every Mac, every Windows machine, every web browser — they all ship SQLite. The engine is hardened in ways most server-class databases are not. It is not a toy.
The thing to internalize is that “production workload” is not a single number. A workload is a shape. Reads-per-second, writes-per-second, write burstiness, query complexity, working-set size, latency tolerance, durability requirements. SQLite + WAL handles the long, fat middle of the production-workload distribution beautifully. It chokes on the tails. Knowing which tail you are in is the entire question.
Is per-tenant SQLite a good architecture for SaaS?
For the workloads Remy is built for, yes — and the architectural choice does more work than the database choice alone.
Per-tenant means each customer’s data is physically isolated in its own database. That isolation does three useful things at once:
- Blast radius is bounded. A bad query, a runaway migration, or a corrupt write affects exactly one tenant. There is no shared connection pool to saturate, no shared buffer cache to poison, no shared schema lock to contend over.
- Export is trivial. Any SQLite client can read the file. Migrating off the platform is a download and a re-pointing of your data-access layer. Compare that to extracting a single tenant from a multi-tenant Postgres database, which is a project, not a download.
- Per-release database cloning becomes cheap. Every release on Remy ships with its own database clone. DDL applies to the clone. If the migration fails, the live database is untouched and the release is marked
failed. The live pointer atomically flips when the clone is ready.
Plans first. Then code.
Remy writes the spec, manages the build, and ships the app.
That last point is worth pausing on. In most SaaS stacks, schema migrations are run against the live database. You hope the migration is fast enough that nobody notices. You write rollback scripts you mostly never test. When a migration fails halfway through, you are doing surgery on a running patient.
On Remy, a release is a unit that includes the code and the schema and the data shape. Rolling back to the previous release rolls back schema and data together, atomically. That is better than what 95% of teams build for themselves, and it falls out almost for free from the per-tenant + per-release architecture.
The shape of SaaS this maps to: vertical SaaS, internal tools, CRMs, approval workflows, vendor-management tools, inventory systems, lightweight ERPs, internal dashboards. Workloads where writes correlate with human action — somebody clicked a button, submitted a form, approved a request. Human-shaped traffic.
Where does serverless SQLite break?
This is the section the candor-becomes-ammunition crowd will quote out of context. Read all of it.
The single-writer ceiling per tenant is real. WAL mode lets readers run concurrently with the writer, but writes against the same database serialize. That is fine when writes are human-paced; it is not fine when writes are firehose-paced.
Specifically, here are the workload shapes where per-tenant serverless SQLite will hurt:
- Bursty event ingestion. Webhook firehoses, IoT telemetry, click-stream capture, log aggregation. Any workload where a single tenant might emit hundreds or thousands of write events per second. WAL’s single-writer model serializes these, and you will see queuing.
- Real-time multiplayer state. Collaborative editors with sub-second write feedback, multiplayer games with per-frame state updates, anything that looks like an operational transform stream. The latency budget here is tighter than a serializable write path can afford.
- Append-only analytics over millions of writes. Analytics warehouses, time-series workloads, event sourcing at scale. Even at hundreds of writes per second, this kind of workload grows the WAL faster than checkpointing can keep up cleanly. You want a columnar store or a purpose-built time-series database for this.
- High-frequency financial transactions. Order book updates, payment-processing fan-out, anything that needs millisecond-bounded write commit guarantees. SQLite’s durability guarantees are excellent but the throughput envelope is not built for this.
- Cross-tenant queries at scale. Per-tenant means there is no single SQL surface to query across all of your tenants. If your product needs to compute “top 10 customers by GMV across all tenants” in real time, you are doing a fan-out query and aggregating in application code. That is fine for small numbers of tenants and brutal at thousands.
If your app is shaped like any of those, per-tenant SQLite is the wrong choice. You should know that before you pick the tool.
Where does serverless SQLite shine?
For the rough 90% of business apps that do not look like the list above, serverless SQLite is not just acceptable — it is better than the obvious alternatives for the cost.
Anything where writes correlate with human action runs comfortably:
- Internal tools. Vendor approval systems, expense workflows, inventory trackers, equipment checkout. Writes are human-paced; reads are human-paced; isolation is helpful.
- Vertical SaaS. A CRM for dental practices, a deal tracker for a small VC fund, a scheduling app for trade contractors. Each tenant is small. Each tenant’s writes are small. Per-tenant isolation maps perfectly onto the customer model.
- Approval workflows. PR approval, contract signing, document routing. Low write volume per tenant, high read fan-out, lots of state machines.
- CRMs and customer dashboards. Sales pipelines, support ticketing, customer-data dashboards. Reads dominate; writes are human-paced.
- Internal AI agents and chatbots. A research agent, a customer-service co-pilot, an HR question-answering bot. Database is mostly logging conversations and storing state. Read-heavy, write-modest.
- ✕a coding agent
- ✕no-code
- ✕vibe coding
- ✕a faster Cursor
The one that tells the coding agents what to build.
For all of these, you are paying the operational cost of running a Postgres cluster — connection pools, vacuuming, failover, replication, schema-migration tooling — to handle a workload that fits comfortably inside a 200 KB database engine. The math on that gets bad quickly. Serverless SQLite with per-tenant isolation gives you a simpler architecture that is also more durable and easier to export.
How does Remy’s database scale?
In two directions, neither of which involves making any single tenant’s database bigger than it should be.
Horizontal scaling is the boring one. More tenants means more databases. Each tenant gets its own database. Adding the thousandth tenant adds the thousandth database, not the thousandth row to a shared schema. There is no shared write path to saturate. Operationally, this is the same shape as adding a thousandth row to a key-value store keyed by tenant ID — except the “value” is a full relational database with WAL concurrency and SQL.
Per-tenant scaling is the interesting one, and it has limits. A single tenant can grow to several GB of data without operational concerns. Above that, you start thinking about indexing more aggressively, archiving older data, partitioning by time. The 281 TB ceiling is theoretical; the practical ceiling for any one tenant is closer to “as much data as your queries can still finish quickly,” which is workload-dependent.
The atomic-release model also scales well. Every release of every app gets its own database clone, prepared in advance, with DDL applied. Promotion flips a pointer. If you ship ten releases a week across a hundred apps, you are doing a thousand atomic database swaps — and you never wrote a migration script that you weren’t sure would roll back cleanly.
Try Remy and describe the app you want. The architecture under it does the work whether you are building one tenant for yourself or many tenants for your customers.
Remy database architecture vs the obvious alternatives
A small comparison table. Where the rows on the right are red flags, that is a reason to pick a different tool. The rows on the right are not “bad” — they are tradeoffs that make sense for different workloads.
| Decision | Per-tenant serverless SQLite (Remy) | Shared Postgres (Supabase, Neon) | Per-region Postgres (RDS, Aurora) | Document store (DynamoDB, Firestore) |
|---|---|---|---|---|
| Tenant isolation | Physical (separate databases) | Logical (row-level) | Logical (schema or row-level) | Logical (partition key) |
| Per-tenant export | Download the file | Export query, no isolation | Export query, no isolation | Export query, no isolation |
| Concurrent writes per tenant | Hundreds/sec (single writer + WAL) | Thousands+ (MVCC) | Thousands+ (MVCC) | Tens of thousands+ |
| Cross-tenant analytics | Application-level fan-out | Native SQL | Native SQL | Eventual via streams |
| Schema migration safety | Per-release clone, atomic flip | Live database, hope and rollback scripts | Live database, blue/green if you build it | Schemaless, app-level versioning |
| Operational complexity | Near zero | Moderate | High | Moderate |
| Cost at small scale | Negligible | $25/mo and up | $50/mo and up | Pay-per-request |
| Best fit | Vertical SaaS, internal tools, CRMs, workflow apps | General-purpose SaaS, complex cross-tenant queries | Enterprise SaaS with heavy cross-tenant analytics | High-throughput event ingestion, gaming, IoT |
The point of the table is not that one column is correct. The point is that the columns map to different workload shapes. If your workload sits in the leftmost column, you would be paying twice — once for the database, once for the complexity — to live in any of the other three.
What should you use if serverless SQLite doesn’t fit?
If you are building one of the workloads in the “where it breaks” section, do not use Remy as-is. Here is the honest tool recommendation:
- Bursty event ingestion or high-throughput writes: Convex is the closest thing to a “Remy-shaped” architecture with a different write model. Supabase or Neon with Postgres works well if you need SQL and traditional connection-pool semantics.
- Real-time multiplayer state: Liveblocks, PartyKit, or any operational-transform layer purpose-built for the shape.
- Append-only analytics: ClickHouse, BigQuery, or any columnar warehouse. SQLite is not the right tool here. Nothing relational really is.
- High-frequency financial transactions: Postgres or a purpose-built ledger database like TigerBeetle. Both are well-suited to the durability + throughput envelope this workload demands.
- Cross-tenant analytics at scale: Pair Remy (or any per-tenant store) with a downstream warehouse. Stream tenant changes to BigQuery, Snowflake, or ClickHouse. Run analytics there, not in the operational store.
That recommendation list is not a list of “Remy competitors.” It is a list of right-shaped tools for workloads Remy is not built for. The honest framing here matters: per-tenant serverless SQLite is a precise choice. The 90% of business apps it fits, it fits well. The 10% it does not fit, you should use a different tool and not feel bad about it.
Frequently asked questions
How does serverless SQLite work for production apps?
Each tenant gets their own SQLite database, served behind a managed runtime. Reads and writes happen on a hot runtime copy of the database; the platform handles durability, versioning, and snapshots underneath so you never run a database server. WAL mode keeps reads and writes concurrent. The combination gives you SQLite’s query semantics with serverless durability.
Can SQLite handle a real production workload?
Yes, for workloads where writes correlate with human action. Per-tenant databases routinely run at 1 GB+ on Remy without operational stress, and the engine itself supports databases up to 281 TB. Bursty event ingestion, real-time multiplayer state, and append-only analytics at scale are where SQLite hits its ceiling.
Is per-tenant SQLite a good architecture for SaaS?
For vertical SaaS, internal tools, CRMs, and approval workflows: yes. It gives you physical tenant isolation, trivial per-tenant export, atomic schema migrations, and operational cost close to zero. For high-throughput or cross-tenant-analytics-heavy SaaS, no — use a shared relational store or a purpose-built system.
What is the write throughput ceiling on per-tenant SQLite?
WAL mode gives you a single writer per database with concurrent readers. In practice, a single tenant database handles hundreds of writes per second of normal application traffic comfortably. Above that, writes start queuing. The exact ceiling depends on transaction size and write complexity.
How does Remy’s database scale?
Horizontally by adding tenants — each tenant gets its own database, so there is no shared write path to saturate. Per-tenant, by indexing aggressively and archiving older data when needed. The atomic per-release cloning scales independently: every deployment gets its own database clone whether you ship once a week or ten times a day.
Remy doesn't build the plumbing. It inherits it.
Other agents wire up auth, databases, models, and integrations from scratch every time you ask them to build something.
Remy ships with all of it from MindStudio — so every cycle goes into the app you actually want.
Who else runs SQLite in production at scale?
Notion uses per-workspace SQLite for its block storage. Tailscale uses SQLite for its control plane. Fly.io built LiteFS as a distributed SQLite layer. Cloudflare’s D1 is SQLite-at-the-edge. The pattern is well-established in production systems serving hundreds of millions of users.
Can I export my data off Remy?
Yes. The SQLite database is a standard SQLite file. Any SQLite client can read it. Migrating off Remy at the data layer is a download and a re-point. Migrating off at the application layer — the methods, the auth, the deployment runtime — is a larger project, because that is where the platform value sits.
What happens if a schema migration fails?
Nothing happens to your live database. Every release gets its own database clone, DDL is applied to the clone, and the live pointer only flips after the clone is verified. Failed migrations leave the live database untouched and mark the release as failed. Rollback is a re-point to the previous release, which restores schema and data together.
Does Remy support Postgres or external databases?
Today, Remy is opinionated on per-tenant serverless SQLite because that architecture maps cleanly to the workloads Remy is built for. If you need Postgres-shaped semantics — bursty writes, cross-tenant analytics, complex distributed transactions — Remy is not the right tool yet. We will be honest about that rather than try to bend the architecture to fit.
Where can I read more about the architecture?
The full developer guide is open source at github.com/mindstudio-ai/remy. The database section covers schema migrations, per-release cloning, and the auto-migration model. The MSFM and methods sections explain how the spec compiles into the table definitions and TypeScript code that runs against this architecture.
The payoff
Per-tenant serverless SQLite is not exotic. It is what a senior engineer designs when the constraint is “I want database isolation, atomic deployments, easy export, and low operational cost — and I am willing to give up cross-tenant analytics and high-throughput writes to get there.” That is a sensible trade for a large class of business apps, and it is the trade Remy is built around.
If your workload fits that trade, start building with Remy and let the architecture do the work. If your workload doesn’t, use a different tool. The credibility of the architectural choice is in being clear about both sides.
What is Remy?
Remy is a product agent that compiles annotated markdown into a full-stack app — backend, database, frontend, auth, tests, and deployment — in a single step. Built by Wooster Labs on the MindStudio platform substrate. See goremy.ai.

