Optimizing generated VCL
Fastly compiles and optimizes your VCL when you upload and activate a service so that it runs efficiently across our servers. You can structure your VCL to reduce the work required before activation, which in turn reduces deployment and activation times, especially when automating updates through the API.
For smaller services, these techniques have a minimal effect. They do, however, make your code cleaner and easier to understand. The actual execution speed of a running service is the same either way.
The techniques in this guide apply especially to generated VCL. Generated code tends to have repetitive structures. The following sections cover some of the most common patterns and how to improve them.
Use the least powerful feature you can
If a simpler function does the job, use it instead of a more powerful one. For example, if you are looking for a string prefix, use std.prefixof instead of a regex. Regexes are fine for isolated cases, but if you are generating thousands of them, the overhead adds up.
There are two reasons for this:
- Fastly compiles both to the same thing. Determining that a regex is equivalent to a string prefix match takes work, though. For small services this is negligible. For extremely large services, avoiding that work reduces compilation time.
- Regexes matching URLs and filenames are often written as
if (... ~ "xyz.html")where the unescaped.acts as an "any character" wildcard match rather than a literal dot. This prevents the VCL compiler from treating the string as a literal and making the substitution automatically, because the literal stringxyz.htmlis not equivalent to the regexxyz.html.
Use std.prefixof, std.suffixof, std.strstr, and similar functions where possible. This avoids unintentional wildcard matches from unescaped characters.
Use local variables instead of headers
When you need temporary storage during a subroutine, use local variables rather than headers.
Because of the semantics of HTTP, headers are stored in a data structure that tracks multiple instances and must deal with appending values. Local variables have lighter-weight memory tracking internally to Varnish.
Headers also persist longer than a single subroutine and need explicit cleanup if you do not want them to remain during the rest of processing.
If you have an integer, use an integer type
Headers are strings, so any other data you keep there requires formatting to a string representation and parsing back again. Local variables can be whatever type suits your data.
Before:
set req.http.tmp-bucket = randomint(0, 10);...unset req.http.tmp-bucket;After:
declare local var.bucket INTEGER = randomint(0, 10);...Factor out code into custom subroutines with parameters and return values
When you have blocks of code that repeat across subroutines, move them into user-defined subroutines. User-defined subroutines accept arguments and return values, so you no longer need to pass data through headers and clean them up afterward. This also keeps your local variables more tightly scoped, which helps the compiler with static analysis because it has fewer possibilities to consider at once. Local variables also support block scope within a subroutine, allowing you to restrict their scope even further.
Before:
123456789101112sub regional_indicator { set req.http.X-ri = table.lookup(unicode_ri, substr(req.http.X-code, 0, 1)) + table.lookup(unicode_ri, substr(req.http.X-code, 1, 1));}
sub vcl_recv { set req.http.X-code = client.geo.country_code; call regional_indicator; unset req.http.X-code; log req.http.X-ri; unset req.http.X-ri;}After:
12345678910sub regional_indicator(STRING var.code) STRING { declare local var.ri STRING; set var.ri = table.lookup(unicode_ri, substr(var.code, 0, 1)) + table.lookup(unicode_ri, substr(var.code, 1, 1)); return var.ri;}
sub vcl_recv { log "regional indicator: " + regional_indicator(client.geo.country_code);}Use "else" when there is no fall-through
When every branch in a chain of if statements terminates with error, return, or restart, or when the conditions are mutually exclusive, chain them with else. This makes no semantic difference, but it helps the compiler with branch analysis. Without else, the compiler spends time on branch analysis only to end up generating the same assembly anyway.
Before:
1234567891011121314151617if (req.http.host == "a.example.com") { error 801;}
if (req.http.host == "b.example.com") { error 802;}
if (req.http.host == "c.example.com") { error 803;}
if (req.http.host == "d.example.com") { error 803;}
...After:
12345678910if (req.http.host == "a.example.com") { error 801;} else if (req.http.host == "b.example.com") { error 802;} else if (req.http.host == "c.example.com") { error 803;} else if (req.http.host == "d.example.com") { error 804;}...Use a table for key/value lookup
When you are testing a variable against constant strings and setting constant values for each match, use a table instead of a chain of if statements. Tables are also faster to execute because they avoid testing one key at a time.
Before:
123456789101112131415161718if (req.http.host == "at.example.net") { set req.http.host = "www.example.com/at"; error 800;}if (req.http.host == "de.example.net") { set req.http.host = "www.example.com/de"; error 800;}if (req.http.host == "se.example.net") { set req.http.host = "www.example.com/se"; error 800;}if (req.http.host == "nl.example.net") { set req.http.host = "www.example.com/nl"; error 800;}
...After:
1234567891011121314table t1 { "at.example.net": "www.example.com/at", "de.example.net": "www.example.com/de", "se.example.net": "www.example.com/se", "nl.example.net": "www.example.com/nl", ...}
declare local var.s STRING;set var.s = table.lookup(t1, req.http.host);if (var.s) { set req.http.host = var.s; error 800;}Use non-capturing groups in regexes where group capture is not necessary
When a regex group exists only for grouping and you do not need the captured value, use a non-capturing group ((?:...)). In many cases, this allows the regex to be compiled ahead of time rather than executing dynamically because the remaining syntax does not require backtracking. For more detail, check out our regex best practices guide.
Before:
# two (...) groups here, both capturing unnecessarilyif (req.url.path ~ "/([a-z-]+/)?bathtubs\.html(/?)$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/en/products/bathtubs";}After:
# the first group is made (?:...) for non-capturing,# the second does not need to be a group at allif (req.url.path ~ "/(?:[a-z-]+/)?bathtubs\.html/?$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/en/products/bathtubs";}Factor out common suffixes or prefixes
When multiple conditions share a common pattern in the URL path, extract the shared part so you can test the unique part with simpler string comparisons instead of repeating the full regex for every case. In the following example, the value of interest is req.url.basename, which allows you to skip using regular expressions for each individual match. You can also use else because the paths are mutually exclusive.
Before:
123456789101112131415161718192021222324if (req.url.path ~ "/(?:[a-z-]+/)?rektanglar\.html/?$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/rektanglar";}
if (req.url.path ~ "/(?:[a-z-]+/)?trianglar\.html/?$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/trianglar";}
if (req.url.path ~ "/(?:[a-z-]+/)?kvadrater\.html/?$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/kvadrater";}
if (req.url.path ~ "/(?:[a-z-]+/)?enkelsidiga-former\.html/?$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/enkelsidiga-former";}
if (req.url.path ~ "/(?:[a-z-]+/)?nollsidiga-former\.html/?$") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/nollsidiga-former";}After:
123456789101112131415161718if (req.url.path ~ "/(?:[a-z-]+/)?") { # note (?:...) for no group capture if (req.url.basename == "rektanglar.html") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/rektanglar"; } else if (req.url.basename == "trianglar.html") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/trianglar"; } else if (req.url.basename == "kvadrater.html") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/kvadrater"; } else if (req.url.basename == "enkelsidiga-former.html") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/enkelsidiga-former"; } else if (req.url.basename == "nollsidiga-former.html") { set req.http.X-Redirect-Url = "https://" + req.http.host + "/se/products/nollsidiga-former"; }}This can then be rewritten as a table:
123456789101112131415table t2 { "rektanglar.html": "/se/products/rektanglar", "trianglar.html": "/se/products/trianglar", "kvadrater.html": "/se/products/kvadrater", "enkelsidiga-former.html": "/se/products/enkelsidiga-former", "nollsidiga-former.html": "/se/products/nollsidiga-former"}
if (req.url.path ~ "/(?:[a-z-]+/)?") { declare local var.path STRING; set var.path = table.lookup(t2, req.url.basename); if (var.path) { set req.http.X-Redirect-Url = "https://" + req.http.host + var.path; }}