-
Notifications
You must be signed in to change notification settings - Fork 14
HIGH IMPACT: Cache the results on the phone (both platforms) #21
Comments
Assuming you're referring to this screen,
To address the reasons you picked the current solution:
I imagine it would be fairly easy to have the backend simply directly send the data it's using to create that HTML rather than sending the HTML itself. And I don't expect implementing this natively would take much time since we have frameworks doing all the heavy lifting. |
Any sort of code change will need to pass App Review (obviously that includes small things, like this HTML caching), so we might as well do the fix correctly. Keep in mind that using any major web views puts you at the mercy of a 10.6 rejection. (Yes, 10.6 is incredibly broad). As you know, App Review can be very inconsistent. Couldn't we simply add a Boolean in the JSON we sync to the client, telling the device which view (graph vs. game) to show? And we could also sync over the URL of the image we want to show for the game, to minimize client-side hardcoding and maximize future flexibility. The backend could simply manage which stage of the game a user is in (which I'm sure it does right now) and translate that to an image URL. And, of course, we wouldn't break previous versions of the iOS app--it's easy to simply version the API. The email address I'm using is wdaareg@gmail.com. Do you expect the researchers' need to change frequently and without notice? |
Agreed. But once we have the HTML caching in place, we don't need client side code changes in order to change the result UI.
We could, and that's what happens on the server side (at a very high level). But note the implication that we need to have the code for every single potential result in the client then. With the webview, we can come up with completely new client views (maybe a set of animated images) without having the modify the client view in any way.
In our case, these are:
Let's say we had somebody from the ITS team come by and say that they wanted to deploy a study in the next 2-3 weeks that had a game with some more graphs on it, so that when you clicked the score, it showed you the components of the score. If we were doing native result views, what would you say to them? |
I don't know for sure, since so far, our deployment rate is close to zero. We had one pre-survey and they decided not to use smartphone collection for the full survey. But even for the pre-survey, they had a requirements around the results and the filtering, and they would bring them up at arbitrary times, and they would not make progress on the deployment until they saw the results. Thanks, |
This all makes sense. I think it's just very weird for me to work on a mobile app where building the best user-centric product is not a goal, but these are also very different constraints than usual. I will look further into the best way to execute your caching solution. |
Yeah, I think that this is a change is focus from building an app to At the same time, you have certainly raised my awareness towards looking If you are open to it, I'd be interested in brainstorming further about On Tue, Feb 10, 2015 at 3:40 PM, Neeraj Baid notifications@github.com
|
Is there a strong reason why we are using HttpClient rather than HttpUrlConnection for displaying the result summary? There's a handy platform-provided cache, the HttpResponseCache that seems handy for this issue. However, it doesn't work with HttpClient, only HttpUrlConnection. See: http://developer.android.com/reference/android/net/http/HttpResponseCache.html Also, Apache HttpClient should be considered deprecated for anything later than Gingerbread. |
If I don't have a comment explaining it, there is no strong reason. Feel free to change to HttpUrlConnection. I'll look through my previous commit logs (this project was initially hosted internally, and then moved to github without the previous commit history) to confirm that there is no deep reason. |
After working for some time with the HttpResponseCache I learned that POST requests are not meant cached by default. So it seems we have two options, (1) we can change the api s.t. getting the display is done with a GET rather than a POST request. Or (2) we can do some kind of manual caching of the raw html that we receive. That would involve writing that html file to the file system and then checking for staleness manually. I am not actually sure how this was accomplished on iOS, because I think the problem would be the same in that case. The problem being that each time the view is loaded, a new request must be made because the previous request was not cached. Of the two options, I think that (1) changing the api call makes more sense. We are only getting data with the /compare call, and there are no server-side side effects, so a GET request makes sense. |
A POST request isn't the right way to "get" information. We are retrieving information from the server, so technically we shouldn't even be using a POST request for this task. It also makes sense that POST request can't be cached. I propose that we fix it by going with option 1) |
Aha! Great detective work, Zack. This might be why the cache didn't work for Neeraj. He said that he got the He probably tried to do a "GET" on the compare URL, which would obviously He worked around it by loading the WebView in the background as soon as the On Wed, Feb 18, 2015 at 6:43 PM, zacatac notifications@github.com wrote:
|
@neerajbaid what happens when you close out the results pane by traveling to another screen (e.g. settings) and then come back to the results screen? Aren't you reloading each time you go through a flow like that? |
Also, my apologies for not catching the non-cachableness of POST requests during our earlier discussions/design review. |
No worries. This gives us the chance to look at some different approaches. One idea to compensate for this is to have some kind of persistent data structure that stores the data that we receive from the /compare call. That way we can display the data immediately, and then update once the request completes. I suppose this would interfere with the customizability of the views though since only data and not visualizations would be sent back and forth. |
This was what I suggested originally because even with a HTTP cache, the phone still has to contact the server to determine freshness. If we have multiple javascript files, that is multiple liveness checks (client -> server calls) happening in user visible time. Depending on how the cache is implemented, it also means that the results could unusable if the network connection is spotty or absent because the phone won't be able to contact the server. Do you know whether the cache returns existing values if the server is unreachable? It didn't seem like this would be too much work - all the javascript files are defined upfront in the headers, so you just need to parse the html HEAD tag to recreate the structure on the server and then load from disk. But I think that it might be too much work at this stage in the project. |
I don’t think that the cache will contact the server to realize freshness. Since the cache is stored phone-side we can specify how long the cached requests stays fresh. That value is then stored at the phone-side cache and checked whenever we make a new request to that same resource, so until it is stale we wouldn’t make external requests. I suppose that the trouble with storing the html file locally is that we basically need to reimplement what the httpResponseCache handles for us, which I imagine will be quite a lot of work. At least on iOS Neeraj was saying that this is extremely difficult. On Wed, Feb 18, 2015 at 7:40 PM, shankari notifications@github.com
|
Good point on GET versus POST. But if you recall our discussion in class, this particular method returns privacy sensitive information, so we want to ensure that it is called with a JWT and I picked POST because we were "sending" a JWT and the response was to retrieve the user specific information. We clearly don't want to send the JWT as a query param - a snooper is going to be able to read that user's information until the JWT expires. It also ensured that there was a clear separation between methods that returned user specific data and methods that returned public data. Having said that, there might be a way to pass the JWT in the GET headers. I have to go have dinner with my family now, but maybe you could spend ~ 10-15 minutes playing around with what would happen if it did become a GET. Change the route on the server side, and try to put the token into a header and see if the cache works then. If we know that it works technically, we can argue the philosophy later :) |
Sounds good. We will get started on that. |
Great. Remember: no token in query params. But I think that the header route should work. Also, you need to change the code that validates the JWT to read it from the headers instead of the post message. @jesca knows exactly where the JWT is validated :) |
So my understanding is that using GET with the standard Just trying to understand the landscape for how to fix this... |
Yeah that's correct. — On Thu, Feb 19, 2015 at 9:10 PM, shankari notifications@github.com
|
I think we will have to just cache the data in our own cache, and then update that data whenever a request completes. — On Thu, Feb 19, 2015 at 9:10 PM, shankari notifications@github.com
|
Let's see what the outcome of the email thread around my email to the security grads group is. Because that was what confused Prof. Culler today morning - he thought we were trying to cache only the data, and was suggesting that we could send a script to the client as well. Although it looked like he didn't like javascript as the script very much... |
@neerajbaid so I think that your cache didn't work initially because POST requests are not cached. If we switch to GET with custom headers, it seems like the cache should work on iOS as well. Or is there some other technical reason that makes it hard to use the iOS cache? Because your solution has the drawback that if a user launches the app just to see the results, maybe if there are no trips, then it doesn't help at all. And we want to make our results interesting enough for people to use, right? |
To recap some more of Prof. Culler's suggestions while they are still fresh in my mind. The main goal here is to display a result screen whose format can:
We can do this by sending data + script from the server to the client. The script need not be javascript (although I will argue that this is the easiest since there is a built-in interpreter in the WebView). But even if we assume that we are going to display HTML + javascript, it does not imply that the server has to return HTML + javascript that you parse to get the files. Instead, the server could return a custom data structure that listed all the components of the result e.g.
And the client could parse that data structure easily, store the components in a cache, and then load from disk. It didn't need to parse the HTML directly, with all the complications that it entails. @neerajbaid, @zacatac, @jesca do you remember something different? |
Yeah that's what I got too. I have never used a mobile app with a UI this customizable, and I imagine there is a reason for that (namely difficulty). But the principle of sending data back and forth along with Resource files seems good. Maybe a first step could be to make the api only send the data, and then store the default histogram view on the phone and populate the view with data when it arrives. Then we could build up the customizability aspect from that starting point. — On Thu, Feb 19, 2015 at 10:50 PM, shankari notifications@github.com
|
It looks to me like there are a couple of options here:
First, note that (3) is a generalization of (2) - in (2), the data structure returned is the HTML file. Which one do you want to work on? I said earlier that this is a high impact change, so I would be willing to help with the implementation, and that offer still stands.
Well, the result screen is already customizable using the WebView, and includes options for "data" and "game", so reverting to storing the default histogram view on the phone would be a step backwards. I would rather do the HTTP GET with custom header solution than go backwards to a non-customizable view. |
I suppose there is a security reason that HTTP GET with Authorization headers is not cached, but if our tokens are short lived enough (I think you said ~15 minutes), that at least alleviates the security issue a bit. As of now the phone-side code does exactly what (1) describes. It will be easy to change the server side code to look into the custom header field instead of json. So pending some more thought on the design of (2) or the more generalized (3), I can make a pull request for solution (1). Does that sound like a good plan? On Fri, Feb 20, 2015 at 2:28 PM, shankari notifications@github.com
|
Yeah I think that is reasonable. Can you outline a testing strategy for (1)? In particular, it would be good to not only test that the caching works, but that the invalidation works - that is, if the script does change on the server side, the new results are displayed. Also, @neerajbaid, once we switch to GET, you won't get the "spammer" message any more. Are there technical reasons why a cache won't work on iOS? |
In particular, it would be really nice to have the same solution on both platforms (HTTP cache) if possible, for increased maintainability. |
Do you have a suggested cache timeout? I will set it to 2 hours for now, but this can always be updated. |
2 hours is fine - that's the interval at which we currently pull trips from the server. |
Okay, sounds good. On Tue, Feb 24, 2015 at 11:22 PM, shankari notifications@github.com
|
@zacatac so what happens once the cache is invalidated? Will there be a long delay at that time, or will the old results still be returned while the new data is fetched in the background?
|
In that case the result will be displayed slowly. The cache considers the data to be invalid so it won't return the necessary html files. There aren't enough knobs on the built in cache to tune it to be best effort. We could however mimic best effort by setting the maxStale value to the max (4weeks) and then manually track the last time the data was updated. Then we can begin requesting updated data and only flush the cache when we know the server has returned the updated data. That would take a bit more work, but it's something to keep in mind. — On Tue, Feb 24, 2015 at 11:54 PM, shankari notifications@github.com
|
How about this simpler solution? Since the background sync on android is pretty reliable, is it possible to do the following on every background sync:
This way, in the normal case, the cache will be refreshed in the background without affecting user-perceived latency. If the background sync is not invoked for a long time, we will fall back to a foreground fetch and a user-perceived latency. |
As far as I can deduce from the HttpResponseCache documentation:
|
@zacatac, actually, I don't think that you need the last of those, since you will be using that property by default for the regular calls. So I think that adding a call to retrieve the data with forced validation
in the background fetch method Basically, this will ensure that the cache is refreshed every 2 hours. And the cache expiration is 6 hours. So whenever the user accesses the cache, it will always contain fresh data. |
I just want to step back and put what we are doing here in the context of the higher-level design. We are not figuring out a way for users to load random web pages more quickly. Instead, we are figuring out a way to sync the results onto the phone in the background so that they are visible to the user instantly. Think "coda paper". Since our results are currently displayed in HTML, we are able to use a built-in cache for the details of the sync. But we really want to invoke the sync in the background on a periodic basis so that the user is able to view them without any delay. |
While theoretically that would be great, you know that there is no reliable way to wake the app up in the background to do long running fetch operations without remote notifications (even then I'm not sure if the system gives us enough time depending on network quality). So, the next best option is loading the webpage earlier so it seems quicker to the user, which is what I'm doing. With backend changes allowing, I will now be able to cache this for x hours. It was previously my understanding that this was an acceptable solution, doesn't sound like that's the case any more. |
There are definitely differences between ios and android that affect that I think that we should aim for the ideal design on android where it is On iOS the background loading + cache is an acceptable compromise. Shankari
|
Made pull requests for server and phone side |
Our results are currently computed on the server side and sent over to the client at the following times:
In both these cases, the phone makes a HTTP POST call to the "/compare" endpoint, which returns a HTML page. The page is then displayed in a webview on the appropriate platform.
This was an early attempt by me to work cross-platform, and to:
This works great, but is very slow, so slow that a lot of times, people just don't wait for it to finish loading. Part of the slowness was due to the time taken to compute the results, so I checked in a fix to pre-compute the results daily on the server side.
issue: https://github.com/e-mission/e-mission-server/issues/23
fix: e-mission/e-mission-server#25
However, even after that fix, the result view load is slow. I suspect that this is due to the time required to retrieve the associated .css and .js files. I had hoped that these would be cached on the phone, so the retrieve time would be negligible, but it looks like even the version check required to determine whether a new version is available takes time, specially since there are multiple of these checks (see snippet below).
Pre-fetching the results to the phone should move this delay to the background and result in a more responsive UI for the user.
The idea is that you would add this additional POST call to the existing remote sync mechanism
store the result on the local disk, and then change the display code load from local disk instead.
One thing to watch out for: the returned HTML document currently has relative references to javascript code, i.e.
<script src="clients/choice/front/ionic.bundle.js" charset="utf-8"></script>
so you would need to parse the returned HTML, issue the appropriate GET calls as well and store those in the appropriate locations on the local disk as well.
The text was updated successfully, but these errors were encountered: