Skip to content

Feature: Active/Standby High Availability #796

Description

@patsevanton

Labels: enhancement, core/general

Problem

NORA currently runs as a single instance (documented in ARCHITECTURE.md). This means:

  • Planned downtime during upgrades. Even with Kubernetes Recreate strategy, there is a window where the registry is unavailable. While client-side caches mitigate this for pulls, CI/CD pipelines actively publishing artifacts during a deployment window will fail.
  • No fault tolerance. A hardware failure, OOM kill, or crash of the single instance causes a complete registry outage until the process restarts or a new pod is scheduled. For organizations using NORA as a critical path in their build infrastructure, this is a single point of failure.
  • S3 storage already decouples state. NORA's architecture stores all durable state on S3-compatible object storage, and in-memory indexes are rebuilt on startup. This means the groundwork for running multiple instances against shared storage is largely already in place — the codebase is already defensively coded for multi-replica scenarios (per-version keys for npm packument, sparse Cargo index regeneration from storage, etc.).

Proposed Solution

Introduce active/standby support — a lightweight high-availability mode where one instance serves traffic (active) and one or more instances stand by, ready to take over if the active fails.

Core Design

  1. Shared-nothing, shared-storage model. Both active and standby instances connect to the same S3-compatible backend. No state replication protocol is needed — S3 is the source of truth.

  2. Leader election via distributed lock. Use S3 conditional writes (If-None-Match / PutIfAbsent) or a lightweight coordination mechanism (e.g., a lease object in S3 with TTL-based heartbeat) to elect the active instance. The standby instance continuously attempts to acquire the lock; if the active fails to renew its lease, the standby promotes itself.

  3. Config-driven. A new top-level configuration section:

    [ha]
    enabled = true
    mode = "active-standby"            # future: "active-active"
    lease_ttl_secs = 15                # how long a lease is valid
    heartbeat_interval_secs = 5        # how often the active renews
    fence_token = true                 # use monotonic fencing tokens
  4. Fencing tokens. The elected active holds a monotonic token. Any write operations to S3 include this token so that a stale (former) active cannot corrupt data after a split-brain scenario. This is especially important for operations that do read-modify-write on storage (e.g., npm packument merging).

  5. Readiness probe integration. The standby instance exposes /healthz as 503 Standby and /readyz as 503 while in standby mode. Kubernetes readiness probes ensure only the active receives traffic. Upon promotion, the new active transitions its probes to 200.

  6. Fast promotion. Since indexes are rebuilt from S3 on startup, a standby instance can periodically warm its in-memory indexes (read-only polling of storage) so that promotion is near-instant rather than requiring a full reindex.

Behavioral Guarantees

Scenario Behavior
Active crashes Standby acquires lease within lease_ttl_secs, starts serving traffic
Network partition (active isolated) Active's lease expires; standby promotes. Former active sees lease loss and stops serving writes (fencing)
Planned rollout New standby starts, acquires lease only after old active releases it on shutdown (SIGTERM handler releases lease)
Both instances healthy, no failover Standby serves 503 on readiness; zero cost to active
S3 unavailable Both instances degrade; active returns 502, standby cannot promote

Implementation Outline

  1. ha/ modulelease.rs (S3-based lease acquisition/renewal), fence.rs (fencing token management), promotion.rs (index warming and readiness flip).
  2. Startup change — Before binding the HTTP listener, attempt to acquire the lease. If acquired, proceed as active; otherwise, enter standby mode (serve admin/metrics only, poll for lease).
  3. Graceful shutdown — On SIGTERM, release the S3 lease object so the standby can promote immediately rather than waiting for TTL expiry.
  4. Metricsnora_ha_role gauge (0=standby, 1=active), nora_ha_lease_renewal_total, nora_ha_lease_acquisition_duration_seconds, nora_ha_promotion_total.
  5. Config validation — Warn if ha.enabled = true but storage backend is local filesystem (HA requires shared storage).

Alternatives Considered

  1. Active/Active (load-balanced multi-replica). More complex — requires distributed locking on every write, not just leader election. Could be a future mode but adds latency to every publish operation. Active/standby is simpler and sufficient for most HA needs.

  2. External coordination (etcd, Consul, ZooKeeper). Adds operational burden and external dependencies, contradicting NORA's "single binary, no external services" philosophy. S3-based leasing keeps the dependency surface unchanged.

  3. Kubernetes-native HA (PodDisruptionBudget + Recreate). Already the documented approach. It reduces but does not eliminate downtime windows and provides no fault tolerance for unplanned failures.

  4. Hot standby with WAL-based replication. Overly complex for an artifact registry where S3 already provides durable, consistent storage. Not justified by the read-heavy, write-light workload.

Related Registry

  • Core/General

Additional Context

  • The codebase is already partially prepared for this: per-registry defensive coding for multi-replica safety exists in proxy_coalesce.rs, registry/npm.rs, registry/cargo_registry.rs, and config/auth.rs.
  • The backup.rs module provides disaster recovery but not live failover — active/standby complements it.
  • S3 conditional write support (PutIfAbsent) is available in the object_store crate already used by NORA.
  • This feature is critical for enterprise adoption where SLA requirements mandate <30s recovery time.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions