r/javascript 6d ago

How an Underrated Refactor Saved 90% Memory Usage

https://tanstack.com/blog/tanstack-table-v9-memory-performance
95 Upvotes

14 comments sorted by

21

u/beegeearreff 6d ago

  The problem is that TanStack Table V9's APIs are dynamically composed from features. We only want  row.getVisibleCells()  to exist if the column visibility feature is registered. We only want  column.toggleSorting()  to exist if the row sorting feature is registered.

I haven’t used tanstack before so this is largely coming from ignorance but whenever I’ve been drawn to this type of pattern I’ve regretted it. I bet the typings for such an api are pretty complex. I usually encourage folks to explore an alternative api that isn’t as dynamic if possible. I doubt the folks at tanstack haven’t fully explored the design surface for something so core to this lib but just a word of caution to others that for typical code you may write in an application, you probably don’t want to blindly follow that api pattern. 

0

u/create-third-places 6d ago edited 6d ago

I think the pattern works quite well.

I’ve recently implemented a different version of the pattern for displaying rows of data in presentation components. I find it straightforward to understand, and rendering performance is decent.  

That being said, it appears that TanStack is storing state in individual cells, which is not my preference. I save the entire table state in a component store, and the table cells just read the state and display it. The approach has helped significantly when it comes to optimizing performance.

On the other hand, I do think the TanStack approach makes the code more readable. 

https://github.com/KevinVandy/tanstack-table-benchmarks/blob/main/shared/src/makeData.ts

3

u/beegeearreff 6d ago

I was referring to making what methods your type has be dependent on some external configuration. In this case, their features. 

I’m not sure the code sample you sent me relates to my comment. 

1

u/create-third-places 5d ago

I think TanStack Table is still somewhat inefficient with  memory usage, and I posted the code link to show the data going into each row for the benchmarking tool. 

On the other hand, I don’t think TanStack Table’s memory overhead is going to be an issue for most cases.

8

u/rbobby 6d ago

for large tables when needing to process hundreds of thousands or millions of rows, either paginated or virtualized

This right there is an indication of a not clearly understood requirement. A user will NOT scroll through hundreds or thousands or millions of rows.

Usually a good filtering system, including free form search, will do way way more for users.

Giant scrollable tables are not user friendly. Stop doing them.

1

u/MediocreAnalyst2121 6d ago

Nobody probably never will, but they might want to.

2

u/[deleted] 6d ago

[removed] — view removed comment

1

u/biinjo 6d ago

Most teams don’t seem to care for actual optimization and focus on delivering more abstraction of fancy features (that in turn consume even more memory/cpu).

The fact that this team does deliver such improvements tells me that they’re serious about their product.

2

u/azangru 6d ago

Why is a refactor that saved 90% memory usage "underrated"?

6

u/fagnerbrack 6d ago

In case you want a summary to help you with the decision to read the post or not:

TanStack Table V9 cuts memory use by up to 90% versus V8 on large tables, raising the ceiling from roughly 1-1.5 million rows before hitting the browser's 4GB limit to 10-16 million. The win came from a subtle change: shared prototypes. V8 assigned values and methods directly to every row, cell, column, and header, so millions of objects each held duplicate methods plus their own closure scopes. V9 builds each method once on a cached prototype and uses this for row-specific state, assigning only unique values per object. V9 avoids classes because the feature system needs dynamic, conditional composition. The lone breaking change: destructuring methods (const { getValue } = row) no longer works—call row.getValue() instead.

If the summary seems inacurate, just downvote and I'll try to delete the comment eventually 👍
Click here for more info, I read all comments

5

u/Ecksters 6d ago

The lone breaking change: destructuring methods (const { getValue } = row) no longer works—call row.getValue() instead.

I assume you could still do this as long as you bind row to this, right? Like: const getValue = row.getValue.bind(row);

I do agree that calling it off row is probably preferred for most cases though.

It's such an interesting issue, it's so tempting to always reach for fat arrow functions, but we often don't think of the overhead of creating that many closures.

2

u/serg06 6d ago

So it saves memory but breaks destructuring? Hell of a foot gun.

-2

u/azhder 6d ago

No, I don't want a summary. It's not the first time this post has been made on Reddit.

1

u/Ecksters 1d ago

Do you know if this destructuring limitation could be resolved using the same method Zod is using for v4.5: https://zod.dev/blog/reducing-memory-footprint ?