Skip to content

Improvements to Onyx.get  #2762

Description

@kidroca

If 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: Improvements for Onyx connect react-native-onyx#63 (comment)

Solution:

This was widely discussed in the following places and it was decided to go with the cache solution:

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: Expensify/react-native-onyx#65 (comment)

  1. 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...)
  2. 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
  3. Use the same ENV var from above to expose a readCollectMetrics and resetMetrics as an Onyx method
    • you will be able to further aggregate data like: how much calls were there for get with the a specific key
    • this can also include information about how much data is currently in cache, after the update is made

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:

View all open jobs on Upwork

Metadata

Metadata

Assignees

Labels

ReviewingHas a PR in review

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions