-
Notifications
You must be signed in to change notification settings - Fork 76
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
Use MemoryOnlyProvider
as fallback to allow the app to work without storage
#439
Use MemoryOnlyProvider
as fallback to allow the app to work without storage
#439
Conversation
@chrispader Is this ready to review again? I wasn't sure and I don't want to be the hold-up here. |
ah no, not yet. Going to finish this this week! |
@tgolen @marcaaron i just removed the additional "multi-storage" logic and just fallback to the This whole re-structuring and cleanup as well as this extra "storage" layer comes in handy though, since we can now handle the initialisation phase first, and only then start handling operations. I'm going to test this in the app now, but make sure to review this PR, as it should be ready! |
cache.set(key, value); | ||
keyChanged(key, value); | ||
}); | ||
} |
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.
thought: Why was this moved?
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.
Just to keep the code that basically initiates the Storage layer together. Technically, it makes no difference
lib/storage/index.js
Outdated
* @return {Promise<*>} | ||
*/ | ||
getItem(key) { | ||
return runAfterInit(() => provider.getItem(key)); |
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.
confusion: Why do we need runAfterInit()
?
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.
Since i moved the initialization of the provider to provider.init()
, we need this, to prevent any operations from running before the storage provider actually got intialized/created
init() { | ||
const newIdbKeyValStore = createStore('OnyxDB', 'keyvaluepairs'); | ||
|
||
if (newIdbKeyValStore == null) throw Error('IDBKeyVal store could not be created'); |
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.
What problem specifically does this solve? Do we have any documented cases where createStore()
returned null
? What was the problem we saw? What about when an irrecoverable error happens when trying to save after initializing the store successfully?
(that second situation is why this issue was created in the first place - and I'm not convinced we have solved it)
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.
You're right. Currently i assumed that this or any other error would only occur during initialization.
I updated the PR to handle errors during runtime as well and gracefully degrade the performance of the app. Lmk what you think of the general approach, before actually reviewing it :)
It's still very useful to have this extra storage layer and a cleaned up provider structure.
One more time, i'm asking for you're opinions on this new approach. @marcaaron @tgolen (no need to completely review yet, i'm just curious what you think :)) The current approach handles errors during initialization and runtime, while also utilizing the extra storage layer and the cleaned up provider structure. Still can't really test this in a real-life scenario, since we don't know how to trigger this As In the |
My only concern with this is the chance of false positives. It seems like we are struggling between the extremes of not knowing which errors get thrown or only knowing one error that gets thrown with no way to reproduce it. Maybe this hints that we need better error reporting first so that we can make a better assumption about which errors are actually occurring. |
Yes you're right, it's either this or that extreme case. I don't see any other way right now to check for these errors, unless we're able to confidently reproduce it. After doing some internet research, i'm like 80% sure the
My suggestion here would be to basically have this error check for initialization and every operation ( There really is no official way from |
On the subject of error reporting... I worked on improving it a bit in the past and an internal log search will show us when storage is failing and why it failed (some explanations more cryptic than others). This will give us the ability to catch something like a "critical" vs. "benign" loss of client data (if there is such a thing). With reliable updates, it seems extremely important to ensure storage failures don't lead to data loss at all e.g. if you are able to store one recent update, but for some reason could not store some previous update - the previous update is now lost forever. Maybe I am wrong, but the current system we have depends on a near perfect reliability. Anytime we fail to store something the update chain will essentially be broken. So, I might actually argue that we should always stop storing things immediately when any error happens and address these errors with urgency. |
unfortunately i don't have access to that link.. are you referring to some sort of analytics where we collect data about (the type of) errors occurring (in production)?
If i understand you correctly, this means we would need to implement some sort of "transaction" system, to basically rollback changes if there an error occurs later in a chain of operations? We can definitely check for initialization errors - probably like the original one described in the issue - and degrade performance before any operations are even triggered. But if a fatal error occurs at some other point during Onyx's lifetime, we can only degrade performance from there... meaning after the failing operation. The data will still be stored in cache (in-memory), though that data will obviously be lost after a refresh. Without some other persisted fallback storage solution, i'm not sure if we can prevent this sort of data loss. Maybe i got something wrong, because i'm not totally sure what you are referring to here and if it isn't extending the scope of this PR/issue massively. |
JFYI @marcaaron @tgolen I'm going to be OOO from 31/01/2024 - 17/03/2024, which means i can either complete a (limited) version of this issue fix/PR by tomorrow/tuesday, or i'll have to hand it over to someone else to takeover. From my perspective, i don't think the current state of this PR affects the behaviour of Onyx negatively at all. The current implementation of This PR also includes lots of re-structuring and code quality improvements and paves the way for future TypeScript typings and clean-up. Do you guys think we can merge this (as-is) and work on advanced error reporting and preventing in a follow-up PR? |
I think with such large changes in this PR, and you being gone for so long, I'd actually prefer holding this PR until you are back. Unless someone else is willing and able to take over maintaining it. We've had a lot of issues where Onyx PRs need to be reverted and then it causes as cascade effect on other PRs. I think the risk of causing a regression here is pretty high. The best way to combat that is to split this PR up into multiple smaller pieces. |
I understand! I can hand this PR over to another Margelo engineer and ask them to split it up into more easily reviewable PRs. Otherwise (if this not a priority) i can also continue working on it in March. |
Hmm no I wasn't trying to imply any kind of transaction system (unsure what that would entail). I am just stating a fact - which is that if you are unable to store an update, but allow storing a later update then you will have irrecoverable "gaps" in the client side data. |
Hey, as Chris is OOO pretty long we are internally assigning someone new to carry on this PR. |
Thanks @hannojg. I'm going to close this PR for now then. |
@marcaaron @tgolen
Details
This PR adds a
MemoryOnlyProvider
fallback solution and changes the interface ofStorage
(lib/storage/index.js
) to allow multiple simultaneous storage providers.This PR also improves the structure of everything in
lib/storage
and establishes a consistent structure for providers and order for all the functions within them.Related Issues
Expensify/App#29403
Automated Tests
This PR changes the way we use the storage but the functionality of the library to the outer world is the same. Therefore no new tests were added.
Manual Tests
Verify that no flows and functionality were broken by the changes. Check for console errors regarding Onyx.
Author Checklist
### Related Issues
section aboveTests
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.STYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)/** comment above it */
this
properly so there are no scoping issues (i.e. foronClick={this.submit}
the methodthis.submit
should be bound tothis
in the constructor)this
are necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);
ifthis.submit
is never passed to a component event handler likeonClick
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2023-12-27.at.14.12.34.mov
MacOS: Desktop