I have the following VCL:
vcl 4.0;
import std;
import directors;
backend one {
.host = "localhost";
.port = "3333";
}
backend two {
.host = "localhost";
.port = "3333";
}
sub vcl_init {
new random_director = directors.random();
random_director.add_backend(two, 10);
random_director.add_backend(one, 8);
}
sub vcl_recv {
std.log("start vcl_recv");
set req.backend_hint = random_director.backend();
if (req.backend_hint == one) {
std.log("one");
} else if (req.backend_hint == two) {
std.log("two");
} else {
std.log("neither one");
}
std.log("end vcl_recv");
}
When I run it, the output is always:
start vl_recv
neither one
end vcl_recv
How can I properly check to see which backend has been selected?
Thanks
In vcl_backend_fetch you should be able to access bereq.backend.name
So moving your code, you might have something like:
sub vcl_backend_fetch {
if (bereq.backend.name == "one") {
std.log("one");
} else if (bereq.backend.name == "two") {
std.log("two");
} else {
std.log("neither one");
}
}
Update: it's not possible to know the selected backend before requesting a backend call, so you'll never get this information in the vcl_recv. As you may not need the backend selection (if the object is already in cache), or because the backend could change (if one goes down) before the selection is run (so the time between the vcl_recv and vcl_fetch_response), it would be a waste of resources to determine it in the vcl_recv.
But in Varnish 5.0+ you can use the beresp.backend (or beresp.backend.name as you need to log it as a string) available in vcl_backend_response, which is defined as:
This is the backend we fetched from. If bereq.backend was set to a director, this will be the backend selected by the director.
See: https://varnish-cache.org/docs/5.0/reference/vcl.html#beresp
Related
I'm using varnish as a cache for my live streaming content. I have on the backend a video server (Mistserver) with HLS output on port 8090. I've configured varnish in the following way:
`vcl 4.0;
import std;
backend default {
.host = "127.0.0.1";
.port = "8090";
}
acl purge {
"127.0.0.1";
}
sub vcl_recv {
if(req.method == "PURGE"){
if (!client.ip ~ purge) {
return(synth(405,"Not allowed."));
}
return (purge);
}
return (hash);
}
sub vcl_hash {
return (lookup);
}
sub vcl_hit {
if (obj.ttl >= 0s) {
return (deliver);
}
if (std.healthy(req.backend_hint)) {
if (obj.ttl + 10s > 0s) {
return (deliver);
}
else {
return(fetch);
}
}
else {
if (obj.ttl + obj.grace > 0s) {
return (deliver);
}
else {
return (fetch);
}
}
return (fetch); # Dead code, keep as a safeguard
}
sub vcl_purge{
}
sub vcl_pipe {
return (pipe);
}
sub vcl_init {
return (ok);
}
sub vcl_fini {
return (ok);
}
sub vcl_backend_response {
set beresp.http.Access-Control-Max-Age = 15;
set beresp.ttl = 15s;
set beresp.grace = 15s;
}`
And varnish is being executed with a thread pool timeout of 30 seconds:
/usr/sbin/varnishd -a :6081 -T localhost:6082 -f /etc/varnish/user.vcl -S /etc/varnish/secret -s malloc,1G -t 3 -p thread_pool_timeout=30
The Problem: The content is being cached, apparently, correctly. But after some time the stream is running, I'm getting a difference of time with the original stream of 5 minutes. so for example if the live stream has time 22:25, I might see in the output stream the time 22:20. I can't have such a time difference in a live stream.
I've tried purging the cache, but didn't work. Sometime it is correclty synchronized, If I restart both video server and varnish. What I need is to avoid caching for mor than 1 minute or less.
What am I doing wrong?
Thanks Indeed
(not your issue here, but: I'd remove the vcl_hit section, it's probably not doing anything good for you)
For live video, the manifest needs to be treated careful, essentially, you want to lag as little behind as possible. So the first thing to do is to kill the grace, and the second one is to shorten the TTL to half a chunk duration.
Try with something like:
vcl 4.0;
import std;
backend default {
.host = "127.0.0.1";
.port = "8090";
}
acl purge {
"127.0.0.1";
}
sub vcl_recv {
if(req.method == "PURGE"){
if (!client.ip ~ purge) {
return(synth(405,"Not allowed."));
}
return (purge);
}
return (hash);
}
sub vcl_backend_response {
if (bereq.url ~ "m3u8") {
# assuming chunks are 2 seconds long
set beresp.ttl = 1s;
set beresp.grace = 0s;
} else {
set beresp.ttl = 10m;
set beresp.grace = 5m;
}
}
Note: You don't want the manifest delivered by varnish to lag behind the real one. The current setup can deliver data that is 30 seconds old, which is usually way to much for live. Interestingly, it seems the player will just wait until the manifest shows new chunk to resume playing, I guess this is how the delay accumulate. The usual case is actually worse as the player just stop altogether if there's no new chunk once it's done reading the newest chunks.
I have a simple VCL file as follows:
vcl 4.0;
import std;
backend default {
.host = "127.0.0.1";
.port = "3333";
}
sub vcl_recv {
std.log("req.host: "+req.host);
}
sub vcl_backend_response {
}
sub vcl_deliver {
}
When I try to start varnishd with this config on my Mac, I get the following error:
Error:
Message from VCC-compiler:
Symbol not found: 'req.host' (expected type STRING):
('/Users/jononomo/dev/my_project/deploy/testing.vcl' Line 30 Pos 26)
std.log("req.host: "+req.host);
-------------------------########--
('/Users/jononomo/dev/my_project/deploy/testing.vcl' Line 30 Pos 5) -- ('/Users/jononomo/dev/my_project/deploy/testing.vcl' Line 30 Pos 25)
std.log("req.host: "+req.host);
----#####################----------
Running VCC-compiler failed, exited with 2
VCL compilation failed
I have tried different variations of this line:
std.log("req.host: "+req.host);
such as:
std.log(req.host: '+req.host);
std.log("req.host: ",req.host);
std.log('req.host: ',req.host);
std.log('hello');
but none of them work.
How can I do simple logging from my VCL file?
Thanks.
UPDATE: std.log("hello") seems to compile... however, I need to log information about the request object and req, request, etc do not exist.
Use req.http.host instead:
vcl 4.0;
import std;
backend default {
.host = "127.0.0.1";
.port = "3333";
}
sub vcl_recv {
std.log("req.host: " + req.http.host);
}
sub vcl_backend_response {
}
sub vcl_deliver {
}
I know there are a lot of questions and answers on this topic. But most of them are for Varnish 3 or don't use the round_robin director.
I have 2 webservers configured in Varnish.
Some static files may only be generated at one of the two webservers.
On a 404 response, I want varnish to try the other webserver.
Currently i've tested with the following VCL:
sub vcl_init {
new bar = directors.round_robin();
bar.add_backend(cbweb1);
bar.add_backend(cbweb2);
}
sub vcl_recv {
set req.backend_hint = bar.backend();
unset req.http.Cookie;
}
sub vcl_deliver {
if(resp.status == 404 && req.restarts < 1)
{
return(restart);
}
}
In my small test, this appears to work. But I don't understand how this works.
Does the bar.backend() remembers which servers have been used and skips those? Or is this just round-robinning globally, is it possible, a server would be called twice if another requests comes in during processing?
Update:
The following VCL appears to work:
sub vcl_init {
new bar = directors.round_robin();
bar.add_backend(cbweb1);
bar.add_backend(cbweb2);
}
sub vcl_recv {
set req.backend_hint = bar.backend();
}
sub vcl_backend_fetch
{
if (bereq.retries > 0)
{
if(bereq.http.X-Backend == "cbweb1")
{
set bereq.backend = cbweb2;
}
else
{
set bereq.backend = cbweb1;
}
}
}
sub vcl_backend_response {
set bereq.http.X-Backend = beresp.backend.name;
if (beresp.status == 404 && bereq.retries < 1) {
return(retry);
}
}
I think it works in your test because of round robin. It will try servers one after each other.
I think in a production setup this won't work because of concurrent requests.
If you retry, it will go again through the vcl_recv and get a backend_hint from the director, whatever is the req.restarts value. If you want a different backend then you will have to code it.
An idea could be (code not tested, you will have to adapt the X-backend comparison):
sub vcl_init {
new bar = directors.round_robin();
bar.add_backend(cbweb1);
bar.add_backend(cbweb2);
}
sub vcl_recv {
if (req.restarts > 0){
if (req.X-backend == cbweb1 ){
set req.backend_hint = cbweb2;
}
if (req.X-backend == cbweb2 ){
set req.backend_hint = cbweb1;
}
}
else {
set req.backend_hint = bar.backend();
}
set req.X-backend = req.backend_hint
unset req.http.Cookie;
}
sub vcl_deliver {
if(resp.status == 404 && req.restarts < 1)
{
return(restart);
}
}
I'm using stale-if-error to deliver stale content while the server is marked unhealthy looking at grace and keep object options in vcl_hit.
The question is: Is it possible to deliver a cache object after entering the vcl subroutine vcl_backend_error (fresh request error). Actually, I deliver cached object at vcl_hit but looking at the next diagram, I don't see how it is possible to access the cached object of that request.
Source: https://www.varnish-software.com/book/4.0/chapters/VCL_Basics.html
When using the built-in VCL (see code bellow):
# Built-in 'vcl_hit'.
sub vcl_hit {
if (obj.ttl >= 0s) {
return (deliver);
}
if (obj.ttl + obj.grace > 0s) {
return (deliver);
}
return (fetch);
}
If vcl_backend_error is reached by a background / asynchronous backend fetch triggered by a return (deliver) during vcl_hit you don't need to worry. It's just a background thread to update a stalled object. The stalled content has already been delivered to the client.
If vcl_backend_error is reached by a synchronous backend fetch triggered by a return (fetch) during vcl_hit you don't need to worry too. An error will be delivered to the client, but you have not choice. A stalled object is not available in the Varnish storage.
However, if you have customised vcl_hit to limit grace when the backend is healthy (see VCL example code below), a return (fetch) executed during vcl_hit will be handled as a synchronous backend request. The client will wait for the backend response. If the backend request reaches vcl_backend_error and error will be delivered to the client side. A stalled object is available in the Varnish storage (stalled more than 60 seconds ago in this example), but it's not going to be used.
# Customised 'vcl_hit'.
sub vcl_hit {
if (obj.ttl >= 0s) {
return (deliver);
}
if (std.healthy(req.backend_hint)) {
if (obj.ttl + 60s > 0s) {
return (deliver);
}
} else {
if (obj.ttl + obj.grace > 0s) {
return (deliver);
}
}
return (fetch);
}
If you want to deliver stalled objects when the synchronous backend fetch fails, in this case you need some extra VCL logic. The idea is shown in the code below:
backend fail_be {
.host = "127.0.0.1";
.port = "9000";
.probe = {
.url = "/give-me-a-non-200-please";
.interval = 24h;
.timeout = 1s;
.window = 1;
.threshold = 1;
}
}
sub vcl_recv {
# Force the non-healthy backend in case of restart because of a previous
# failed backend fetch. This will force serving stalled content using
# full grace during 'vcl_hit' (if possible).
if (req.restarts == 0) {
unset req.http.X-Varnish-Restarted-5xx;
} else {
if (req.http.X-Varnish-Restarted-5xx) {
set req.backend_hint = fail_be;
}
}
# ...
}
sub vcl_synth {
# 503 generated for synchronous client requests when abandoning the
# backend request (see 'vcl_backend_fetch') and not executing a POST.
if (resp.status == 503 &&
req.method != "POST" &&
!req.http.X-Varnish-Restarted-5xx) {
set req.http.X-Varnish-Restarted-5xx = "1";
return (restart);
}
# ...
}
sub vcl_backend_fetch {
if (bereq.retries == 0) {
unset bereq.http.X-Varnish-Backend-5xx;
} else {
if (bereq.http.X-Varnish-Backend-5xx) {
# Jump to 'vcl_synth' with a 503 status code.
return (abandon);
}
}
# ...
}
sub vcl_backend_response {
if (beresp.status >= 500 && beresp.status < 600) {
set bereq.http.X-Varnish-Backend-5xx = "1";
return (retry);
}
# ...
}
sub vcl_backend_error {
set bereq.http.X-Varnish-Backend-5xx = "1";
return (retry);
}
I'm using devicedetect.vcl to send the X-UA-Device header to my app, so it knows which layout to render. The possible values that varnish will set for this header are mobile or desktop.
On the way out, this header gets transformed to Vary: User-Agent.
Now, as a separate, isolated project, I need to set another header on the resp object (which gets sent to our Golang proxy before it gets sent to the client). This header will be called X-Analytics-Device and will have the possible values of bot, mobile, tablet, or desktop.
The backend server does not need to do anything with X-Analytics-Device. Only our Go proxy will parse and then remove this header before sending it to the client.
The problem is, I need to set the X-Analytics-Device header based on the results of the subroutine call devicedetect;, which is in vcl_recv. I need to ultimately set it on resp which is in vcl_deliver, and I need to know the best way to pass the data.
The only real way I can think of that might work (based on my limited understanding of Varnish), is that I need to set some other header, and access it later.
Perhaps something like this (I left out bot for now):
if (req.http.X-UA-Device ~ "^mobile") {
set req.http.X-UA-Device = "mobile";
set req.http.X-Analytics-Device = "mobile";
} elseif (req.http.X-UA-Device ~ "^tablet") {
set req.http.X-UA-Device = "desktop";
set req.http.X-Analytics-Device = "tablet";
} else {
set req.http.X-UA-Device = "desktop";
set req.http.X-Analytics-Device = "desktop";
}
After this... I don't know. Do I need to set it like this in vcl_deliver?
set resp.http.X-Analytics-Device = req.http.X-Analytics-Device;
How does it get passed from the resp to the req? What happens if it's a hit or a miss? Does that matter? Is this going to try to cache this header in varnish (which it shouldnt be, obviously)?
My main fear with doing it this way is that there are so many moving pieces I just don't know the best way.
The end result is that... EVERY request needs to check the device, and on the way out it needs to set the header, without that value being cached along with the data in varnish, and while it doesnt hurt to send it to the backend, its not needed.
Here's my full VCL, before I added the pseudo-code lines above.
vcl 4.0;
backend default {
.host = "127.0.0.1";
.port = "8080";
}
import std;
include "purge.vcl";
include "devicedetect.vcl";
acl purge {
"localhost";
"127.0.0.1";
"10.0.0.0"/8;
}
sub vcl_recv {
call devicedetect;
if (req.http.X-UA-Device ~ "^mobile") {
set req.http.X-UA-Device = "mobile";
} else {
set req.http.X-UA-Device = "desktop";
}
if (req.restarts == 0) {
if (req.http.X-Forwarded-For) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
if (req.method !~ "^(GET|HEAD|PUT|POST|OPTIONS|DELETE)$") {
return (synth(405));
}
# never cache anything except GET/HEAD
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# don't cache images or assets
if (req.url ~ "\.(js|css|jpg|jpeg|png|gif|ico|tiff|tif|bmp|svg)$") {
return (pass);
}
# fix up the request
unset req.http.cookie;
return (hash);
}
sub vcl_backend_response {
set beresp.do_stream = false;
# device detect
if (bereq.http.X-UA-Device) {
if (!beresp.http.Vary) { # no Vary at all
set beresp.http.Vary = "X-UA-Device";
} elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
}
}
# bypass cache for files > 5 MB
if (std.integer(beresp.http.Content-Length, 0) > 5242880) {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);
}
# catch obvious reasons we can't cache
if (beresp.http.Set-Cookie) {
set beresp.ttl = 0s;
}
# avoid caching error responses (1m grace period)
if (beresp.status >= 500) {
set beresp.ttl = 1m;
return (deliver);
}
# set times
set beresp.ttl = 24h;
set beresp.grace = 4h;
return (deliver);
}
sub vcl_deliver {
# device detect
if ((req.http.X-UA-Device) && (resp.http.Vary)) {
set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
}
# remove junk headers
unset resp.http.Server;
unset resp.http.Via;
unset resp.http.X-Powered-By;
unset resp.http.X-Runtime;
unset resp.http.X-Varnish;
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
This link actually perfectly clarifies and answers all the things I was failing to articulate... https://info.varnish-software.com/blog/adding-headers-gain-insight-vcl
The answer is to shovel all the bits of data you need into the req headers in vcl_recv, and then copy them over to the response in vcl_deliver.
He states the following, about why it won't get cached:
Since the req object is not delivered to the client we need to copy the data from the req object to resp. We do this when we deliver it. If you do it in vcl_backend_response the headers will be stored in the cache and this might not be what you want.
The Answer from #Tallboy saved my day. To sum it up what you want to do is:
# set the value in vcl_recv
sub vcl_recv {
set req.http.X-NAVO-AY = "AYAYAYAYAYAYYYYY";
}
# copy the value from req to resp (because it is not done automatically)
sub vcl_deliver {
set resp.http.X-NAVO-AY = req.http.X-NAVO-AY;
}