Route on Internally-Tracked Quality of Experience (QoE)

How to write ACD Router configurations to route sessions based on internally-tracked Quality of Experience (QoE) values collected from In-Stream sessions.

This page describes how to configure QoE-based routing. For background on In-Stream sessions (required for QoE tracking), see Use In-Stream Sessions. For configuration in general, see Configuration.

How QoE Tracking Works

The router tracks the quality of each In-Stream session by observing which video bitrate variant the client requests on every segment fetch. Quality is measured on a scale of 1–4, where 1 is the lowest bitrate variant and 4 is the highest.

Note: A low quality reading may reflect a mobile device whose highest useful bitrate is low — it does not necessarily indicate that the CDN is performing poorly.

These observations are aggregated per host per session group into a single score in the range [0.0, 1.0]:

score = (0 × count_q1 + 1 × count_q2 + 2 × count_q3 + 4 × count_q4) / (4 × total_count)

Quality levels are weighted exponentially (factors 0, 1, 2, 4) to give extra weight to the highest quality tier.

Score Decay

Scores decay exponentially over time so that stale observations lose influence. The decayFactor setting is applied per second; internally the decay fires every 5 seconds, so the factor applied per tick is decayFactor^5. For example, decayFactor = 0.99 results in roughly 5% decay every 5 seconds (~40% per minute).

The counter for the highest quality level (q4) never decays below 1.0. This acts as an optimistic prior: a host with no recent traffic converges toward a score of 1.0, encouraging the system to send some traffic there to build up fresh statistics.

Enabling QoE Tracking

QoE tracking is enabled per session group in services.routing.settings.qoeTracking:

$ confcli services.routing.settings.qoeTracking.enabled true
services.routing.settings.qoeTracking.enabled = True

$ confcli services.routing.settings.qoeTracking.decayFactor 0.99
services.routing.settings.qoeTracking.decayFactor = 0.99

# Use the wizard to add session group names "my_group" and "my_group_agents"
$ confcli services.routing.settings.qoeTracking.sessionGroupNames -w
sessionGroupNames: my_group
sessionGroupNames: my_group_agents
  • enabled: Master switch. If false, no scores are collected for any group.
  • decayFactor: Per-second exponential decay (applied every 5 s as decayFactor^5).
  • sessionGroupNames: Session groups for which QoE is tracked. Each group must also be defined in services.routing.sessionGroups. If left empty, all session groups are tracked.
  • requestsIgnoredAfterHostChange (optional, default 0): Number of segment requests to skip after a session moves to a new host before incrementing QoE counters. Prevents a burst of potentially stale quality readings from polluting the new host’s score.

Regular and Agent Sessions

An agent is a session that stays pinned to its assigned host even when it experiences bad quality. Agents ensure the router always has observations on every host — including hosts that are currently degraded. Without agents, a poor host would quickly lose all traffic and the router would never learn that it had recovered.

Agents are identified by any classifier. In the examples below, agents are identified by a URL query parameter (agent=1), but any classifier that isolates a small, stable subset of sessions works. The naming convention <group>_agents for the agent session group is expected by the host_weight routing function (see below).

By contrast, a regular session is allowed to move between hosts based on its current quality of experience.

Use Cases

QoE routing supports two distinct configurations depending on whether sessions need to be dynamically re-routed mid-stream.

QoE InStream SelectionQoE Initial SelectionQoE Monitoring Only
Regular sessionsIn-Stream (router proxies manifests, redirects each segment)Initial selection only (single 302 redirect at stream start)Initial selection without QoE
Agent sessionsIn-Stream, pinned to hostIn-Stream, pinned to hostIn-Stream, small fraction, not pinned to host
Routing function for regular sessionshost_weight (re-evaluated per request)agent_weight (evaluated once at stream start)N/A
Can move regular sessions mid-streamYesNoN/A
Router loadHigherLowerLower
Eager switching reqiredYesYesNo

Routing Functions

The following Lua functions are used in routing rule weight expressions.

agent_weight(session_group, default_weight, amplifier [, fallback_hosts])

Used in routing rules for agent sessions. Agents stay pinned to their assigned host regardless of quality.

SituationWeight returned
No session yet (pre-assignment)default_weight
Managed (In-Stream) session on same host as this rule10000 (retain)
Managed session on a different host (not a fallback)0 (excluded)
Managed session on a different host that is in fallback_hosts1 (allow escape from fallback)
Unmanaged (initial selection) sessionQoE score for this host

The optional fallback_hosts list prevents agents from being permanently stuck on a fallback host. If an agent’s current host is in fallback_hosts, primary hosts receive a weight of 1, allowing the agent to move back and resume generating useful statistics.

Parameters:

  1. session_group — The agent session group whose QoE scores are used for initial placement as a fallback if all in-stream sessions are used and the session can’t be a managed session.
  2. default_weight — Weight used when no session exists yet and no QoE data is available, providing initial even balancing across hosts.
  3. amplifier — Controls how strongly QoE scores influence the weight. Can be a number or the name of a selection input variable. With amplifier 2, a host at score 1.0 receives weight 10010 vs 10 for score 0.0 — a 1000× difference.
  4. fallback_hosts (optional) — List of host IDs considered fallback hosts.

host_weight(session_group, amplifier [, initial_requests_skipped])

Used in routing rules for regular (non-agent) sessions. Sessions prefer to stay on their current host when quality is good, but are likely to move when quality is bad. Routing decisions are based on the QoE scores of the corresponding agent group (session_group .. "_agents"), not the regular sessions themselves.

Decision logic:

  1. No session or no assigned host — return QoE score for this host (drawn from the _agents group).

  2. Within the initial lock period (requests_since_last_host_change <= initial_requests_skipped) — lock to the current host. This prevents flip-flopping immediately after a host change while the client may still be playing buffered content from the previous host.

  3. Past the initial lock period — draw a random number and compare against a stay probability based on current quality:

    QualityStay probabilityExpected requests before move
    ≤ 2 (bad)0.0833 (~1/12)~12 (~12 s at 1 req/s)
    > 2 (good)0.995~200 (~3 min at 1 req/s)
    • If staying: return 10000 for the current host, 1 for others.
    • If moving: return the QoE score for each host, letting the routing algorithm select the best available host.

Parameters:

  1. session_group — The regular session group. Scores are read from session_group .. "_agents".
  2. amplifier — Same meaning as in agent_weight. Use a lower value (e.g. 1) than for agents since regular sessions are re-evaluated on every request.
  3. initial_requests_skipped (optional, default 0) — Number of requests after a host change during which the session is locked to its host.

Configuration: QoE InStream Selection

All sessions run as In-Stream. The router observes every segment request and can move regular sessions mid-stream when quality degrades.

1. Classifiers and session groups

Identify agents using any classifier. This example uses a URL query parameter:

$ confcli services.routing.classifiers -w
...
name: is_agent
type: contentUrlQueryParameters
patternType: stringMatch
pattern: agent=1

$ confcli services.routing.sessionGroups -w
...
# Agent group
name: my_group_agents
classifiers: ["is_agent"]

# Regular group (everyone who is not an agent)
name: my_group
classifiers: ["not 'is_agent'"]

2. QoE tracking

$ confcli services.routing.settings.qoeTracking.enabled true
$ confcli services.routing.settings.qoeTracking.decayFactor 0.99
$ confcli services.routing.settings.qoeTracking.sessionGroupNames -w
sessionGroupNames: "my_group"
sessionGroupNames: "my_group_agents"

Both groups are tracked. Scores for my_group_agents drive routing decisions via host_weight. Scores for my_group are available for monitoring in Grafana and may in future be incorporated into routing decisions.

3. Enable In-Stream for all sessions

$ confcli services.routing.translationFunctions.session "return set_session_type('instream')"
services.routing.translationFunctions.session = "return set_session_type('instream')"

See Session Translation Function for details.

Set the services.routing.tuning.general.maxActiveManagedSessions value to a higher limit in order to ensure enough in-stream sessions can be created.

4. Enable eager CDN switching

$ confcli services.routing.tuning.general.eagerCdnSwitching true
services.routing.tuning.general.eagerCdnSwitching = True

5. Routing rules

A split rule sends agents and regular sessions to separate weighted rules:

