---
title: registry
---

<div class="python-reference">
<a id="fastvideo.entrypoints.streaming.router.registry-attributes"></a>
<a id="fastvideo.entrypoints.streaming.router.registry-classes"></a>
<a id="fastvideo.entrypoints.streaming.router.registry-functions"></a>
<a id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry-functions"></a>
<a id="fastvideo.entrypoints.streaming.router.registry"></a>
<p>Replica registry + health-check loop.</p>
<p>The registry tracks the set of known backend replicas and their live
health. The router consults it for "pick a backend for this session"
decisions and a background task updates it from periodic HTTP probes.</p>
<p>State machine per replica::</p>
<pre><code>HEALTHY ──(N consecutive failures)──▶ UNHEALTHY
   ▲                                     │
   └──────(M consecutive successes)──────┘
</code></pre>
<p>Where N = :attr:<code>RouterConfig.failure_threshold</code> and
M = :attr:<code>RouterConfig.recovery_threshold</code>.</p>
<h2 id="fastvideo.entrypoints.streaming.router.registry.ReplicaEndpoint"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaEndpoint</code></h2>
<pre class="doc-signature"><code>ReplicaEndpoint(url: str, name: str | None = None, primary: bool = False, weight: float = 1.0)</code></pre>
<p>One backend replica the router can route to.</p>

