> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Scale Operations

> Architectural patterns for running multiple Livepeer orchestrator nodes at data-centre scale – multi-orchestrator setup, capacity management, rolling updates, fleet-wide monitoring, and enterprise onboarding.

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const TableCell = ({children, align = "left", header = false, style = {}, className = "", ...rest}) => {
  const Component = header ? "th" : "td";
  return <Component className={className} style={{
    padding: "0.75rem 1rem",
    textAlign: align,
    border: header ? "none" : "1px solid var(--lp-color-border-default)",
    ...style
  }} {...rest}>
      {children}
    </Component>;
};

export const TableRow = ({children, header = false, hover = false, style = {}, className = "", ...rest}) => {
  const rowId = `table-row-${Math.random().toString(36).substr(2, 9)}`;
  return <>
      {hover && <style>{`
          #${rowId}:hover {
            background-color: var(--lp-color-bg-card);
          }
        `}</style>}
      <tr id={rowId} className={className} style={{
    ...header && ({
      backgroundColor: "var(--lp-color-accent-strong)",
      color: "var(--lp-color-on-accent)",
      fontWeight: "bold"
    }),
    ...style
  }} {...rest}>
        {children}
      </tr>
    </>;
};

export const StyledTable = ({children, variant = "default", style = {}, className = "", ...rest}) => {
  const wrapperVariants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)",
      overflow: "hidden"
    },
    bordered: {
      border: "2px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-page)",
      overflow: "hidden"
    },
    minimal: {
      border: "none",
      backgroundColor: "transparent",
      overflow: "visible"
    }
  };
  return <div data-docs-styled-table-shell className={className} style={{
    width: "100%",
    padding: 0,
    margin: 0,
    ...wrapperVariants[variant],
    ...style
  }} {...rest}>
      <table data-docs-styled-table style={{
    width: "100%",
    borderCollapse: "collapse",
    borderSpacing: 0,
    margin: 0,
    backgroundColor: "transparent"
  }}>
        {children}
      </table>
    </div>;
};

Run a fleet when one Orchestrator is no longer enough for the GPU footprint, failure isolation, or regional coverage you need. The sections below cover the multi-node patterns, worker scaling model, rollout mechanics, and enterprise handoff points that apply once you are beyond a single machine.

<CustomDivider />

## When you need fleet operations

Single-node operation is appropriate for most Orchestrators. You are in fleet territory when these conditions start to apply:

* Your workload requires more GPU capacity than fits in one machine
* You need geographic distribution for latency-sensitive AI workloads
* You want to separate concerns – one node for reward calling, one for ticket redemption, separate GPU workers
* You are operating at data-centre scale with SLA commitments to Gateway operators

Start with a single node unless one of these conditions already applies. Fleet architecture adds operational complexity, and the gains only justify that overhead once scale pressure is already present.

<CustomDivider />

## Multi-Orchestrator architecture

go-livepeer supports running multiple Orchestrator nodes behind a single on-chain identity. This is documented in `doc/multi-o.md` in the go-livepeer repository.

The key insight: **each Orchestrator node has its own keypair and accepts payments on behalf of the same on-chain registered Ethereum address.** Node separation allows you to assign specific functions to specific nodes:

```mermaid theme={"theme":{"light":"github-light","dark":"dark-plus"}}
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#18794E', 'primaryTextColor': '#fff', 'primaryBorderColor': '#3CB540', 'lineColor': '#3CB540', 'mainBkg': '#18794E', 'nodeBorder': '#3CB540', 'clusterBkg': 'transparent', 'clusterBorder': '#3CB540', 'titleColor': '#3CB540', 'edgeLabelBackground': 'transparent', 'textColor': '#3CB540', 'nodeTextColor': '#fff'}}}%%
graph TD
    GW["Gateways"] --> LB["Load Balancer\n(optional)"]
    LB --> O1["Orchestrator Node A\n(workload processing)"]
    LB --> O2["Orchestrator Node B\n(workload processing)"]
    O1 --> W1["GPU Workers"]
    O2 --> W2["GPU Workers"]
    separate["Separate nodes:"]
    R["Reward Node\n(calls reward() each round)"]
    T["Ticket Redeemer Node\n(redeems winning tickets)"]
    separate -.-> R
    separate -.-> T
```

**Separation patterns:**

<StyledTable variant="bordered">
  <TableRow header>
    <TableCell header>Node role</TableCell>
    <TableCell header>What it does</TableCell>
    <TableCell header>Why separate it</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Workload nodes</TableCell>
    <TableCell>Process transcoding and AI inference jobs</TableCell>
    <TableCell>Scale horizontally; restart without affecting rewards</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Reward node</TableCell>
    <TableCell>Calls `reward()` each round</TableCell>
    <TableCell>Reward safety – dedicated stable machine, not affected by workload disruption</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Redeemer node</TableCell>
    <TableCell>Redeems winning payment tickets</TableCell>
    <TableCell>High-volume ticket processing on dedicated hardware</TableCell>
  </TableRow>
</StyledTable>

This pattern is architecturally similar to the [Siphon split setup](/v2/Orchestrators/guides/deployment-details/siphon-setup). The same principle separates reward calling from workload processing, but the split is implemented entirely within go-livepeer instead of through OrchestratorSiphon.

<Card title="doc/multi-o.md  -  go-livepeer" icon="github" href="https://github.com/livepeer/go-livepeer/blob/master/doc/multi-o.md">
  The canonical multi-Orchestrator architecture documentation in the go-livepeer repository.