{
  "rules": [
    {
      "name": "agent_or_regular",
      "type": "split",
      "condition": "in_session_group('my_group_agents')",
      "onMatch": "agent_weights",
      "onMiss": "regular_weights"
    },
    {
      "name": "regular_weights",
      "type": "weighted",
      "targets": [
        { "target": "host1", "weight": "host_weight('my_group', 1)", "condition": "always()" },
        { "target": "host2", "weight": "host_weight('my_group', 1)", "condition": "always()" }
      ]
    },
    {
      "name": "agent_weights",
      "type": "weighted",
      "targets": [
        { "target": "host1", "weight": "agent_weight('my_group_agents', 100, 2.0, {'host2'})", "condition": "always()" },
        { "target": "host2", "weight": "agent_weight('my_group_agents', 100, 2.0, {'host1'})", "condition": "always()" }
      ]
    }
  ]
}

Note the amplifier is 1 for host_weight (regular sessions) and 2 for agent_weight. Regular sessions are re-evaluated on every segment request so even a modest preference for better hosts moves traffic effectively. Agents only go through initial QoE-based placement once, so a stronger preference is appropriate.

$ confcli services.routing.entrypoint agent_or_regular
services.routing.entrypoint = 'agent_or_regular'

Configuration: QoE Initial Selection

Only agents run as In-Stream. Regular sessions receive a single CDN selection at stream start, chosen using the QoE scores the agents have accumulated. Regular sessions cannot be re-routed mid-stream.

In this mode a single agent_weight rule handles both agents (which stay on their assigned host) and regular sessions (which receive a one-time QoE-scored placement). No separate host_weight rule is needed.

Agents do not need a separate classifier — a fraction of sessions in the target group is automatically promoted to In-Stream by managedSessions.

1. Session groups

A single session group covers all sessions:

$ confcli services.routing.sessionGroups -w
...
name: qoe_initial
classifiers: ["all"]

2. QoE tracking

$ confcli services.routing.settings.qoeTracking.enabled true
$ confcli services.routing.settings.qoeTracking.decayFactor 0.99
$ confcli services.routing.settings.qoeTracking.sessionGroupNames '["qoe_initial"]'

3. Make a fraction of sessions In-Stream (agents)

Use managedSessions to promote a fraction of sessions to In-Stream. These become the agents. The rest remain as initial-selection sessions.

$ confcli services.routing.settings.managedSessions.fraction 0.1
services.routing.settings.managedSessions.fraction = 0.1

$ confcli services.routing.settings.managedSessions.sessionTypes -w
...
type: instream
enabled: true
sessionGroupNames: ["qoe_initial"]
maxActive: 1000

Keep the agent fraction small (5–10% is a reasonable starting point). Agents are pinned to their host even under poor quality conditions, so a larger fraction increases viewer exposure to any degraded host.

Do not set translationFunctions.session to instream here — that would override the managedSessions fraction and make all sessions In-Stream.

4. Enable eager CDN switching

$ confcli services.routing.tuning.general.eagerCdnSwitching true
services.routing.tuning.general.eagerCdnSwitching = True

5. Routing rules

One weighted rule handles both agents and regular sessions:

{
  "rules": [
    {
      "name": "qoe_initial_weights",
      "type": "weighted",
      "targets": [
        {
          "target": "host1",
          "weight": "agent_weight('qoe_initial', 100, 2)",
          "condition": "in_session_group('qoe_initial')"
        },
        {
          "target": "host2",
          "weight": "agent_weight('qoe_initial', 100, 2)",
          "condition": "in_session_group('qoe_initial')"
        }
      ]
    }
  ]
}

The amplifier is set higher (2) than in the InStream example because initial placement happens only once per session — a stronger preference for high-quality hosts is needed to move traffic effectively.

$ confcli services.routing.entrypoint qoe_initial_weights
services.routing.entrypoint = 'qoe_initial_weights'

Monitoring

If Grafana is installed together with the ESB-3024 installer, the “Quality of Experience” dashboard provides insight into QoE scores per host and session group, including current score values and session movement over time.

Notes on Performance

QoE routing requires In-Stream sessions for at least a subset of traffic (all sessions in the InStream configuration, agents only in the Initial Selection configuration). In-Stream sessions put additional load on the router since clients return to the router for every manifest and segment redirect. See Use In-Stream Sessions for further performance guidance.