5

Business Context of the Problem

Our iphone app allows users to pay merchants and earn rewards.

Users can also do things like:

  1. View transaction history
  2. View their points and available rewards, or claim rewards.
  3. Earn money by referring friends, view a history of converted and unconverted referrals.
  4. View running deals at some merchants (happy hour specials, etc)
  5. View information about merchants (location, hours, etc)

Since things like available deals or rewards, transaction history, and referrals can change at any time, we have a classic cache invalidation problem on the user's device.

Current, Unsatisfactory Solution

Our current architecture is fairly standard, and consists of API endoints like "/merchants", "/rewards", "/transactions", "/deals", and so on.

We currently solve the stale cache problem by:

  1. Polling the server frequently while the app is open (as often as every minute for some API calls), as well as whenever the app regains focus.
  2. Indicating possibly stale data with text like Last updated 3 mins ago

We use etags to avoid resending unchanged data whole hog (eg, the entire merchant list), but there is still a lot of wasted bandwidth with our current approach. For example, if a single merchant changes, all merchants are resent. If a single new transaction is added, all transactions are resent.

In practice, from the user's perspective, this usually isn't a big deal, as we're talking about sizes of 1-200K at the very most, and typically less. However, it can be a problem in areas with poor cell reception, a situation that comes up often enough that we hear complaints.

And on the server side, all this polling creates a lot of unnecessary load, especially because processing and database queries occur even when etags are unchanged,

Better Solutions?

We are re-architecting the app and the server code, and thinking about alternative strategies. Since this is a fairly common problem, I'd like to know how others are solving it.

Some ideas I have so far:

  1. For minimizing data transfer: Keep track, on the server, of the data each user actually has. Send JSON patches of only changed data, rather than fresh copies of all the data. Copies of each user's data could be stored in redis, and expire after a few days or a week. This would keep "active session" fast, while user's without a stored server cache could just be sent a fresh copy of all their data when logging in after a dormant period (same as now). While in theory this could work, keeping server and client copies in sync, by patching both, seems like a rich source of potential bugs, even if I'm using vetted JSON patch libraries.

  2. For minimizing server load: Establish web socket connections with each client, instead of polling. Let events that change the client's state (eg, a purchase, a referral converting, a deal ending, etc) push down websocket messages, which in turn trigger the client to refresh. This could be used in conjunction with idea 1, or with the current method of sending fresh copies of all changed data. Even with the current method, this would still be a huge improvement, since data would only be requested and sent when it needed to be.

Right now, I am leaning toward 2 without 1, as 1 seems too potentially fraught with problems. I have some concerns about relying on websockets on mobile, but after doing research here, on SO, and other places, it seems that this concern isn't valid.

I would love to hear other, perhaps radically different, suggestions for solving these problems.

  • Do you ever know in advance that some deals will be valid for at least X days while the others might expire today? Or can any deal be cancelled at any moment without warning? – Ixrec Dec 29 '15 at 10:42
  • Usually deals have set start and end times, but they can be cancelled at any time (eg, too many are being redeemed, or the merchant runs out of stock). Even if this weren't the case, though, things like transactions, referrals, and reward claiming can happen at any moment. – Jonah Dec 29 '15 at 13:43
2

Since it's a native app you can sidestep HTTP entirely if you want to.

You could use HTTP/2.0 which allows streams. Streams are a partial solution to head of line (HOL) blocking allowing multiple requests to share a single TCP connection and be delivered incrementally and concurretly. It also allows for some server-initiated communication. It's still TCP so it isn't a complete solution to HOL blocking.

You could use WebRTC which uses DTLS to avoid HOL blocking entirely and enables server or peer to peer events efficiently.

I'm inclined to avoid HTTP for this kind of use case because it places artificial and unsympathetic constraints on your application data request flows. It's a necessity in the browser, but not in an app. HTTP does have the advantage of providing a single service interface for all broswer based, desktop, and mobile app clients though so take this with a grain of salt.

You could use AMQP over TLS (with something like RabbitMQ) on port 443 and do event-based communications via its message queues. Backend services would push updates into the queues or an intermediary updater service would call the REST services and update them. This would still suffer from HOL blocking at the TCP level.

Another option is ZeroMQ with CurveZMQ. It's also a message-oriented solution, but one in which you don't need a separate central broker like AMQP. Your app could talk directly to your server via messaging. Your server could do the REST polling and state management and push updates to the client as frequently or infrequently as you want. This allows for graceful degradation in failure situations. This is the most flexible and arguably the leanest option, but is also over TCP so doesn't fully avoid HOL blocking.

For what it's worth HOL blocking is unlikely anywhere near your immediate problem and solving it requires significant compromises.

  • 1
    Good stuff, thanks for your answer. Given what you know about my app, which of these solutions do you lean toward? Based on your answer it seems like I should seriously consider WebRTC. Are there downsides to it? Eg, reliability problems or complexity on the server side? – Jonah Dec 30 '15 at 21:42
  • @Jonah WebRTC is interesting. The major factors for me here are: 1) what is your team familiar with? 2) what protocols are best supported on your target platforms?. I lean towards ZeroMQ for conceptual simplicity, but it looks like builds might be fiddly so your team needs to be comfortable with native library linking. WebRTC is really compelling but you need to be very careful of the trust model between peers if you let them send data or conmands directly to each other. – Alain O'Dea Dec 30 '15 at 21:52
  • @Jonah I found the High Performance Browser Networking book very helpful on these topics. It goes all the way from kernel, to TCP, to TLS, to HTTP/1.1, HTTP/2, to WebRTC and talks tuning at each step. The mobile tuning stuff, particularly around battery life, was very interesting. – Alain O'Dea Dec 30 '15 at 21:57
  • 1
    Target platforms are ios and android. We're building for the long haul, so putting in up-front learning effort is okay as we're a small team. I just want to make the best choice. I definitely value conceptual simplicity, and generally am willing to trade performance to achieve it (within reason, ofc). That is to say, ensuring that this syncing and update code is bug free and easy to maintain is priority one. Making it fast is priority two. Could you explain your comment about the peer trust model? We'll only have users communicating with our server, fwiw... – Jonah Dec 30 '15 at 22:04
  • 1
    Finally, how do zeromq and webrtc compare with websockets in terms of speed and reliability? – Jonah Dec 30 '15 at 22:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.