CornerJob has had an exponential growth since it was created over a year ago. Today it has more than 1M active users and generates more than 50k requests per minute of throughput.
To be able to maintain this growing rate without harm the UX, we decided to implement a powerful local storage layer in both Android and iOS platforms. The main objectives are:
- Reduce significant the number of request to backend to improve the platform performance.
- Reducing the number of requests implies that devices running the app will decrease network data and battery usage.
- Improve the UX, allowing users to navigate through the application although backend or network are not available. Furthermore, we could use it to store certain user's actions to perform them when the services come online again.
CornerJob's use case
In CornerJob we work with different entities that have different time to live (TTL):
- Inscriptions expires after 24 hours.
- The timeline of every user is filled with different offers, depending on the user's geolocation.
- Entities with low modification rate.
Because of this, we had to think in a cache system that allow us to have entities with different TTLs:
- Session TTL: period of time while data is valid.
- Cache TTL (or LTTL): period of time while data would not remove from cache. In our case, always LTTL > TTL.
Which DB engine fits better on CornerJob?
Ok, we have to implement a local storage system, and we already know the motivation. Now, the million dollar question is: Which technology fits better with our use case?
Today, there are a lot of options available in market to help us to save data locally. Some are platform specific, and some other are multiplatform:
Our choice was Realm, but... Why? Well, it wasn't an arbitrary or capricious decision. It has some key features that we need:
- Multiplatform: which means common db files, and allow us to define common solutions. Also exists both well maintained SDKs,
- Possibility of having multiple db files (called realms) within same application.
- Copy objects between realms.
- Notifications: UI can be reactive to data changes.
- Very good documentation.
- Very high performance with primary key queries.
- Transactional engine.
- Easy to use encryption API.
- Easy to integrate with some other third party commonly used libraries, like Gson, Retrofit or RxJava.
- Multithreading, which means that changing an object in a thread are reflected in the others.
Clear cache policy
Every time the app is started, we erase all data that aren't valid anymore, with the main objective of maintain the cache as optimal as possible.
Data request policy
When we have to make a request to backend, we follow a flow during which we make a series of decisions depending on the cache's state, taking into account both TTL we talk before, TTL (session TTL) and LTTL (cache TTL).
With this cache system we are able to give a good and smooth UX, maintaining our data sufficiently up to date (TTL) at the same time that we significantly reduce the number of request. In addition, after a minimum use of the app, we have enough data in cache (LTTL) to provide a complete experience even if the server is not available.