-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State: Refactor state persistence #9957
Conversation
Doing it in a worker would be really interesting. What are the limitations
on storage there?
…On Fri, Dec 9, 2016, 7:11 PM Andrew Duthie ***@***.***> wrote:
Performing persistence in a web worker might also make it more reasonable
to consider using something like jsonpack
<https://github.com/sapienlab/jsonpack> or lz-string
<https://github.com/pieroxy/lz-string> for compression. @blowery
<https://github.com/blowery> I think you may have mentioned one or the
other of these in the past?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9957 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAA4DghaYNxbsIQcRg1kERytX-P0-RzBks5rGe4qgaJpZM4LJerm>
.
|
@blowery Based on the following, it would seem that
Edit: A comment on this answer suggests IndexedDB is unavailable in Safari web workers. We might need to investigate that. Perhaps |
Another related avenue to explore may be putting the reducer execution inside a web-worker, assuming we're not expecting it to run synchronously (let's hope). |
STATE_REPLACE: 'STATE_REPLACE' | ||
}, | ||
throttle: 500, | ||
maxAge: 604800000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we express this in terms of some unit? appears to be 7000 days?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we express this in terms of some unit? appears to be 7000 days?
I guess @gwwar was on to something with her implementation 😄
The time is 7 days (Date.now
is measured in milliseconds since epoch, not seconds)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the minimum a comment would help, though those can drift over time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though those can drift over time.
like any clock! hehe
queue.push( action ); | ||
} | ||
|
||
getState().catch( noop ).then( ( persistedState ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the catch( noop )
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the
catch( noop )
?
Since we need to replace the reducer and replay the queued action regardless of whether we were able to load anything from persistence.
Though, given we're never handling errors in either the getState
or below in setState
, maybe this should be swallowed in worker.js
instead of here.
|
||
function createWorker() { | ||
const blob = new Blob( [ ` | ||
importScripts( 'https://cdnjs.cloudflare.com/ajax/libs/localforage/1.4.3/localforage.js' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be useful to define this in an actual js file and pull that in using webpack's raw loader. maybe with a .worker-js
extension or something to avoid the js / babel loader?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be useful to define this in an actual js file and pull that in using webpack's raw loader. maybe with a
.worker-js
extension or something to avoid the js / babel loader?
Yeah, I'm thinking we'll probably have to create a separate bundle that includes the worker handler logic and which depends on our lib/localforage
module. Most examples of workers load from a standalone script file, not generated as a Blob like here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really cool. |
maxAge: 604800000 | ||
}; | ||
|
||
const DESERIALIZE_INIT = '@@DESERIALIZE_INIT'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should namespace it to @@calypso/DESERIALIZE_INIT
to make it clear that it's our internal event that should not be handled.
Interesting idea here. We played around a bit with service workers last year at a meetup, but it didn't have the best browser support, and was a nightmare to try and update the scripts. So we ended up with a lot of zombie workers. (It was very heavily cached). With that thought, would changes to the web worker ever require us to flush our local cache? If so, perhaps we should have some mechanism in place to detect this.
Very cool stuff. I haven't looked at web workers in a while, but one more thing to consider is server state for SSR pages. Currently this is being passed down on the window object, which I believe the workers do not have access to. Server state should override any cached local state, but should defer to any fresh data. I'll need to step through this, but we should take care not to introduce any regressions here.
This makes a lot of sense, though we'll likely need to perform an audit and make sure our current data structure can handle this. eg, making sure parts of the tree don't make assumptions on what's available, and that we don't load an inconsistent application state. |
For Safari it sounds like we can only rely on message passing and we do not have access to localStorage/indexedDB/webSQL. Some folks also worked around this by using a web worker polyfill. |
After some inclinations that the actual serialization was a major source of delay in the persistence I had another idea I would like to explore: a tradeoff between lag and memory consumption… Instead of serializing the state tree in the main thread, why not simply create a duplicate store in a web worker and send every single Redux action across the gap to that persister thread. it could batch those actions and then replay them at intervals large enough to fit the serialization and storage too (for example, we could queue the actions and then fire them off in succession every 1000ms, then store the tree to core to this idea is the suspicion that we can significantly reduce the lag introduced if we serialize hundreds of tiny objects (Redux actions) spread out over time vs. serializing a huge object (Redux state tree) at a single point in time. my plan has been to code up a PR but we see if that ever happens… |
I think that's a really neat idea @dmsnell . My main concern which you noted would be implications of memory usage of having two copies of the store. We should do some measurements to see just how large the state object is after some common usage, because it seems.... big. Another thing I'd meant to look at in getting back around to this is easier integration of web workers with |
@dmsnell I'll close this as being severely out-of-date. There is still value in the objectives here, but they could be split into separate tasks, reflected by items noted in the original comment:
I don't believe that opt-in persistence has any notable impact on any of these goals. |
This pull request seeks to make a number of enhancements to Redux store persistence:
Implementation notes:
WIP: This a very early prototype, and is not very functional at the moment.
Unfinished:
localForage
inside the webworkerlib/localforage
moduleFinished:
Related refactoring includes:
createStore
between server and browser to minimizetypeof window
conditionsstate/reducer.js
fileTesting instructions:
TBD
cc @gwwar