Redux Performance with Large Store and frequent updates #1303

Closed
landabaso opened this Issue Jan 28, 2016 · 18 comments

Comments

8 participants
@landabaso

This is a question / ask for advice

Let's say you have a fairly large SPA with lots of state since there are many pages, panels, sub panels and lots of stuff.

Now imagine you design your App using reducer composition (at various levels) and have the Store pretty well organised so that it makes your life easier.

Now imagine that in one of the panels you get live events. For example, let's say you have a list of 100 items consisting of stock exchange values which are updated each one independently 1 time per second. (Please disregard the problem of the network and assume the values are randomly generated).

I believe React is the way to go since it will be smart and won't render when it is not necessary.

But I'm not sure how to make it work with Redux.

It's nice that I can use one Store and fetch all the stock values and store them individually triggering actions an so on and having a pretty nice Store with all the info very well organized.

But since I am getting very frequent updates, then all my reducers (which will be a lot) will have to be called. I know that the call will be very quick since most of the time I will only copy object pointers.

Anyway since my app is using composition so heavily then there will be many function calls. This can be bad specially on old mobile devices.

I'm designing one such app and I'm not sure how to make Redux fit in there (if possible). Perhaps you could give some valuable advice at this point (I just started working on it). Is it possible to use (somehow) different Stores each one connected only to different actions so that I minimize calls to reducers?

Any ideas?
Thanks

@gaearon gaearon added the discussion label Jan 28, 2016

@sompylasar

This comment has been minimized.

Show comment
Hide comment

This comment and the whole discussion may help: https://github.com/rackt/redux/issues/1287#issuecomment-176330134

@landabaso

This comment has been minimized.

Show comment
Hide comment
@landabaso

landabaso Jan 28, 2016

Thanks @sompylasar. My question is a bit different.

I'm sure that the updated values belong to the Store because they are a fundamental part of the App.

Say we have a store such as this one:

store: {
  subStore1: {
    subSubstore1: {}
    ...
    subSubstore10: {}
  },
subStore2: {
    subSubstore1: {}
    ...
    subSubstore10: {}
  }
...
subStore10: {
    subSubstore1: {}
    ...
    subSubstore10: {}
  }
}

(I hope you get the idea). In fact this could go even further in depth.

All substores correspond to very different concerns of the App and the subSubstores are handled by their own reducers.

Then I dispatch an action which I know will only update substore2:{subSubstore6}.

Why not simply copying pointers substore1, substore3, ...substore9 and now calling the reducers for the other subSubstores?

Would it be possible to to tell somehow combineReducers which possible actions will trigger new states in their child reducers?

Or perhaps I should consider using different Stores?

Any advice?

Thanks!

Thanks @sompylasar. My question is a bit different.

I'm sure that the updated values belong to the Store because they are a fundamental part of the App.

Say we have a store such as this one:

store: {
  subStore1: {
    subSubstore1: {}
    ...
    subSubstore10: {}
  },
subStore2: {
    subSubstore1: {}
    ...
    subSubstore10: {}
  }
...
subStore10: {
    subSubstore1: {}
    ...
    subSubstore10: {}
  }
}

(I hope you get the idea). In fact this could go even further in depth.

All substores correspond to very different concerns of the App and the subSubstores are handled by their own reducers.

Then I dispatch an action which I know will only update substore2:{subSubstore6}.

Why not simply copying pointers substore1, substore3, ...substore9 and now calling the reducers for the other subSubstores?

Would it be possible to to tell somehow combineReducers which possible actions will trigger new states in their child reducers?

Or perhaps I should consider using different Stores?

Any advice?

Thanks!

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jan 28, 2016

Collaborator

You might want to look at https://github.com/omnidan/redux-ignore.

Collaborator

gaearon commented Jan 28, 2016

You might want to look at https://github.com/omnidan/redux-ignore.

@landabaso

This comment has been minimized.

Show comment
Hide comment
@landabaso

landabaso Jan 28, 2016

I didn't know about redux-ignore and it definitely looks very promising!

Perhaps this is a possible way to go when developing very large apps (SPA) that deal with orthogonal concerns.

Thanks for the advice!

I didn't know about redux-ignore and it definitely looks very promising!

Perhaps this is a possible way to go when developing very large apps (SPA) that deal with orthogonal concerns.

Thanks for the advice!

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Jan 28, 2016

Member

But since I am getting very frequent updates, then all my reducers (which will be a lot) will have to be called.

Let me make a important correction: There is only one reducer function on a Redux store. You can break up that function however you'd like, making a tradeoff for convenience, speed, or other factors depending on your needs. combineReducers is a common way to handle this, but it's not required.

One potentially performant idea I have is to map type to an object of handlers, keyed by type. Here's a naive way of doing it:

function buildReducer(handlers) {
  return function reducer(state = initialState, action) {
    return handlers[action.type](state, action);
  }
}

Then you would only be running the one reducer per type. There isn't a 1:1 mapping of actions to reducers, so you might need to reduce your reducers for each type, but you'll still be lowering your amount of overhead.

Of course, DOM operations and React rendering is probably going to be slower than some plain JS in your production apps. Plus most JS engines will treat your reducers as hot code and JIT compile them to native code, so I wouldn't expect it to be that slow in practice.

Member

timdorr commented Jan 28, 2016

But since I am getting very frequent updates, then all my reducers (which will be a lot) will have to be called.

Let me make a important correction: There is only one reducer function on a Redux store. You can break up that function however you'd like, making a tradeoff for convenience, speed, or other factors depending on your needs. combineReducers is a common way to handle this, but it's not required.

One potentially performant idea I have is to map type to an object of handlers, keyed by type. Here's a naive way of doing it:

function buildReducer(handlers) {
  return function reducer(state = initialState, action) {
    return handlers[action.type](state, action);
  }
}

Then you would only be running the one reducer per type. There isn't a 1:1 mapping of actions to reducers, so you might need to reduce your reducers for each type, but you'll still be lowering your amount of overhead.

Of course, DOM operations and React rendering is probably going to be slower than some plain JS in your production apps. Plus most JS engines will treat your reducers as hot code and JIT compile them to native code, so I wouldn't expect it to be that slow in practice.

@elado

This comment has been minimized.

Show comment
Hide comment
@elado

elado Jan 29, 2016

You can also debounce renders using redux-batched-subscribe, so in case you have multiple actions in a very short duration, it'll render only once.

See rackt/react-redux#263 (there's a jsbin link there)

elado commented Jan 29, 2016

You can also debounce renders using redux-batched-subscribe, so in case you have multiple actions in a very short duration, it'll render only once.

See rackt/react-redux#263 (there's a jsbin link there)

@jorgecarmona

This comment has been minimized.

Show comment
Hide comment
@jorgecarmona

jorgecarmona Feb 2, 2016

In the case of a large app that receives pushed data, should the data structure live in the state tree?
If push was happening every second, we would end up with thousands of state history right? I do understand that the reducers are just changing a small part of the tree.

A side effect of this would be that the devtools screen would have lots of dom elements and would soon slow down the browser. It comes to a point where it's unusable.

Is this a correct assumption?

In the case of a large app that receives pushed data, should the data structure live in the state tree?
If push was happening every second, we would end up with thousands of state history right? I do understand that the reducers are just changing a small part of the tree.

A side effect of this would be that the devtools screen would have lots of dom elements and would soon slow down the browser. It comes to a point where it's unusable.

Is this a correct assumption?

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Feb 2, 2016

Member

@jorgecarmona That only affects Dev Tools, which aren't used in production. Dev Tools, like most other debugging or development tooling, aren't designed to be performant. The concern here is real world performance.

Member

timdorr commented Feb 2, 2016

@jorgecarmona That only affects Dev Tools, which aren't used in production. Dev Tools, like most other debugging or development tooling, aren't designed to be performant. The concern here is real world performance.

@jorgecarmona

This comment has been minimized.

Show comment
Hide comment
@jorgecarmona

jorgecarmona Feb 2, 2016

@timdorr Agreed.

So back to real world performance. Should the data that is streaming into the app every second be accumulated in the state tree? Does this approach scale?

@timdorr Agreed.

So back to real world performance. Should the data that is streaming into the app every second be accumulated in the state tree? Does this approach scale?

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Feb 2, 2016

Member

If you're just appending to an array and it never becomes sparse, then it should be. You might run into some O(n) performance, but you can also solve that via checkpointing and breaking things into sub-arrays. I don't think that's going to be your bottleneck as much as the frontend DOM maintenance would be.

Member

timdorr commented Feb 2, 2016

If you're just appending to an array and it never becomes sparse, then it should be. You might run into some O(n) performance, but you can also solve that via checkpointing and breaking things into sub-arrays. I don't think that's going to be your bottleneck as much as the frontend DOM maintenance would be.

