TLDR:
What is the difference between the Public, max-age=<VALUE> and
maxage=<VALUE>, s-maxage=<VALUE> cache-control syntax?
Question:
For one of my projects, I am looking to reduce the server load via the Cache-control http-header. This project is hosted as a serverless function on Vercel and calls GitHub's GraphQL API on the backend to retrieve GitHub user information. Both the Vercel API and GitHub API are rate limited, so I am looking for the best header to prevent these limits from being hit. To achieve this, I am currently using the following Cache-control header:
public, max-age=14400, stale-while-revalidate=86400
According to the Mozilla documentation, this header should keep both the private browser cache and server cache fresh for 4 hours, while the stale cache can be reused for 1 day while it revalidates on the server. Furthermore, since I am not using an authentication header, I should be able even to remove the Public keyword.
The Vercel cache documentation, however, recommends the following header:
maxage=0, s-maxage=14400, stale-while-revalidate=86400
Based on the Vercel documentation and this Stack Overflow question, I, therefore, think the following header is best suited for reducing both the Vercel and GitHub load:
maxage=14400, s-maxage=7200, stale-while-revalidate=86400
To my understanding, with this header, the cache will be fresh for 4 hours for individual users while the Vercel server refreshes the cache every 2 hours, and a stale cache can be reused for 1 day while it revalidates on the server.
As I am uncertain about the difference between the Public, max-age=<VALUE> and maxage=<VALUE>, s-maxage=<VALUE> syntax, I quickly wanted to double-check my understanding.
According to the Mozilla documentation, these two syntaxes should result in the same behaviour if the <VALUE> is equal between maxage and s-maxage properties. However, the Symfony documentation states that the s-maxage flag prohibits a cache from using a stale response in stale-if-error scenarios. My question is, therefore: What is the exact difference between these two syntaxes, and which one would you recommend for reducing the load on both the Vercel and GitHub api?
The Vercel-recommended cache headers are well-suited to minimizing the number of API calls:
Cache-Control: maxage=0, s-maxage=N
The s-maxage is used to control caching by the Vercel Edge Network, allowing it to serve cached responses rather than call the serverless function. As far as I know it does not have rate limits of its own.
Of course, you probably have other caching goals besides just reducing API calls. So you might want to use maxage as well to allow browser caching to reduce latency for your users.
It's also a good idea to use stale-while-revalidate, but note that this is just a mechanism to reduce latency. The revalidation still happens, so it won't have an effect on the number of API calls.
As for public, it means "that any cache MAY store the response, even if the response would normally be non-cacheable or cacheable only within a private cache". If your response is already cacheable by a public cache then this directive won't have any effect.
Related
I'm using the api http://exchangeratesapi.io/ to get exchange rates.
Their site asks:
Please cache results whenever possible this will allow us to keep the service without any rate limits or api key requirements.
-source
Then I found this:
By default, the responses all of the requests to the exchangeratesapi.io API are cached. This allows for significant performance improvements and reduced bandwidth from your server.
-somebody's project on github, not sure if accurate
I've never cached something before and these two statements confuse me. When the API's site says to "please cache the results", it sounds like caching is something I can do in a fetch request, or somehow on the frontend. For example, some way to store the results in local storage or something. But I couldn't find anything about how to do this. I only found resources on how to force a response NOT to cache.
The second quote makes it sound like caching is something the API does itself on their servers, since they set the response to cache automatically.
How can I cache the results like the api site asks?
To clear your confusion on the conflicting statements you're referencing:
Caching just means to store the data. Examples of where the data can be stored are in memory, in some persistence layer (like Redis), or in the browser's local storage (like you mentioned). The intent behind caching can be to serve the data faster (compared to getting it from the primary data source) for future requests/fetches, and/or to save on costs for getting the same data repeatedly, among others.
For your case, the http://exchangeratesapi.io/ API is advising consumers to cache the results on their side (as you mentioned in your question, this can be in the browser's local storage, if you're calling the API front front-end code, or stored in memory or other caching mechanisms/structures on the server-side application code calling the API) to that they can avoid the need to introduce rate limiting.
The project from Github you're referencing, Laravel Exchange Rates, appears to be a PHP wrapper around the original API - so it's like a middleman between the API and a developer's PHP code. The intent is to make it easier to use the API from within PHP code, and avoid having to make raw HTTP requests to the API and avoid processing the responses; the Laravel Exchange Rates handles that for the developer.
In regards to the
By default, the responses all of the requests to the exchangeratesapi.io API are cached
statement you're asking about, it seems the library follows the advice of the API, and caches the results from the source API.
So, to sum up:
http://exchangeratesapi.io/ is the source API, and it advises consumers to cache results. If your code is going to be calling this API, you can cache the results in your own code.
The Laravel Exchange Rates PHP library is a wrapper around that source API, and does cache the results from the source API for the user. If you're using this library, you don't need to further cache.
With caching headers I can either make the client not check online for updates for a certain period of time, and/or check for etags every time. What I do not know is whether I can do both: use the offline version first, but meanwhile in the background, check for an update. If there is a new version, it would be used next time the page is opened.
For a page that is completely static except for when the user changes it by themselves, this would be much more efficient than having to block checking the etag every time.
One workaround I thought of is using Javascript: set headers to cache the page indefinitely and have some Javascript make a request with an If-Modified-Since or something, which could then dynamically change the page. The big issue with this is that it cannot invalidate the existing cache, so it would have to keep dynamically updating the page theoretically forever. I'd also prefer to keep it pure HTTP (or HTML, if there is some tag that can do this), but I cannot find any relevant hits online.
A related question mentions "the two rules of caching": never cache HTML and cache everything else forever. Just to be clear, I mean to cache the HTML. The whole purpose of the thing I am building is for it to be very fast on very slow connections (high latency, low throughput, like EDGE). Every roundtrip saved is a second or two shaved off of loading time.
Update: reading more caching resources, it seems the Vary: Cookie header might do the trick in my case. I would like to know if there is a more general solution though, and I didn't really dig into the vary-header yet so I don't know yet if that works.
Solution 1 (HTTP)
There is a cache control extension stale-while-revalidate which describes exactly what you want.
When present in an HTTP response, the stale-while-revalidate Cache-
Control extension indicates that caches MAY serve the response in
which it appears after it becomes stale, up to the indicated number
of seconds.
If a cached response is served stale due to the presence of this
extension, the cache SHOULD attempt to revalidate it while still
serving stale responses (i.e., without blocking).
cache-control: max-age=60,stale-while-revalidate=86400
When browser firstly request the page it will cache result for 60s. During that 60s period requests are answered from the cache without contacting of the origin server. During next 86400s content will be served from the cache and fetched from origin server simultaneously. Only if both periods 60s+86400s are expired cache will not serve cached content but wait for origin server to fresh data.
This solution has only one drawback. I was not able to find any browser or intermediate cache which currently supports this cache control extension.
Solution 2 (Javascript)
Another solution is usage of Service workers with its feature to make custom responses to requests. With combination with Cache API it is enough to provide the requested feature.
The problem is that this solution will work only for browsers (not intermediate caches nor another http services) and even not all browsers supports Services workers and Cache API.
I have blob storage and CDN endpoint, that store my static content.
Now I want to update app.js file, because it was modified, but when I write this file to blob, CDN still gives me old app.js file. How can I update my app.js file? Or I have to wait until my cache is not going to end?
Simply you can't update the cache object before its expiration.
From https://msdn.microsoft.com/en-us/library/azure/gg680303.aspx:
If you no longer wish to cache an object in the Azure Content Delivery Network (CDN), you can take one of the following steps:
For a Azure blob, you can delete the blob from the public container.
You can make the container private instead of public. See Restrict Access to Containers and Blobs for more information.
You can disable or delete the CDN endpoint using the Azure Management Portal.
You can modify your hosted service to no longer respond to requests for the object.
An object already cached in the CDN will remain cached until the time-to-live period for the object expires. When the time-to-live period expires, the CDN will check to see whether the CDN endpoint is still valid and the object still anonymously accessible. If it is not, then the object will no longer be cached.
No explicit "purge" tool is currently available for the Azure CDN.
Other workarounds include using either fake query strings or new file names, if possible. See here: https://stackoverflow.com/a/8773202/908336
Question was asked quite long ago. I just wanted to update on method that proved useful for me. Its recommended by Microsoft. Essentially you need to set up cache-control headers in your Blob Storage. You can set cache control header with value "public, max-age=3600". This will cache your file for about 1 hour.
https://azure.microsoft.com/en-us/documentation/articles/cdn-manage-expiration-of-blob-content/
The CDN is simple. When a request comes in, it fetches the content from the origin (in this case, blob storage), and then caches it for some time based on the Cache-Control header. It will keep delivering the same content until the cache expires.
There's no way to tell the CDN to expire something early.
Others may jump in with more helpful advice about how to deal with this (like query string parameters), but I just wanted to give a straightforward explanation of how the CDN's caching works.
The only way to do this right this now is to contact Azure Support and they will in turn open a support ticket with Verizon EdgeCast to remove the file from the CDN and it will update at that point. This whole process takes about 8 hours on the basic Azure support plan. This isn't a good solution and I really hope they update this to where we can programmatically purge something from the CDN. This seems like a basic feature they are lacking. Your best bet I think right now is to enable querystring status and then update that querystring when you update. We do this for js files like so /js/custommix.js?version=1. Then we append a new version from our config when we need to update those.
http://azure.microsoft.com/en-us/blog/best-practices-for-the-windows-azure-content-delivery-network/
How can I purge or invalidate content in the Windows Azure CDN?
As of 1.4, no purge function is available. This feature is under development. The best freshness control is to set good cache expiration headers as described in this document and the Windows Azure CDN documentation on MSDN.
You can purge the content from Azure new Management portal.
It appears the default expiration time is 7 days.
From: http://msdn.microsoft.com/en-us/library/azure/gg680306.aspx
Blobs that benefit the most from Azure CDN caching are those that are
accessed frequently during their time-to-live (TTL) period. A blob
stays in the cache for the TTL period and then is refreshed by the
blob service after that time is elapsed. Then the process repeats.
You have two options for controlling the TTL:
Do not set cache values thus using the default TTL of 7 days.
Explicitly set the x-ms-blob-cache-control property on a Put Blob, Put
Block List, or Set Blob Properties request, or use the Azure Managed
Library to set the BlobProperties.CacheControl property. Setting this
property sets the value of the Cache-Control header for the blob. The
value of the header or property should specify the appropriate value
in seconds. For example, to set the maximum caching period to one
year, you can specify the request header as x-ms-blob-cache-control:
public, max-age=31556926. For details on setting caching headers, see
the HTTP/1.1 specification.
ETags allow browsers to perform conditional GETs. Only if the resource in question has been altered will the resource have to be re-downloaded. However, the browser still has to wait for the server to respond to its request.
An alternative to ETags is to introduce a token into the URL pointing to the resource:
http://example.com/css/styles.css?token=134124134
or
http://example.com/css/134124134/styles.css
Both approaches avoid having to re-download an unchanged resource.
However, using URLs with tokens allows the server to set a far-future expiry header on the resource. This saves the round trip taken up by a conditional GET - if the resource is unchanged then the URL pointing to it will be unchanged.
Are there any advantages to using ETags over URLs with tokens?
The major downside for read-only resources that I see is that if we all took this approach for all static resources then client caches would start to fill with all sorts of out-dated resources.
Also, think of all the intermediary caches that would start holding loads of useless files.
You are fighting against the web with this approach and if it became popular then something would have to change because it is not a scalable solution.
Could there be some kind of hybrid approach where you use a limited set of tokens and set the expiry small enough that an old cached resource would expire before the token was reused?
Etags are also used for read-write resources and in this case the I suspect the token solution just does not work.
I think the biggest difference/potential advantage would be configuration; The URL setting must be configured/setup inside the application (eg, the HTML actually must include the value). ETags are configured for the entire web server, and the HTML doesn't have to be modified to take advantage of them.
Also, the ETags will (assuming they are configured correctly) change when the file pointed at changes; Adding a token to the URL will require some additional "thing" that tells it to change (either a person editing the HTML or some configuration setting, etc).
Have a constant URI?
I've been thinking about batch reads and writes in a RESTful environment, and I think I've come to the realization that I have broader questions about HTTP caching. (Below I use commas (",") to delimit multiple record IDs, but that detail is not particular to the discussion.)
I started with this problem:
1. Single GET invalidated by batch update
GET /farms/123 # get info about Old MacDonald's Farm
PUT /farms/123,234,345 # update info on Old MacDonald's Farm and some others
GET /farms/123
How does a caching server in between the client and the Farms server know to invalidate its cache of /farms/123 when it sees the PUT?
Then I realized this was also a problem:
2. Batch GET invalidated by single (or batch) update
GET /farms/123,234,345 # get info about a few farms
PUT /farms/123 # update Old MacDonald's Farm
GET /farms/123,234,345
How does the cache know to invalidate the multiple-farm GET when it sees the PUT go by?
So I figured that the problem was really just with batch operations. Then I realized that any relationship could cause a similar problem. Let's say a farm has zero or one owners, and an owner can have zero or one farms.
3. Single GET invalidated by update to a related record
GET /farms/123 # get info about Old MacDonald's Farm
PUT /farmers/987 # Old MacDonald sells his farm and buys another one
GET /farms/123
How does the cache know to invalidate the single GET when it sees the PUT go by?
Even if you change the models to be more RESTful, using relationship models, you get the same problem:
GET /farms/123 # get info about Old MacDonald's Farm
DELETE /farm_ownerships/456 # Old MacDonald sells his farm...
POST /farm_ownerships # and buys another one
GET /farms/123
In both versions of #3, the first GET should return something like (in JSON):
farm: {
id: 123,
name: "Shady Acres",
size: "60 acres",
farmer_id: 987
}
And the second GET should return something like:
farm: {
id: 123,
name: "Shady Acres",
size: "60 acres",
farmer_id: null
}
But it can't! Not even if you use ETags appropriately. You can't expect the caching server to inspect the contents for ETags -- the contents could be encrypted. And you can't expect the server to notify the caches that records should be invalidated -- caches don't register themselves with servers.
So are there headers I'm missing? Things that indicate a cache should do a HEAD before any GETs for certain resources? I suppose I could live with double-requests for every resource if I can tell the caches which resources are likely to be updated frequently.
And what about the problem of one cache receiving the PUT and knowing to invalidate its cache and another not seeing it?
Cache servers are supposed to invalidate the entity referred to by the URI on receipt of a PUT (but as you've noticed, this doesn't cover all cases).
Aside from this you could use cache control headers on your responses to limit or prevent caching, and try to process request headers that ask if the URI has been modified since last fetched.
This is still a really complicated issue and in fact is still being worked on (e.g. see http://www.ietf.org/internet-drafts/draft-ietf-httpbis-p6-cache-05.txt)
Caching within proxies doesn't really apply if the content is encrypted (at least with SSL), so that shouldn't be an issue (still may be an issue on the client though).
HTTP protocol supports a request type called "If-Modified-Since" which basically allows the caching server to ask the web-server if the item has changed. HTTP protocol also supports "Cache-Control" headers inside of HTTP server responses which tell cache servers what to do with the content (such as never cache this, or assume it expires in 1 day, etc).
Also you mentioned encrypted responses. HTTP cache servers cannot cache SSL because to do so would require them to decrypt the pages as a "man in the middle." Doing so would be technically challenging (decrypt the page, store it, and re-encrypt it for the client) and would also violate the page security causing "invalid certificate" warnings on the client side. It is technically possible to have a cache server do it, but it causes more problems than it solves, and is a bad idea. I doubt any cache servers actually do this type of thing.
Unfortunately HTTP caching is based on exact URIs, and you can't achieve sensible behaviour in your case without forcing clients to do cache revalidation.
If you've had:
GET /farm/123
POST /farm_update/123
You could use Content-Location header to specify that second request modified the first one. AFAIK you can't do that with multiple URIs and I haven't checked if this works at all in popular clients.
The solution is to make pages expire quickly and handle If-Modified-Since or E-Tag with 304 Not Modified status.
You can't cache dynamic content (withouth drawbacks), because... it's dynamic.
In re: SoapBox's answer:
I think If-Modified-Since is the two-stage GET I suggested at the end of my question. It seems like an OK solution where the content is large (i.e. where the cost of doubling the number of requests, and thus the overhead is overcome by the gains of not re-sending content. That isn't true in my example of Farms, since each Farm's information is short.)
It is perfectly reasonable to build a system that sends encrypted content over an unencrypted (HTTP) channel. Imagine the scenario of a Service Oriented Architecture where updates are infrequent and GETs are (a) frequent, (b) need to be extremely fast, and (c) must be encrypted. You would build a server that requires a FROM header (or, equivalently, an API key in the request parameters), and sends back an asymmetrically-encrypted version of the content for the requester. Asymmetric encryption is slow, but if properly cached, beats the combined SSL handshake (asymmetric encryption) and symmetric content encryption. Adding a cache in front of this server would dramatically speed up GETs.
A caching server could reasonably cache HTTPS GETs for a short period of time. My bank might put a cache-control of about 5 minutes on my account home page and recent transactions. I'm not terribly likely to spend a long time on the site, so sessions won't be very long, and I'll probably end up hitting my account's main page several times while I'm looking for that check I recently sent of to SnorgTees.