Improvements to `Onyx.get`
See original GitHub issueIf you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
Problem:
- Each time a component “connects” to Onyx we read that data from disk before returning it to
withOnyx
. - When multiple components ask for the same key at the same time (e.g. during init | switching chats) the key is requested multiple times from storage
- This causes a lot of unnecessary traffic through the native bridge and adds unnecessary CPU / memory usage
- Confirmed with the following manual benchmarks: https://github.com/Expensify/react-native-onyx/issues/63#issuecomment-832002373
Solution:
This was widely discussed in the following places and it was decided to go with the cache solution:
- Improvements for Onyx: https://github.com/Expensify/react-native-onyx/issues/63
- Benchmark Onyx: https://github.com/Expensify/react-native-onyx/issues/65
- Latest slack thread: https://expensify.slack.com/archives/C01GTK53T8Q/p1620065578311900
Prevent unnecessary reads from disk
When a request is made to retrieve something from disk capture the task that would resolve with the read data, as more calls for the same key arrive, instead of making a separate call for the same thing redirect the reads to be resolved from the already pending task. This way only one round trip would ever happen for a given key. As soon as the data for the first call is available - all calls are resolved at once.
Cache and lazy loading
As data is read from file, keep a reference/pointer to the data in memory in a cache dictionary Fill the cache lazily - only after a key was requested and read from file Remove cache entries after the last connection for the given key is disconnected
Additional Work
✔️ Should we implement any benchmarks / metering?
The general principle I like to apply for a benchmark is through method decoration like the same here: https://github.com/Expensify/react-native-onyx/issues/65#issuecomment-821027845
- Create a metrics capturing function that tracks call and response information
- call start/end time
- call execution time
- arguments for the call
- tag returned objects so we can see how much of them are still in memory (Or just tag the
cache
map since it should pretty much hold the same information - thought this is only possible after the update that adds cache…)
- Use a ENV variable (let’s say
ONYX_BENCHMARK
) to apply the metring function through decoration to a list of Onyx methods- in order to work the methods in the list need to return a promise or have a callback that is triggered when the call is over
- e.g. the base methods like
get
,set
,merge
would pretty much work out of the box
- Use the same ENV var from above to expose a
readCollectMetrics
andresetMetrics
as an Onyx method- you will be able to further aggregate data like: how much calls were there for
get
with the a specifickey
- this can also include information about how much data is currently in cache, after the update is made
- you will be able to further aggregate data like: how much calls were there for
Expected Result:
Onyx does not try to retrieve data that is already available in memory
Actual Result:
Onyx will always ask data from AsyncStorage
Action Performed:
N/A
Workaround:
Can the user still use Expensify without this being fixed? Have you informed them of the workaround?
Affected Platforms:
-[x] Web -[x] iOS -[x] Android -[x] Desktop App -[x] Mobile Web
Version Number: Logs: https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL:
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:23 (20 by maintainers)
Top GitHub Comments
Thanks for the ping @kidroca it was my responsibility and I missed it (we previously didn’t leave Contributor Managers assigned to issues, we now do). I paid in Upwork and added the bonus for writing the OP. Thanks!
@tgolen It’s seconds, updated the table