What happens when you filter a million rows in TableRune
“I want to know whether a browser spreadsheet can really handle my data or whether it will fall over.”
A spreadsheet that runs in a browser tab and handles millions of rows sounds like it should not work. Browsers are not known for that, and most tools making the claim turn out to be sending your file somewhere.
This is the actual path a click takes, because the interesting part is which piece does what, and how little of it we wrote.
We did not write a query engine
The thing executing your filter is DuckDB, compiled to WebAssembly and running in your browser. It is an established analytical database with years of work behind it, and it is very good at exactly this.
Saying so costs us nothing and is the honest starting point. If you are comfortable writing SQL, you can use DuckDB directly and skip us entirely. What we built is the part between a spreadsheet grid and that engine.
The path a click takes
You click a filter on a column. Four things happen.
1. The click becomes an operation. Not a change to your data, an entry in a list. That list is the pipeline, and it is the single source of truth for what the view shows. Nothing is applied in place, which is why undo and redo are just a matter of which version of the list is current.
2. JavaScript builds a graph. The operations become nodes with edges between them: a source, a filter, a sort, a derived column, a projection. This is a directed graph rather than a list because a later step can depend on more than one earlier one, a merge being the obvious case.
3. Rust plans it. The graph is handed to our engine, compiled to WebAssembly, which returns an ordered set of execution steps. This is the piece we wrote, and it is a planner rather than an executor. It decides what has to happen and in what order.
4. DuckDB executes it. JavaScript walks the plan and drives DuckDB step by step, and the result comes back as Arrow, a columnar format both sides already speak, so the data does not get converted at the boundary. The grid then points at the materialized result.
The division is worth stating plainly, because the interesting engineering is in the seams rather than in any one piece. Rust decides, JavaScript drives, DuckDB computes.
Where the work happens
All of it runs in a Web Worker, so a slow query does not freeze the interface. DuckDB then runs in a worker of its own inside that one, which makes this a worker inside a worker. Browsers handle that arrangement fine.
Queries run on one thread. DuckDB-Wasm does offer a threaded build, but it only activates on a cross-origin isolated page. This site is not one, so DuckDB would never select that build even if we shipped it, and shipping it would mean another 35 MB of WebAssembly that never runs. The production build therefore contains the single threaded modules only. The threaded one is compiled in behind a flag we use for benchmarking and nothing else.
We would rather say that than imply every query is parallel. If you are comparing against a threaded DuckDB on your own machine, that is the difference you are seeing.
Why the row ceiling rejects instead of truncating
The plan limits are 500,000 rows on the free tier and 10 million on the paid one. The numbers live in the WebAssembly engine rather than in the page around it, so they are not a one-line edit in the browser console.
It is worth being precise about what that buys, because it is a licensing control and not a security boundary. The limit itself cannot be rewritten by editing our JavaScript. The check that calls it can be, because all of this runs on your machine, and anything running on your machine can be patched by someone determined enough. What the design gets is that the honest path is the easy one, which is all a row ceiling needs to do.
When a file is over the limit the table is dropped and the import fails with a message naming the actual row count and the actual limit.
That behavior is deliberate and it is the whole reason this tool exists. A spreadsheet that opens a three million row file and shows you the first 1,048,576 rows has given you a file that looks complete and is not, and every total you compute from it is quietly wrong. Refusing is worse to look at and better to trust.
Why nothing is uploaded
DuckDB-Wasm normally loads its WebAssembly from a public CDN at runtime. Ours does not. The engine modules are served from assets.tablerune.com, which is our own storage bucket on our own domain rather than a third party.
They sit there rather than inside the application bundle for a dull reason. Each module is around 35 MB, over the per-file limit of the host that serves the site, so they are uploaded once per release and fetched from there. The small worker scripts are still bundled with the app.
That matters for one reason. If the engine pulled from a third party at runtime, "nothing leaves your machine" would depend on who that party was and what the request carried. Serving it ourselves removes the question rather than answering it. The traffic also runs one way: the browser downloads an engine, and nothing goes back.
You can confirm the part that counts in about thirty seconds. Open developer tools, go to the Network tab, clear it, then load a file and run a transform. You will see the application's own assets, the engine modules from assets.tablerune.com, and nothing carrying your rows. That last part is the claim, and it is the one you do not have to take from us.
What this does not give you
It is not faster than DuckDB. It is DuckDB. If your comparison is a SQL query you would have written yourself, expect the same engine to perform the same way, minus whatever our planning adds.
Ten million is a ceiling, not a promise about your machine. The limit is what the plan permits. What actually completes depends on your available memory and the shape of the query, and a wide table with many derived columns will hit practical limits before it hits the licensed one.
The plan is ours and the SQL is generated. A query you write by hand can be smarter than one synthesized from a sequence of grid operations. That is the trade you make for not writing it.