Search

Score-based Routing

Score-based Routing

Introduction

This example shows how to use the Score routing type to steer traffic towards the endpoint with the most available capacity. Rather than relying solely on bandwidth utilization, a score can capture any metric that matters to your environment, memory pressure, CPU load, queue depth, and so on.

In this example each agent node runs a small script that reads the system’s free memory, converts it to a score between 1 and 100 (100 = most free memory = most preferred), and writes that score to the agent’s score file. The router then routes incoming requests to the highest-scored healthy endpoint.

See Score Routing Type for a conceptual overview and Score File for the agent configuration option.

Score Script

The following script reads /proc/meminfo, calculates the ratio of available memory to total memory, maps it to a 1–100 integer, and atomically writes the result to the score file.

#!/bin/bash
# score-memory.sh - update the agent score based on available memory.
# Schedule this with cron or a systemd timer, for example every 30 seconds.

set -euo pipefail

SCORE_FILE="/var/lib/varnish-controller/<agent-name>/score"

mem_total=$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo)
mem_available=$(awk '/^MemAvailable:/ { print $2 }' /proc/meminfo)

# Score 1–100: 100 means all memory is free (most preferred).
# The result is clamped so it is never below 1.
score=$(awk "BEGIN { s = int(($mem_available / $mem_total) * 100); print (s < 1 ? 1 : s) }")

# Write atomically to avoid the router reading a partial file.
tmp=$(mktemp "$(dirname "$SCORE_FILE")/.score.XXXXXX")
echo "$score" > "$tmp"
mv "$tmp" "$SCORE_FILE"

Replace <agent-name> with the value of the agent’s -name argument (defaults to the hostname).

Running the Script Periodically

Install a cron job that runs the script every 30 seconds:

# /etc/cron.d/varnish-controller-score
* * * * * root /usr/local/bin/score-memory.sh
* * * * * root sleep 30 && /usr/local/bin/score-memory.sh

Or use a systemd timer for more precise control:

# /etc/systemd/system/vc-score.service
[Unit]
Description=Update Varnish Controller agent score

[Service]
Type=oneshot
ExecStart=/usr/local/bin/score-memory.sh
# /etc/systemd/system/vc-score.timer
[Unit]
Description=Run vc-score every 30 seconds

[Timer]
OnBootSec=10
OnUnitActiveSec=30s

[Install]
WantedBy=timers.target
systemctl enable --now vc-score.timer

Agent Configuration

Point the agent at the score file explicitly if it is not in the default location, or rely on the default path <base-dir>/<agent-name>/score:

# Using the default path - no extra configuration needed.
varnish-controller-agent -name myagent

# Or override the path explicitly:
varnish-controller-agent -name myagent -score-file /var/lib/varnish-controller/myagent/score

Environment variable equivalent:

VARNISH_CONTROLLER_SCORE_FILE=/var/lib/varnish-controller/myagent/score

After the script runs, you can inspect the value that the router will see in the health check JSON:

$ cat /var/lib/varnish-controller/myagent/traffic_router_health.json | python3 -m json.tool
{
    "version": 1,
    "status": "healthy",
    "updated_at": "2025-01-15T12:00:30Z",
    "domains": null,
    "mbps": 312.4,
    "max_mbps": 1000.0,
    "score": 74
}

Routing Rule Configuration

Enable Score routing on an existing routing rule with vcli:

$ vcli rr update 1 --lookup-order=Score
+----+---------+------+------+-------+---------------+
| ID |  Name   | DNS  | HTTP | Order | Debug Headers |
+----+---------+------+------+-------+---------------+
|  1 | Routing | true | true | Score | false         |
+----+---------+------+------+-------+---------------+

If you want history-based stickiness as a first pass and score-based selection as a fallback, put History first:

$ vcli rr update 1 --lookup-order=History,Score
+----+---------+------+------+---------------+---------------+
| ID |  Name   | DNS  | HTTP |     Order     | Debug Headers |
+----+---------+------+------+---------------+---------------+
|  1 | Routing | true | true | History,Score | false         |
+----+---------+------+------+---------------+---------------+

Verification

Enable debug headers on the routing rule to confirm which endpoint was selected and why:

$ vcli rr update 1 --debug-headers=true

Then send a request through the router:

$ curl -s -o /dev/null -v http://<router-ip>:8900 -H "Host: mysite.example.com"
< HTTP/1.1 302 Found
< X-Router-Name: router1
< X-Router-LookupTime: 8.214µs
< X-Router-ClientIP: 203.0.113.5
< X-Router-RouteType: Score
< X-Router-Endpoint: 0-agent2
< X-Router-Trace: [Score]
< Location: http://10.0.0.12:8081/mysite.example.com/

The X-Router-RouteType: Score header confirms that score-based selection was used. The request was routed to agent2, which at that moment had the highest score (most available memory) among healthy, non-over-utilized endpoints.


®Varnish Software, Wallingatan 12, 111 60 Stockholm, Organization nr. 556805-6203