Library / SDK
elixir-horde/horde avatar
elixir-horde/horde

Horde for Distributed Supervision in Elixir and Postgres

Horde is a distributed Supervisor and Registry backed by Postgres

1,506 stars122 forksElixirMIT

At a glance

What is it?
Horde provides distributed supervisor and registry behavior across an Elixir cluster, using CRDT-based synchronization backed by Postgres through DeltaCrdt. Its familiar APIs reduce migration work, while eventual consistency and documented API gaps require careful evaluation.
Who is it for?
Horde is appropriate for Elixir applications that need supervision and registration across a dynamic cluster and can design around eventual consistency. It is a poor fit for systems requiring immediate consistency, strict singleton guarantees, or every feature of Elixir's local APIs.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 18 days ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Horde's distributed Elixir control-plane role

Horde addresses a specific gap between Elixir's standard supervisors and an application spread across several servers. Elixir supervisors and registries manage processes within a local BEAM runtime. Horde provides distributed counterparts named `Horde.DynamicSupervisor` and `Horde.Registry`, allowing cluster members to participate in deciding which processes should run and where registry keys are associated.

The target audience is Elixir developers operating a multi-node application rather than a single isolated node. Horde is most relevant when process placement, failure recovery, and registry membership need to continue after the loss of individual nodes. It is not intended to turn every supervisor into a cluster-wide service. The README explicitly directs developers who only need one singleton process toward `Highlander` or `HighlanderPG` instead.

Horde is built on DeltaCrdt and backed by Postgres, according to the repository description. The README does not document the required database configuration, so this review cannot name connection keys or migration steps without guessing. The full documentation at HexDocs is the place to determine how a particular application supplies that dependency. This omission matters during adoption because the API examples do not explain which Postgres services, credentials, schemas, or network settings are expected.

The project targets distributed coordination rather than general-purpose cluster management. The README does not claim that Horde configures Postgres, deploys nodes, or provisions the BEAM cluster. Those responsibilities remain outside the documented scope shown in the repository.

CRDT synchronization and hash-ring placement

Horde's central architectural trade-off is eventual consistency. Nodes synchronize their state through CRDT-based behavior, but changes are not immediately guaranteed to appear everywhere. Cluster membership is dynamic: nodes may join or leave, and the README says Horde continues operating as the reachable membership changes.

`Horde.DynamicSupervisor` uses a hash ring when distributing child processes. The ring places processes across currently reachable supervisors and is also used to limit race conditions during membership changes. If a node fails or becomes unreachable, the README states that its processes are redistributed among remaining nodes. Calling `Horde.DynamicSupervisor.stop/3` follows the same lifecycle model by stopping the local supervisor, shutting down its local children, and redistributing those processes to remaining supervisors.

Network partitions are controlled through `:distribution_strategy`. `Horde.UniformDistribution` is the default and distributes processes by hash among reachable nodes, allowing both sides of a partition to continue operating. `Horde.UniformQuorumDistribution` also distributes by hash, but shuts down when less than half of the cluster is reachable. This option favors continued operation only while the stated quorum is reachable. It does not turn Horde into a strongly consistent system.

The README says synchronization with neighboring nodes is aggressive and that this behavior is tunable. It does not provide the option name for synchronization frequency in the displayed usage guidance. Configuration details must therefore come from the linked Horde documentation. Any tuning based on an invented key could prevent the application from starting or leave synchronization at an unintended setting.

The hash ring addresses placement and membership-change races, but it cannot remove the CAP trade-off. Two partition sides can each believe they own a placement before their state has synchronized. Callers must account for that possibility when designing restart and reconciliation behavior.

Horde.Registry conflicts and distributed process identity

Horde.Registry follows the API and behavior of Elixir's standard registry closely, but the README documents meaningful divergences. One explicit omission is support for keys set to `:duplicate`. Horde.Registry therefore does not satisfy applications that depend on the standard registry's duplicate-key feature.

Horde is built around the availability and partition-tolerance side of the CAP trade-off described in the README. During a network partition it cannot guarantee consistency. Duplicate supervised processes may therefore exist temporarily. The README says `Horde.Registry` detects and terminates conflicting duplicates with a special exit code. This makes the condition observable, but the application still needs to handle that exit and decide whether restarting the affected process is safe.

Since Horde 0.6.0, `Horde.DynamicSupervisor` ignores the `id` in a child specification, as `Elixir.DynamicSupervisor` does. A child `id` consequently is not guaranteed to be unique across the cluster. The README specifically recommends `Horde.Registry` when cluster-wide process naming is required. This is a functional boundary, not merely a style difference.

The README explains why the project avoids checking uniqueness in both components. Earlier versions allowed `Horde.DynamicSupervisor` and `Horde.Registry` to select different surviving processes during a race. The supervisor could choose process A while the registry chose process B, leading to the termination of both. The current division assigns placement and lifecycle management to `Horde.DynamicSupervisor`, with `Horde.Registry` serving cluster-wide uniqueness checks.

Applications migrating from older Horde releases must inspect code that assumes child IDs form a cluster-wide namespace. The documented behavior changed at 0.6.0, so code that depends on stable IDs cannot be carried forward without review.

Installing Horde and starting the first supervisor