@jorgecarmona

This comment has been minimized.

Show comment
Hide comment
@jorgecarmona

jorgecarmona Feb 2, 2016

@timdorr I will do some tests with those recommendations.

Thanks for taking the time to answer!

@timdorr I will do some tests with those recommendations.

Thanks for taking the time to answer!

@pedramphp

This comment has been minimized.

Show comment
Hide comment
@pedramphp

pedramphp Feb 24, 2016

@gaearon @timdorr react-ignore helps to skip some reducer subtrees for some actions so we have minimal update in our state tree.
But how about all those components that have been subscribed to state changes.
They will get notified even though if the state associated to the component haven't been changed.

Is there away to avoid it ?

@gaearon @timdorr react-ignore helps to skip some reducer subtrees for some actions so we have minimal update in our state tree.
But how about all those components that have been subscribed to state changes.
They will get notified even though if the state associated to the component haven't been changed.

Is there away to avoid it ?

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 25, 2016

Collaborator

react-reducer helps to skip some reducer subtrees for some actions so we have minimal update in our state tree.

Not sure what you are referring to. I’m not aware of such project. Did you mean https://github.com/omnidan/redux-ignore?

But how about all those components that have been subscribed to state changes.

Use https://github.com/reactjs/react-redux which takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.

Collaborator

gaearon commented Feb 25, 2016

react-reducer helps to skip some reducer subtrees for some actions so we have minimal update in our state tree.

Not sure what you are referring to. I’m not aware of such project. Did you mean https://github.com/omnidan/redux-ignore?

But how about all those components that have been subscribed to state changes.

Use https://github.com/reactjs/react-redux which takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 25, 2016

Collaborator

I am closing this thread because the original discussion has quieted down.

Collaborator

gaearon commented Feb 25, 2016

I am closing this thread because the original discussion has quieted down.

@gaearon gaearon closed this Feb 25, 2016

@pedramphp

This comment has been minimized.

Show comment
Hide comment
@pedramphp

pedramphp Feb 25, 2016

@gaearon

Use https://github.com/reactjs/react-redux which takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.

I read the following in the doc.

mapStateToProps(state, [ownProps]): stateProps: If specified, the component will subscribe to Redux store updates. Any time it updates, mapStateToProps will be called. Its result must be a plain object*, and it will be merged into the component’s props

Just wanted to fully understand that what's going on under the hood here, So if the Redux store gets updated but one specific component state hasn't changed, Redux won't trigger the forceUpdate() method for that component.

If the answer is yes, you have made my day :)

@gaearon

Use https://github.com/reactjs/react-redux which takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.

I read the following in the doc.

mapStateToProps(state, [ownProps]): stateProps: If specified, the component will subscribe to Redux store updates. Any time it updates, mapStateToProps will be called. Its result must be a plain object*, and it will be merged into the component’s props

Just wanted to fully understand that what's going on under the hood here, So if the Redux store gets updated but one specific component state hasn't changed, Redux won't trigger the forceUpdate() method for that component.

If the answer is yes, you have made my day :)

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 25, 2016

Collaborator

React Redux does not use forceUpdate() at all. It’s much more careful :-)
Yep, it won't be causing unnecessary updates.

Collaborator

gaearon commented Feb 25, 2016

React Redux does not use forceUpdate() at all. It’s much more careful :-)
Yep, it won't be causing unnecessary updates.

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Feb 25, 2016

Contributor

More specifically: the wrapper component generated by React-Redux's connect() function does a several checks to try to minimize the number of times your actual component has to re-render. This includes a default implementation of shouldComponentUpdate, and doing shallow equality checks on the props going into your component (including what's returned from mapStateToProps). So yes, as a general rule a connected component will only re-render when the values it's extracting from state have changed.

Contributor

markerikson commented Feb 25, 2016

More specifically: the wrapper component generated by React-Redux's connect() function does a several checks to try to minimize the number of times your actual component has to re-render. This includes a default implementation of shouldComponentUpdate, and doing shallow equality checks on the props going into your component (including what's returned from mapStateToProps). So yes, as a general rule a connected component will only re-render when the values it's extracting from state have changed.

@pedramphp

This comment has been minimized.

Show comment
Hide comment
@pedramphp

pedramphp Feb 25, 2016

👍 Sweet.

👍 Sweet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment