The udo
vmod offers flexible load-balancing features, with notably local
reconfiguration and smart retry behavior.
vcl 4.1;
import udo;
backend be1 { .host = "1.1.1.1"; }
backend be2 { .host = "2.2.2.2"; }
backend be3 { .host = "3.3.3.3"; }
backend be4 { .host = "4.4.4.4"; }
# create a weighted random director
sub vcl_init {
new udo_dir = udo.director();
udo_dir.set_type(random);
udo_dir.add_backend(be1, weight = 1);
udo_dir.add_backend(be2, weight = 1);
udo_dir.add_backend(be3, weight = 1);
udo_dir.add_backend(be4, weight = 4);
}
sub vcl_backend_fetch {
set bereq.backend = udo_dir.backend();
# for videos, use consistent hashing
if (bereq.url ~ "^/video/") {
udo_dir.set_type(hash);
}
}
sub vcl_backend_response {
# if the response isn't a 200, retry, udo_dir will pick a different
# backend, even with consistent hashing enabled
if (beresp.status != 200) {
return (retry);
}
}
An important facet of udo
is that each task get its own cache of the state,
allowing the VCL writer to change load-balancing policy or to deny some
backends easily on a per-request fashion.
Note: Calling std.healthy()
on this director from a client context, for
example vcl_recv, will always return false. This is subject to change in the
future. Doing the same from a backend context returns true or false like any
other director, depending on whether there are any healthy and unused backends
in the director.
OBJECT director()
Create an empty hash director object. One characteristic of this director is that within a VCL task, it’ll remember which backends have been used, and it will skip them for the subsequent decisions.
Arguments: None
Type: Object
Returns: Object.
VOID .set_type(ENUM {hash, fallback, random})
Changes the type of the director:
If used in sub vcl_init
, the change is global and all requests will inherit this
property. If called from client (e.g. sub vcl_recv
) or backend (e.g.
sub vcl_backend_fetch
) context, the type is changed just within this context.
Arguments: None
Type: Method
Returns: None
VOID .set_subtype(ENUM {hash, fallback, random} subtype, INT top)
Apply a different type to the top
highest scoring backends in the director.
When set, the director will calculate a subtype score in addition to a main type
score for each backend. For a given request, a list of all the backends in the
director is created and sorted by main score. But each time the director is
resolved to a backend, the top
scoring backends are first re-sorted
according to their subtype score. Used and unhealthy backends do not count
towards the top
limit, so when a backend is exhausted during a retry, it no
longer counts towards the limit, and the director re-sorts the subset with one
additional backend.
An example use-case is sharding objects over multiple backends. By setting
.set_subtype(random, 2)
, the director will shard each object over two
backends. This is useful for partial replication in a two-tier architecture.
Arguments:
top
accepts type INT
subtype
is an ENUM that accepts values of hash
, fallback
, and random
Type: Method
Returns: None
VOID .set_hash(BLOB hash)
Override the object hash, within a client or backend context. It’s useful in two notable cases:
.dump()
before sub vcl_hash
has been runThe blob must be 32 bytes long.
Arguments:
hash
accepts type BLOBType: Method
Returns: None
VOID .add_backend(BACKEND be, REAL weight = 1, [BLOB hash])
Introduce be
into the director, with an optional weight
and hash
. If no
hash is given, the VCL name of the backend is used.
Note that this call must be made from sub vcl_init
and will fail the
initialization of the VCL if a backend with the same hash
already exists in
the director.
The blob must be 32 bytes long.
Arguments:
be
accepts type BACKEND
weight
accepts type REAL with a default value of 1
optional
hash
accepts type BLOB
Type: Method
Returns: None
BACKEND .backend()
Return a backend, according to the current type set.
Arguments: None
Type: Method
Returns: Backend
STRING .dump(ENUM {list, json} fmt = list)
Output some of the director’s internal information.
json
will produce a fairly complete (and large) string
{
"hash": "0x77d833ebbeae1ef5fcc14eb2fe94cf23a7d12bf5d582926e199b68808c6788b0",
"type": "hash",
"backends": [
{
"name": "s2",
"used": false,
"score": 5.733361,
"weight": 1.000000,
"hash": "0xad328846aa18b32a335816374511cac163c704b8c57999e51da9f908290a7a4"
},
...
]
}
While list
is a simple comma-separated list
s2, s1, s3, s4
Arguments:
fmt
is an ENUM that accepts values of list
, and json
with a default value of list
optional
Type: Method
Returns: String
VOID .exhaust_backend(BACKEND be)
Mark be
as “used” so that it won’t be returned again while in the same VCL
task.
Arguments:
be
accepts type BACKENDType: Method
Returns: None
VOID .reset(ENUM {exhausted, health} reset, [BACKEND be])
Reset attributes that are cached for the current task. Resetting exhausted
will mark all backends as unused. Resetting health
will prompt the director
to re-check the health of all backends. If the optional be
parameter is
supplied, the reset only applies to that backend.
Arguments:
be
accepts type BACKEND
reset
is an ENUM that accepts values of exhausted
, and health
Type: Method
Returns: None
VOID .subscribe(STRING tag)
Subscribe to an ActiveDNS DNS group. This director will start to generate dynamic backends based on DNS results and DNS group configuration. For more information on how to create and configure a DNS group, see the ActiveDNS VMOD documentation.
Depending on the DNS and DNS group, dual-address backends may be created. The backend hash used for the Highest Random Weight algorithm is generated from the combination of ipv4 address, ipv6 address, and port. ActiveDNS guarantees that each backend hash is unique.
If a dynamic backend is created with port number 443, and no backend hint has
been set for the DNS group, ssl is implicitly enabled for that backend. However,
if a backend hint has been set for the DNS group, .ssl = 1;
must be set in
the backend definition for ssl to be enabled.
When creating a backend from an SRV record, the SRV record weight is used as the weight of that backend. The weight influences the hash distribution in the same way as for static backends. Currently, the SRV record priority is ignored.
The name of each backend has the following format
udo.DIRNAME.(sa[4,6]:IP:PORT)[.(sa6:IP:PORT)]
Example backends for a director named udo_dir
:
udo.udo_dir.(sa4:1.1.1.1:443) udo.udo_dir.(sa4:2.2.2.2:443).(sa6:::2:443) udo.udo_dir.(sa6:::3:443)
Example configuration
import udo;
import activedns;
sub vcl_init {
new group1 = activedns.dns_group();
group1.set_host("example.com");
new udo_dir = udo.director();
udo_dir.subscribe(group1.get_tag());
}
Arguments:
tag
accepts type STRINGType: Method
Returns: None
VOID .set_identity([STRING string], [BLOB hash])
For static backends, you can optionally use this method to manually set the
identity of a node instead of relying on self-identification. The identity can
be provided as either a string or a 32 byte hash, and must match one of the
backends in the director. If the backend was added to the director using
.add_backend()
with the optional hash
argument, the same hash should be
passed to this function. Otherwise, the name of the backend should be passed as
a string. Can only be used in vcl_init.
Arguments:
string
accepts type STRING
hash
accepts type BLOB
Type: Method
Returns: None
BOOL .is_identified()
Returns whether or not the director has successfully determined it’s own
identity. The identity can either be determined through self-identification
with .self_identify()
, or it can be set manually with .set_identity()
.
Arguments: None
Type: Method
Returns: Bool
STRING .get_identifier()
Returns a random alphanumeric identifier string.
The identifier should be placed in a header when self-routing a request, and
becomes temporarily associated with the backend Varnish is fetching from. When
an identifier header is received, it should be passed to the directors
.self_identify()
method, which will attempt to self-identify the director.
When the director has successfully self-identified, this function returns an
alphanumeric representation of this directors identity hash.
Arguments: None
Type: Method
Returns: String
BOOL .self_identify(STRING identifier)
Take an identifier string and attempt to self-identify the director, returns
true if successful. The director keeps track of identifiers that have been
retrieved with .get_identifier()
, so if the identifier passed to this method
is recognized, this director must have sent a request to itself. The backend
that the identifier is associated with becomes the identity of the director.
If more than one identifier is passed in a comma-separated string, this method returns true if any of the identifiers/identities are recognized. The director identity is never set when more than one identifier is passed. This works both before and after self-identification, so it can be used to detect loops in a self-routing cluster.
Arguments:
identifier
accepts type STRINGType: Method
Returns: Bool
BOOL .self_is_next([INT lookahead])
Returns true if this node is next in line for this request. The next in line for
each request is determined by ordering the backends according to the director
type (usually hash
), and checking whether the directors identity matches the
next healthy and unused backend in the list.
If lookahead
is supplied, more than one backend is checked for an identity
match. A lookahead value of two will match the director identity against the two
next healthy and unused backends.
Minimal self-routing example
sub vcl_recv {
if (udo_dir.self_identify(req.http.identifier)) {
return (synth(242, "Self-Identified"));
}
}
sub vcl_backend_fetch {
if (!udo_dir.self_is_next()) {
set bereq.http.identifier = udo_dir.get_identifier();
set bereq.backend = udo_dir.backend();
}
}
sub vcl_backend_response {
if (beresp.status == 242 && beresp.reason == "Self-Identified") {
return (retry);
}
}
The above example is intended to show the bare minimum VCL required implement self-routing.
This VMOD ships also with a VCL called cluster.vcl
, which uses
this VMOD to implement self routing in a more complete way compared
to the example above.
Please note that new versions of this file might alter the behavior slightly, if such changes can improve the overall design and performance of the solutions it provides.
If you use the VCL or the new self routing functions in this VMOD, please pay attention to change logs and release announcements until this notice has been removed.
The VCL file can be included at the top of your VCL to enable self-routing
include "cluster.vcl";
cluster.vcl
provides a self-routing director called cluster
,
all the logic required to self-route within the cluster, and options
to modify the caching behavior.
Available options:
token
: Required. Used to indicate that a request is coming a cluster node.primaries
: Optional, default “1”. Determines the number of nodes that can go to origin for a given object.replicas
: Optional. If set, determines the number of long duration replicas that should be stored across the cluster.long_ttl
: Optional. TTL set for long duration replicas.long_grace
: Optional. Grace set for long duration replicas.long_keep
: Optional. Keep set for long duration replicas.long_stores
: Optional. Sets the MSE stores for long duration replicas.short_ttl
: Optional. TTL set for short duration objects.short_grace
: Optional. Grace set for short duration objects.short_keep
: Optional. Keep set for short duration objects.short_stores
: Optional. Sets the MSE stores for short duration objects.Example configuration
import activedns;
include "cluster.vcl";
backend origin { .host = "origin.node"; }
sub vcl_init {
new cluster_group = activedns.dns_group();
cluster_group.set_host("varnish.nodes:6081");
cluster.subscribe(cluster_group.get_tag());
cluster_opts.set("token", "secret");
cluster_opts.set("replicas", "2");
cluster_opts.set("short_stores", "none");
cluster_opts.set("long_stores", "ssd");
}
sub vcl_backend_fetch {
set bereq.backend = origin;
}
This configuration will self route requests among nodes resolved from varnish.nodes, and the primary node for each request will fetch from origin. When caching an object in the cluster, two nodes will store the object on SSD, while the rest of the nodes store the object in memory. TTL, grace, and keep are not modified.
Arguments:
lookahead
accepts type INTType: Method
Returns: Bool
The udo
VMOD is available in Varnish Enterprise version 6.0.8r2
and later.