The README identifies Hex as the installation source and gives the dependency entry to add to `mix.exs`. It specifies `~> 0.8.5`, although the release list includes `v0.10.0`. A team should resolve that version mismatch against its own compatibility requirements before locking the dependency. The README does not show a dependency-fetch command, so no undocumented Mix command is added here.

Add the dependency entry as documented:

elixir
def deps do
  [
    {:horde, "~> 0.8.5"}
  ]
end

The first documented runtime step is to add `Horde.DynamicSupervisor` to an application's supervision tree. This code names the distributed supervisor and selects `:one_for_one` as its strategy:

elixir
defmodule MyApp.Application do
  use Application
  def start(_type, _args) do
    children = [
      {Horde.DynamicSupervisor, [name: MyApp.DistributedSupervisor, strategy: :one_for_one]}
    ]
    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

Once the application starts, use the dynamic supervisor API to add a child. The README gives this `Task` example:

elixir
Horde.DynamicSupervisor.start_child(MyApp.DistributedSupervisor, %{id: :task, start: {Task, :start_link, [:infinity]}})

A successful call should return the result required by `Horde.DynamicSupervisor.start_child/2` and add the task to the distributed supervisor. The repository contains `examples/hello_world`, which the README identifies as an additional usage reference. It does not explain the complete Postgres configuration for that example, so the example directory is not presented here as a full deployment guide.

API compatibility and Horde's operational boundaries

Horde's main convenience is its similarity to Elixir's local APIs. The README says both public modules are designed to stay close to their standard-library counterparts and, for many scenarios, can replace them with limited changes. `Horde.DynamicSupervisor.start_child/2` preserves the shape of `Elixir.DynamicSupervisor.start_child/2`, which can reduce migration work for code that starts children and handles the returned result.

That compatibility has clear boundaries. Distributed membership changes the failure model, and Horde does not preserve every local guarantee or option. The lack of `Horde.Registry` support for keys set to `:duplicate` is one explicit gap. The ignored child `id` is another. Eventual consistency can also produce temporary duplicates, whereas a local supervisor coordinates processes without CRDT synchronization across nodes.

For one cluster-wide process, Horde is the wrong abstraction according to the README. `Highlander` and `HighlanderPG` are identified as potentially better fits. The README does not explain their configuration or compare their consistency behavior, so the defensible distinction remains narrow: Horde implements distributed supervision and registration, while those projects target the single-global-process problem.

Deployment introduces concerns not covered by the basic examples. Since Horde uses DeltaCrdt and Postgres, an adopter needs the full project documentation to confirm database connectivity, node membership, networking, synchronization settings, and recovery behavior. None of these operational details can be inferred from the application supervision snippet.

Applications that require one and only one instance at all times should examine the duplicate-process consequence before adopting Horde.DynamicSupervisor. Applications that tolerate temporary overlap but need a way to detect conflicting registrations can consider Horde.Registry, provided they can handle the documented special exit condition.

Horde releases, upgrades, and MIT licensing

The repository is not archived, and the last push was on 2026-10-05. The latest listed release is `v0.10.0`, published on 2025-10-31. The README also announces work toward a 1.0 release and asks users to report issues. Because the project has not reached the announced 1.0 release, the API documentation matching the selected version remains important.

Upgrade cost cannot be quantified from the README because it does not provide a complete migration guide or a release-by-release upgrade procedure. It does document one behavior change that can require application changes. Since version 0.6.0, `Horde.DynamicSupervisor` ignores child IDs and no longer guarantees cluster-wide ID uniqueness. Code written against earlier behavior may need to move uniqueness responsibilities to `Horde.Registry`.

The `mix.exs` example requests `~> 0.8.5`, while the release list contains `v0.10.0`. That discrepancy should be resolved before copying the example into a new project. An adopter inspecting another version must consult that version's API documentation and `CHANGELOG.md` rather than assuming the sample constraint tracks the newest release.

Horde uses the MIT License. That is the license declared for the repository, but operational cost also includes running and monitoring Postgres, coordinating BEAM nodes, and testing partition scenarios. The README does not provide capacity estimates or a supported deployment topology for those tasks, so it does not support a claim about their resource cost.

Editorial conclusion

Horde is appropriate for Elixir applications that need supervision and registration across a dynamic cluster and can design around eventual consistency. It is a poor fit for systems requiring immediate consistency, strict singleton guarantees, or every feature of Elixir's local APIs. Evaluate the full Horde documentation first to confirm the Postgres setup, node behavior, and acceptable duplicate-process handling.

Frequently asked questions

What is Horde used for in an Elixir application?

Horde provides a distributed supervisor and distributed registry for applications running across multiple nodes. It redistributes supervised processes when a node fails and synchronizes cluster state through CRDT-based behavior.

Does Horde guarantee that supervised processes remain unique during a network partition?

No. Horde is eventually consistent and can allow duplicate processes during a partition. The README states that Horde.Registry detects conflicting duplicates and terminates them with a special exit code.

How does Horde behave when the cluster is partitioned?

The default `Horde.UniformDistribution` allows both sides of a partition to continue by distributing processes among reachable nodes. `Horde.UniformQuorumDistribution` shuts down when less than half of the cluster is reachable.

Official sources

  1. elixir-horde/horde on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/elixir-horde-horde.svg)](https://hysenlabs.com/projects/elixir-horde-horde)

Was this useful?