<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaEndpoint.url"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaEndpoint.url</code></h3>
<pre class="doc-signature"><code>url: str</code></pre>
<p>HTTP base URL, e.g. <code>http://host:8000</code>. WebSocket URL is
derived automatically by replacing the scheme.</p>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaEndpoint.primary"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaEndpoint.primary</code></h3>
<pre class="doc-signature"><code>primary: bool = False</code></pre>
<p><code>True</code> = prefer this replica over others in steady state.</p>
<h2 id="fastvideo.entrypoints.streaming.router.registry.RouterConfig"><code>fastvideo.entrypoints.streaming.router.registry.RouterConfig</code></h2>
<pre class="doc-signature"><code>RouterConfig(host: str = &#x27;0.0.0.0&#x27;, port: int = 9000, replicas: list&#91;ReplicaEndpoint&#93; = list(), health_check_path: str = &#x27;/health&#x27;, health_check_interval_seconds: float = 5.0, health_check_timeout_seconds: float = 2.0, failure_threshold: int = 3, recovery_threshold: int = 2)</code></pre>
<p>Typed router config loaded from a YAML file.</p>
<p>Example::</p>
<pre><code>router:
  host: 0.0.0.0
  port: 9000
  replicas:
    - url: http://streamer-a:8000
      primary: true
    - url: http://streamer-b:8000
  health_check:
    path: /health
    interval_seconds: 5
    failure_threshold: 3
</code></pre>
<p>Validation runs in <code>__post_init__</code>: empty replicas, non-positive
intervals/timeouts, thresholds &lt; 1, non-http(s) URLs, and more than
one primary all raise <code>ValueError</code> so misconfigurations surface at
load time rather than as confusing runtime failures.</p>

<h2 id="fastvideo.entrypoints.streaming.router.registry.init_logger"><code>fastvideo.entrypoints.streaming.router.registry.init_logger</code></h2>
<pre class="doc-signature"><code>init_logger(name: str) -&gt; _FastvideoLogger</code></pre>
<p>The main purpose of this function is to ensure that loggers are
retrieved in such a way that we can be sure the root fastvideo logger has
already been configured.</p>
<h2 id="fastvideo.entrypoints.streaming.router.registry.HttpProbe"><code>fastvideo.entrypoints.streaming.router.registry.HttpProbe</code></h2>
<pre class="doc-signature"><code>HttpProbe = Any</code></pre>
<p>Structural alias for health-probe callables. Concrete signature is
<code>async def __call__(url: str, *, timeout: float) -&gt; tuple[float,
str | None]</code>; typing.Callable cannot express keyword-only parameters,
so duck-typing is the pragmatic compromise.</p>
<details><summary>Source</summary><pre><code>HttpProbe = Any</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.router.registry.ReplicaStatus"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaStatus</code></h2>
<pre class="doc-signature"><code>ReplicaStatus()</code></pre>

<details><summary>Source</summary><pre><code>class ReplicaStatus(enum.Enum):
    UNKNOWN = &quot;unknown&quot;
    HEALTHY = &quot;healthy&quot;
    UNHEALTHY = &quot;unhealthy&quot;</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.router.registry.ReplicaHealth"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaHealth</code></h2>
<pre class="doc-signature"><code>ReplicaHealth(status: ReplicaStatus = ReplicaStatus.UNKNOWN, last_ok_at: float | None = None, last_failure_at: float | None = None, consecutive_failures: int = 0, consecutive_successes: int = 0, last_latency_ms: float | None = None)</code></pre>


<details><summary>Source</summary><pre><code>@dataclass
class ReplicaHealth:
    status: ReplicaStatus = ReplicaStatus.UNKNOWN
    last_ok_at: float | None = None
    last_failure_at: float | None = None
    consecutive_failures: int = 0
    consecutive_successes: int = 0
    last_latency_ms: float | None = None</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.router.registry.Replica"><code>fastvideo.entrypoints.streaming.router.registry.Replica</code></h2>
<pre class="doc-signature"><code>Replica(endpoint: ReplicaEndpoint, health: ReplicaHealth = ReplicaHealth())</code></pre>


<details><summary>Source</summary><pre><code>@dataclass
class Replica:
    endpoint: ReplicaEndpoint
    health: ReplicaHealth = field(default_factory=ReplicaHealth)

    @property
    def url(self) -&gt; str:
        return self.endpoint.url

    @property
    def primary(self) -&gt; bool:
        return self.endpoint.primary

    @property
    def is_healthy(self) -&gt; bool:
        return self.health.status is ReplicaStatus.HEALTHY</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry</code></h2>
<pre class="doc-signature"><code>ReplicaRegistry(replicas: list&#91;ReplicaEndpoint&#93;)</code></pre>
<p>Stateful map of replica URL → :class:<code>Replica</code>.</p>
<p>Selection favors primary replicas when healthy; otherwise the first
healthy non-primary is returned. When none are healthy, the
registry returns <code>None</code> so the router can reject incoming
sessions with <code>gpu_unavailable</code>.</p>

<details><summary>Source</summary><pre><code>class ReplicaRegistry:
    &quot;&quot;&quot;Stateful map of replica URL → :class:`Replica`.

    Selection favors primary replicas when healthy; otherwise the first
    healthy non-primary is returned. When none are healthy, the
    registry returns ``None`` so the router can reject incoming
    sessions with ``gpu_unavailable``.
    &quot;&quot;&quot;

    def __init__(self, replicas: list&#91;ReplicaEndpoint&#93;) -&gt; None:
        if not replicas:
            raise ValueError(&quot;ReplicaRegistry requires at least one replica&quot;)
        self._replicas: dict&#91;str, Replica&#93; = &#123;endpoint.url: Replica(endpoint=endpoint) for endpoint in replicas&#125;
        self._lock = asyncio.Lock()

    def all(self) -&gt; list&#91;Replica&#93;:
        return list(self._replicas.values())

    def get(self, url: str) -&gt; Replica | None:
        return self._replicas.get(url)

    def primaries(self) -&gt; list&#91;Replica&#93;:
        return &#91;r for r in self._replicas.values() if r.primary&#93;

    def select(self) -&gt; Replica | None:
        &quot;&quot;&quot;Pick the best healthy replica.

        Priority order:

        1. The first healthy primary (insertion order).
        2. The first healthy non-primary (insertion order).
        3. ``None`` when nothing is healthy.

        This MVP picks the first match within each tier; it does NOT
        load-balance across multiple healthy replicas of the same tier.
        Round-robin and weighted distribution are deferred until a real
        N-way active deployment exists.
        &quot;&quot;&quot;
        healthy_primaries = &#91;r for r in self._replicas.values() if r.primary and r.is_healthy&#93;
        if healthy_primaries:
            return healthy_primaries&#91;0&#93;
        healthy = &#91;r for r in self._replicas.values() if r.is_healthy&#93;
        if healthy:
            return healthy&#91;0&#93;
        return None

    async def record_success(
        self,
        replica: Replica,
        *,
        recovery_threshold: int,
        latency_ms: float,
    ) -&gt; None:
        async with self._lock:
            h = replica.health
            h.last_ok_at = time.time()
            h.last_latency_ms = latency_ms
            h.consecutive_failures = 0
            h.consecutive_successes += 1
            # State machine: UNKNOWN -&gt; HEALTHY is immediate; only the
            # UNHEALTHY -&gt; HEALTHY transition is gated by recovery_threshold.
            if h.status is ReplicaStatus.UNKNOWN:
                logger.info(&quot;router: replica %s initial probe ok, marking HEALTHY&quot;, replica.url)
                h.status = ReplicaStatus.HEALTHY
                h.consecutive_successes = 0
            elif (h.status is ReplicaStatus.UNHEALTHY and h.consecutive_successes &gt;= recovery_threshold):
                logger.info(&quot;router: replica %s recovered to HEALTHY after %d successes&quot;, replica.url,
                            h.consecutive_successes)
                h.status = ReplicaStatus.HEALTHY
                h.consecutive_successes = 0

    async def record_failure(
        self,
        replica: Replica,
        *,
        failure_threshold: int,
        reason: str,
    ) -&gt; None:
        async with self._lock:
            h = replica.health
            h.last_failure_at = time.time()
            h.consecutive_successes = 0
            h.consecutive_failures += 1
            if (h.status is not ReplicaStatus.UNHEALTHY and h.consecutive_failures &gt;= failure_threshold):
                logger.warning(&quot;router: replica %s marked UNHEALTHY after %d failures: %s&quot;, replica.url,
                               h.consecutive_failures, reason)
                h.status = ReplicaStatus.UNHEALTHY</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.all"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.all</code></h3>
<pre class="doc-signature"><code>all() -&gt; list&#91;Replica&#93;</code></pre>

<details><summary>Source</summary><pre><code>def all(self) -&gt; list&#91;Replica&#93;:
    return list(self._replicas.values())</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.get"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.get</code></h3>
<pre class="doc-signature"><code>get(url: str) -&gt; Replica | None</code></pre>

<details><summary>Source</summary><pre><code>def get(self, url: str) -&gt; Replica | None:
    return self._replicas.get(url)</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.primaries"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.primaries</code></h3>
<pre class="doc-signature"><code>primaries() -&gt; list&#91;Replica&#93;</code></pre>

<details><summary>Source</summary><pre><code>def primaries(self) -&gt; list&#91;Replica&#93;:
    return &#91;r for r in self._replicas.values() if r.primary&#93;</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.select"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.select</code></h3>
<pre class="doc-signature"><code>select() -&gt; Replica | None</code></pre>
<p>Pick the best healthy replica.</p>
<p>Priority order:</p>
<ol>
<li>The first healthy primary (insertion order).</li>
<li>The first healthy non-primary (insertion order).</li>
<li><code>None</code> when nothing is healthy.</li>
</ol>
<p>This MVP picks the first match within each tier; it does NOT
load-balance across multiple healthy replicas of the same tier.
Round-robin and weighted distribution are deferred until a real
N-way active deployment exists.</p>
<details><summary>Source</summary><pre><code>def select(self) -&gt; Replica | None:
    &quot;&quot;&quot;Pick the best healthy replica.

    Priority order:

    1. The first healthy primary (insertion order).
    2. The first healthy non-primary (insertion order).
    3. ``None`` when nothing is healthy.

    This MVP picks the first match within each tier; it does NOT
    load-balance across multiple healthy replicas of the same tier.
    Round-robin and weighted distribution are deferred until a real
    N-way active deployment exists.
    &quot;&quot;&quot;
    healthy_primaries = &#91;r for r in self._replicas.values() if r.primary and r.is_healthy&#93;
    if healthy_primaries:
        return healthy_primaries&#91;0&#93;
    healthy = &#91;r for r in self._replicas.values() if r.is_healthy&#93;
    if healthy:
        return healthy&#91;0&#93;
    return None</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.record_success"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.record_success</code></h3>
<pre class="doc-signature"><code>record_success(replica: Replica, *, recovery_threshold: int, latency_ms: float) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>async def record_success(
    self,
    replica: Replica,
    *,
    recovery_threshold: int,
    latency_ms: float,
) -&gt; None:
    async with self._lock:
        h = replica.health
        h.last_ok_at = time.time()
        h.last_latency_ms = latency_ms
        h.consecutive_failures = 0
        h.consecutive_successes += 1
        # State machine: UNKNOWN -&gt; HEALTHY is immediate; only the
        # UNHEALTHY -&gt; HEALTHY transition is gated by recovery_threshold.
        if h.status is ReplicaStatus.UNKNOWN:
            logger.info(&quot;router: replica %s initial probe ok, marking HEALTHY&quot;, replica.url)
            h.status = ReplicaStatus.HEALTHY
            h.consecutive_successes = 0
        elif (h.status is ReplicaStatus.UNHEALTHY and h.consecutive_successes &gt;= recovery_threshold):
            logger.info(&quot;router: replica %s recovered to HEALTHY after %d successes&quot;, replica.url,
                        h.consecutive_successes)
            h.status = ReplicaStatus.HEALTHY
            h.consecutive_successes = 0</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.record_failure"><code>fastvideo.entrypoints.streaming.router.registry.ReplicaRegistry.record_failure</code></h3>
<pre class="doc-signature"><code>record_failure(replica: Replica, *, failure_threshold: int, reason: str) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>async def record_failure(
    self,
    replica: Replica,
    *,
    failure_threshold: int,
    reason: str,
) -&gt; None:
    async with self._lock:
        h = replica.health
        h.last_failure_at = time.time()
        h.consecutive_successes = 0
        h.consecutive_failures += 1
        if (h.status is not ReplicaStatus.UNHEALTHY and h.consecutive_failures &gt;= failure_threshold):
            logger.warning(&quot;router: replica %s marked UNHEALTHY after %d failures: %s&quot;, replica.url,
                           h.consecutive_failures, reason)
            h.status = ReplicaStatus.UNHEALTHY</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.router.registry.run_health_check_loop"><code>fastvideo.entrypoints.streaming.router.registry.run_health_check_loop</code></h2>
<pre class="doc-signature"><code>run_health_check_loop(registry: ReplicaRegistry, config: RouterConfig, *, stop_event: asyncio.Event, http_get: HttpProbe | None = None) -&gt; None</code></pre>
<p>Poll all replicas' health endpoints in parallel on a fixed interval.</p>
<p><code>http_get</code> is pluggable so unit tests can inject a deterministic
probe without hitting the network. The default builds a single
<code>httpx.AsyncClient</code> shared across the loop's lifetime so the
common case (steady polling against a stable replica set) reuses
TCP/TLS connections instead of paying handshake cost per probe.</p>
<p>Probes within one polling cycle run concurrently via <code>asyncio.gather</code>
so a slow replica doesn't push the cycle past
<code>health_check_interval_seconds</code>.</p>
<details><summary>Source</summary><pre><code>async def run_health_check_loop(
    registry: ReplicaRegistry,
    config: RouterConfig,
    *,
    stop_event: asyncio.Event,
    http_get: HttpProbe | None = None,
) -&gt; None:
    &quot;&quot;&quot;Poll all replicas&#x27; health endpoints in parallel on a fixed interval.

    ``http_get`` is pluggable so unit tests can inject a deterministic
    probe without hitting the network. The default builds a single
    ``httpx.AsyncClient`` shared across the loop&#x27;s lifetime so the
    common case (steady polling against a stable replica set) reuses
    TCP/TLS connections instead of paying handshake cost per probe.

    Probes within one polling cycle run concurrently via ``asyncio.gather``
    so a slow replica doesn&#x27;t push the cycle past
    ``health_check_interval_seconds``.
    &quot;&quot;&quot;
    if http_get is not None:
        await _run_loop(registry, config, stop_event, http_get)
        return
    async with _build_default_probe(config) as probe:
        await _run_loop(registry, config, stop_event, probe)</code></pre></details>
</div>
