Jamon Profile picture
Jul 16, 2021 44 tweets 12 min read Read on X
Tonight's "coding for fun" task is to figure out why we're running into a bit of a perf problem with MobX-State-Tree and MobX-React-Lite in a specific large data set situation.

I'm first going to try to replicate the conditions in a bare React Native app.

Live-tweet it?
First, I'm spinning up a new RN app.

`npx react-native init MSTPerf`

I could have used the TypeScript template project, but decided to add it myself using the instructions here.

reactnative.dev/docs/typescript
Vanilla app is working on iOS, of course. (But I am the type of programmer who checks anyway.)
tf is going on with Android
Um, so, resizing and rotating the Android emulator apparently doesn't work very well?
ok android emulator stays full size ... fine
Adding MST and co
Going a little slow here because, well, not really in a hurry.

Anyway, starting to put together a root store now. This seems to happen most often with large, complex, nested lists of models, when replaced all at once.
Data showing up in logs now
console.time requires a polyfill (I just grabbed this one: github.com/MaxGraey/react…) but I now have some basic perf times.
I need a large dataset. My favorite one is a list of NBA players and teams, along with their ratings and statistics, located here: raw.githubusercontent.com/alexnoob/Baske…
I'm starting by defining a player model and letting most of these be "frozen" data types (aka treated as if they are immutable, and thus not observed).

I will make them observable next, so we have deep & complex MST objects.
Pretty simple api service
My RootStore now can fetch the players from the API and apply that snapshot. I think. Haven't, you know, actually tried it yet. lol
we got objects for days ... it's loading!
So, I want to be testing in production mode, since MST does a lot of optimizations in prod that it doesn't do in dev.

Rebuilding the app...
One fun thing about MST is that you can just copy real data into its models and it'll figure out the type (both TypeScript as well as runtime type checks).
I'm gradually replacing these `types.frozen` properties with real MST models.
Lots of runtime type errors because this API data is kinda dirty. But I'm getting there.
Now we're going to make the app an observer, to capture changes to the store or models that are used within.
Having some problems with making the app run in prod, for some reason, so I'm just going to force it to load what I want.

Objective-C baby
Oops, ignore the syntax error there ... I fixed it 😅
So logs don't show up in terminal anymore when I'm running in prod mode, but I can find them in Xcode. Fine.
Okay, so it's loading 872 quite complex "players" into the system in 1.18s, which isn't amazing but also isn't too bad, considering they're fully "observable" objects.
So now I'm going to render them on the screen, and then trigger re-hydrations to see what happens. Maybe even rearrange them while I'm at it.
It's hydrating. Noice.
Accessing some sub-objects now, just to do it.

Perf seems decent so far.
Okay, so I think I"m starting to see the hints of the issue we're noticing.

Notice that the first log is 1179ms and the second is 1737ms?

So, the first log is *inside* the second's scope, meaning that ideally they would be the same number.
`applySnapshot` is all that's running in `replacePlayers`. So why is `replacePlayers` taking so much longer? 🤔
Proving it. See?
Getting my kids sent to bed and settling back in for a bit longer.
I need to make this even more complex to see where this issue is really coming from.

I'll make "teams" and assign players to each team, too. With references back and forth maybe?
Okay, so before I even get that far, I'm starting to see the behavior exhibited before.

This is without displaying any data, so no observers are being harmed here.

The initial load is just over 1.1s. But the reload is taking like 5 seconds. 5.4, to be exact.

Why...
It also happens when I do `applySnapshot` instead of `.replace()`.
Okay, I'm going to see if I can replicate this in a simple React web app.
I'm impressed with myself at how fast I spun up a new completely bare-bones React app. Not using CRA or anything, just Parcel, index.html, and a few tsx files.

Anyway, self-congrats aside, yes, this happens on web too.
So apparently it's kinda a pain to use @brave with the Chrome Debugger + VS Code.

Downloading Chrome *just* for this purpose. Brave has been entirely fine otherwise as a Chrome replacement.
@brave Debugger running!
@brave It's interesting stepping through the code and seeing what's running.

When the action runs, MST doesn't update any views or observers until the end of the transaction, which is cool
Okay, it's past midnight, so I'm going to call it a night.

HOWEVER

I figured out something *very* interesting.

If I run `.clear()` on the large dataset before repopulating it, there is no longer the long lag.
This leads me to believe MST is doing a bunch of checks against the existing dataset that is just taking forever.

Perhaps @mweststrate can help me figure this out. I'll share the code.
@mweststrate Github: github.com/jamonholmgren/…

1. Clone it down
2. `yarn`
3. `yarn parcel index.html`
4. Open localhost:1234
5. Open the console & observe
Going to work on solutions next. Perhaps a new way to update MST models without doing the merge?

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Jamon

Jamon Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @jamonholmgren

Sep 22
Bun wildly simplifies my stack.

I’m rebuilding an internal web app that we’ve had since 2016. So far, I have a grand total of 1 npm runtime dependencies (jwt-token).

Our previous version of this tool, using Next, had 826 (!!!) NPM dependencies.

I also have zero build steps. Just ship the files directly to the server.

I’m using:

- Bun as a JS runtime
- Bun as a package manager
- Bun.serve as a webserver
- Bun’s websocket server for realtime
- TypeScript on server side
- JSDoc on the client side
- REMEMBER: no build steps!
- not even TypeScript in the deps
- HTML via template strings
- CSS
- Hosted on DigitalOcean droplet
- In-memory state (just a JS object)
- JSON file for persistence
- JS’s setInterval for scheduled tasks
- git to deploy
- Cursor to generate and edit code

I’ll talk more about each of these below.Image
- Bun as a runtime

Bun replaces Node, of course, and brings a ton of speed as a runtime.

I just install Bun and then run it

bun run ./src/index.ts

This is also how I run it on the server.
- Bun as a package manager

This is how most people use Bun.

bun install

It’s lightning fast, but also, I only have 1 runtime dep (and a few dev dependencies, which are all type related like @types/bun), so of course it’s fast.
Read 35 tweets
Feb 12
One time I pulled down a new client’s repo and found this commit from the previous dev
Image
OK by popular demand, I found more details.

First -- here's the document . 😅

Secondly, now that I know the client name, I for sure know why this dev got screwed. I have a lot of stories about Don. README-ASSHOLES.md
Image
I built their original Rails website in 2012. It was an ecommerce website and we built it from scratch with an admin panel and everything.

I think all-in, it was about a $15-20k project, and I did get paid by them.
Read 12 tweets
Jul 15, 2023
My experience raising 4 kids, in a thread:

* First years are a lot of physical work, but I can still remember that freshly bathed baby smell as they lay sleeping on my chest. One of my fondest memories and times of my life.

(She’s now 15!) https://t.co/LeCPjJUShH
Image
* Elementary years are pretty good. Some running around but we had more time where they were out at school and they also are learning to do more themselves. The introduction of more of a more rigid schedule was a big adjustment, and we had to plan trips more. Image
* Middle school years are harder because it’s a transition for them *and* for us — them to the complex social dynamics, and us to being a different kind of parent who can help them navigate that rather than wiping their butts or carrying them around. Also developing more complex… https://t.co/xRBXyI8RZNtwitter.com/i/web/status/1…
Image
Read 9 tweets
Apr 15, 2023
Want to know how easy it is to grab API keys out of a compiled iPhone app?

Buckle up. 👇
DISCLAIMER: This is for security research only. Responsibly disclose vulnerabilities you find to the relevant developers! I MEAN THIS. Don't be a tool.
First, install the open source ipatool.

brew tap majd/repo
brew install ipatool Screenshot of downloading i...
Read 15 tweets
Apr 14, 2023
I once again am asking mobile devs to not ship their API keys in their app bundles.

(No, compiling it into native code won’t make it any more secure. I could extract it from native code in 5 minutes, and that’s not an exaggeration.)

(No, putting it in react-native-env etc won’t… twitter.com/i/web/status/1…
How do you store the API key, then?

Same way you would in a website. Store it server-side, have the app authenticate the user, send them a user-specific token, and then they can use that to ask your server to ask the API something for them.

Only thing exposed is that specific… twitter.com/i/web/status/1…
Ironically, I had to implement this server-side handling of secure tokens a couple times today; haven't done that in months.
Read 7 tweets
Aug 11, 2022
Leaving aside solutions like React Query, I'll talk a bit more about React state management libraries and why I dislike selectors. I'll take a few popular state management libraries and contrast them to MobX (or MST).
So let's do a tiny component that shows Bears, Beets, and Battlestar Galacticas. It'll show the bears and beets, but not the BSGs, so we can measure if the component is rerendering when it shouldn't. Image
Caveat: I'm not an expert in all of these libraries, so if you know a better, more idiomatic way to do this, please let me know, and I'll adjust accordingly.
Read 28 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us!

:(