I'm working on a multi-tenant platform where our customers can create online stores. A store might might be selling products in multiple currencies, like EUR and USD at the same time.
Since the prices of the products displayed differ based on the currency, I would like to have pages cached for both eur and usd.
The initial part of getting it to work was rather easy, but I encountered an undesired side effect.
sub vcl_recv {
if (req.http.cookie) {
cookie.parse(req.http.cookie);
// If the user has a `currency` cookie, set it as a header
set req.http.x-currency = cookie.get("currency");
}
...
}
sub vcl_hash {
hash_data(req.http.x-currency);
}
My backend recognizes uses this header, or its absence, in order to render the proper output and then Varnish stores the result. If the request does not have this header, the server sends a Set-Cookie header, which will tell the browser to create a cookie with the default currency of the store.
My problem stems from the fact that, in advance, I don't have a way of knowing the default currency of a store ( EUR or USD in our case ), because the owner can add/remove them dynamically.
So, on the first request to the /homepage endpoint, the user will not have a currency cookie, so its value will be '', and this will be used in the hash_data method.
The server recognizes that the client did not have a currency cookie, so it set a Set-Cookie header, and when the page hits the browser, the client will have the cookie.
Currently, we have a cache entry for /homepage with no currency cookie.
The customer hits refresh and the request reaches Varnish.
This time, there is a currency cookie, which is used in the hash_data function, but it produces a different key, so it hits the backend again, but the output will be exactly the same ( obviously, Varnish does not know that ).
The /homepage url has two associated cache entries with it, which both have the same content.
Is there an alternative approach I can take to work around this, or a fix of some sort?
My initial train of thought was:
Go inside of the vcl_backend_response subroutine and inspect the request. If the request is missing a cookie, then the beresp object will contain the default currency of the store in its Set-Header. And I can use that to create 2 different cache keys, which point to the same cache entry - /homepage + '' for empty cookie and /homepage + 'EUR'.
That's not technically possible, but its an illustration of something which seems to be fixing my problem.
I have not provided a 100% of my vcl configuration for simplicity, but I have used the default vcls as templates to create a customized configuration for vcl_recv and vcl_backend_response. I know that cookies mean Personalized content and I shouldn't cache that, but the personalization in this case does not work uniquely for this user, so I made an exception.
Edit
www.mystore.com/en/ - default currency is USD
sub vcl_hash {
if(req.url !~ "^/(contact|sitemap)") {
hash_data(req.http.x-currency);
}
}
We have nothing in the cache at the moment
- User visits the
homepagefor the first time, he has no cookies whatsoever - Varnish generates a hash based on
http.x-currency, which is empty. The result is a miss. - Varnish hits the backend, the backend sees the lack of the
x-currencyheader, so it sets a Set-Cookie header, which sets the cookie to USD - Varnish receives the response, caches it, and sends it back to the client
- The same user hits a refresh on the same page
- Varnish generates a hash based on
http.x-currency, which is now USD ( it was empty last time ), which results in a cache miss, again - Varnish goes to the backend, the backend returns the exact same response, because no
x-currencyorx-currency= USD means the same for the backend - Varnish receives the response, caches it and returns it to the client
The homepage route is now completely cached. No matter if somebody visits with currency=USD cookie, or without one, he receives a cached response. However, there are two entries in the cache register, one for an empty currency cookie, and one with currency=USD cookie, and both of these have the same result.
The problem stems from the fact that, when a request arrives and it doesn't have a currency cookie, I don't have a predictable way of figuring out its value without the help of the backend.
It appears to me as though I would just have to live with this.