Architecting for performance
See original GitHub issueGoal: 10ms blocks
When you are in a browser environment, in order to have a smooth UI, main-thread JS should be kept to 10ms per frame. While the view layer is usually the larger culprit, we saw with minimongo (eg https://github.com/meteor/meteor/issues/6835) that updating the data store can easily exceed that 10ms budget.
Apollo work
Our JS work includes:
- performing client-side query
- sending query to server (should be quick)
- performing client-side simulated mutations
- receiving data and updating the store
Which of these might take over 10ms?
Solutions
- Code speed improvements
- Breaking work into estimated 10ms blocks and calling
requestAnimationFrame
between blocks. A proper system for this would be client-wide and include the view layer, so the per-frame work can be totaled / shared / turns can be taken. Related: meteor-kernel. - Doing work in a Worker (which does not block UI)
1 and 3 would be best. Workers have separate memory, so if work on an in-memory store were in a Worker, the store would also have to be in the Worker, and you’d need to pass messages between the Worker and the main thread for each query or change notification. Both have access to the same persistent stores (IndexedDB and localStorage), but they don’t support change notifications, which you’d need for reactivity.
cc @mitar
Issue Analytics
- State:
- Created 7 years ago
- Reactions:4
- Comments:23 (16 by maintainers)
Top GitHub Comments
Hi @helfer , I wrote a slightly longer than expected response. Hope you don’t mind.
I have created a package called
workux
to facilitate Redux and web worker integration. Check it out here.Redux & Web Worker Architecture
Similar to
createStore
in Redux, Workux’screateProxyStore
creates a proxy store that exposes same methods likegetState
,dispatch
, etc. but with different underlying logic. This proxy store uses worker messaging protocol akapostMessage
andonmessage
to dispatch actions and receive Redux store updates respectively. User can also specify enhancers / middlewares that need browser APIs or DOM increateProxyStore
. For example,react-router-redux
useshistory
module, which will not be available on the worker thread, in its middleware. To add a note, All of these happen on the main thread.Workux also provides a method
createWorkerStore
to attach worker messaging protocol to Redux store. So when Redux store gets updated, worker store willpostMessage
to proxy store. Similarly, when worker store receives an action from proxy store, it will dispatch the action to Redux store.Problem with Apollo client
I have to initialize Apollo client on the main thread so that
react-apollo
can use it. With that being said, XHR requests still happens on the main thread. This kind of defeats the purpose of moving everything to web worker. Preferably, I would like to have web worker handle XHR requests, store updates, etc., leaving just the UI and animations on the main thread. One way to solve this is to create an Apollo proxy just like what I did with Redux. But that would be a lot of work and potentially lots of rough edges.An Oversimplified Proposal
Instead of having an Apollo Client instance, split it up to smaller modules like
createApolloMiddleware
,createApolloReducer
andcreateApolloClient
. XHR requests will happen increateApolloMiddleware
as it is now.createApolloClient
will dispatch actions and listen to store updates. That way I can putcreateApolloReducer
andcreateApolloMiddleware
in web worker andcreateApolloClient
on the main thread.Reactive specificity
Oh, I was thinking of that as part of the larger-culprit view layer I mentioned, since in Blaze that’s decided by which reactive deps you use inside helpers. Thanks, wasn’t familiar with how that’s different w/ Redux.
That would be a bigger deal – matching the reactive specificity of Tracker & Minimongo.
Service workers
I was actually thinking about them when I left the Web out of Workers ☺️ It would be fantastic to not only not need to rehydrate the store on subsequent page loads, but not even need to fetch the changes since last load, because the background service had been receiving them. Ideally we put the store in:
Measuring performance
You could use the Timeline or Profiles tab of Chrome Devtools for one-off. For repeatable tests that you can benchmark on different browsers, you can use the performance api, which is even supported on latest ios safari:
And have a test suite that times operations with moderate and large amounts of test data. A good slowest target might be mid-range Android phones.