</Card>

<CustomDivider />

## Scaling GPU workers

Whether you are running a single Orchestrator or a fleet, GPU workers scale horizontally. Each worker connects to an orchestrator with `-orchSecret` and the orchestrator distributes segments across all connected workers.

**Adding capacity:**

1. Provision a new machine with NVIDIA GPU and drivers
2. Install go-livepeer
3. Start in transcoder mode:
   ```bash icon="terminal" title="Add a worker to the fleet" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
   livepeer \
     -transcoder \
     -orchAddr <orchestrator-host>:8935 \
     -orchSecret <shared-secret> \
     -nvidia 0,1,2 \
     -maxSessions 10
   ```
4. The Orchestrator immediately begins routing to the new worker – no configuration change on the Orchestrator required

Orchestrator logs confirm each new connection:

```icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
Got a RegisterTranscoder request from transcoder=10.3.27.5 capacity=10
```

<CustomDivider />

## Capacity management at fleet scale

Each worker advertises its capacity (the `-maxSessions` value). The Orchestrator tracks capacity across all connected workers and routes jobs accordingly. There is no manual load balancing step – go-livepeer handles distribution internally.

**What to monitor fleet-wide:**

<StyledTable variant="bordered">
  <TableRow header>
    <TableCell header>Metric</TableCell>
    <TableCell header>What it reveals</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Sessions per worker</TableCell>
    <TableCell>Whether load is balanced or some workers are always idle</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Workers at max sessions</TableCell>
    <TableCell>Your fleet is at capacity – add workers or increase per-worker `maxSessions`</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Worker disconnections</TableCell>
    <TableCell>Hardware issues, network instability, or software crashes</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Segment failure rate per worker</TableCell>
    <TableCell>A single problematic worker pulling down fleet-wide success rate</TableCell>
  </TableRow>
</StyledTable>

For Prometheus fleet monitoring, run the [livepeer/livepeer-monitoring](https://github.com/livepeer/livepeer-monitoring) Docker image configured with all worker node addresses:

```bash icon="terminal" title="Run the monitoring stack for a fleet" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
docker run --net=host \
  --env LP_MODE=standalone \
  --env LP_NODES=worker1:7935,worker2:7935,worker3:7935,orch:7935 \
  livepeer/monitoring:latest
```

<CustomDivider />

## Rolling updates

Updating a single-node Orchestrator drops all in-flight sessions. At fleet scale, you can do rolling updates to minimise disruption:

**Basic rolling update procedure:**

1. **Remove one node from the rotation.** Update the load balancer or `-orchAddr` configs to stop routing to the node being updated. Wait for in-flight sessions to complete (typically a few minutes).
2. **Update the node.** Pull the new go-livepeer binary and restart the service.
3. **Verify the updated node.** Confirm it connects and is receiving sessions before proceeding.
4. **Repeat for remaining nodes.**

This requires at least two workload nodes to maintain service continuity during updates. With a single Orchestrator, updates are always disruptive.

<Note>
  Workers reconnect automatically when an Orchestrator restarts. From a worker's perspective, the Orchestrator briefly disappears and then reappears. No manual action is needed on the worker side.
</Note>

<CustomDivider />

## Network and key management at scale

Fleet operations introduce key management complexity that does not exist on single-node deployments.

**Key considerations:**

* Each Orchestrator node needs access to the same Ethereum keystore to accept payments on behalf of your on-chain address. Distribute the keystore file carefully – only over encrypted channels, with restricted file permissions on each machine.
* For reward calling, you want exactly **one** node calling `reward()` per round. Running reward calling on multiple nodes risks duplicate submissions and wasted gas. Designate a single node for reward calling and set `-reward=false` on all others.
* For ticket redemption, the Redeemer can be run as a separate process. See `doc/redeemer.md` in the go-livepeer repository.
* Static IPs or stable DNS names are essential at fleet scale – your service URI is stored on-chain and must resolve consistently.

<CustomDivider />

## Enterprise and data-centre onboarding

If you are operating at data-centre scale, multiple co-location sites, or with commercial-grade SLA requirements, the Livepeer Foundation offers direct engagement support.

<Card title="Contact Livepeer Foundation" icon="building" href="https://livepeer.org/contact">
  For enterprise and data-centre operators. Direct support for fleet integration, custom Gateway relationships, and commercial partnership discussions.
</Card>

The SPE (Special Purpose Entity) programme is the primary pathway for professional GPU operators who want a structured relationship with the network. Current SPEs include Titan Node (video mining) and MuxionLabs (formerly known as the AI SPE). See the Livepeer Forum and Discord for active SPE discussions.

<CustomDivider />

<CardGroup cols={2}>
  <Card title="Run a Pool" icon="server" href="/v2/orchestrators/guides/advanced-operations/pool-operators">
    Pool operations – accepting worker connections and managing off-chain payouts.
  </Card>

  <Card title="Split O-T Setup" icon="plug" href="/v2/orchestrators/guides/deployment-details/siphon-setup">
    The foundational split between Orchestrator and transcoder processes.
  </Card>

  <Card title="Siphon Setup" icon="shield-check" href="/v2/orchestrators/guides/deployment-details/siphon-setup">
    The reward-safe split setup using OrchestratorSiphon.
  </Card>

  <Card title="Metrics and Monitoring" icon="gauge" href="/v2/orchestrators/guides/monitoring-and-tooling/metrics-and-alerting">
    Scaling Prometheus monitoring to a multi-node fleet.
  </Card>
</CardGroup>
