{"id":18319851,"url":"https://github.com/bitwalker/libring","last_synced_at":"2025-04-06T13:08:35.263Z","repository":{"id":11396518,"uuid":"69540713","full_name":"bitwalker/libring","owner":"bitwalker","description":"A fast consistent hash ring implementation in Elixir","archived":false,"fork":false,"pushed_at":"2024-09-17T18:08:48.000Z","size":107,"stargazers_count":210,"open_issues_count":1,"forks_count":24,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-09-18T21:52:33.229Z","etag":null,"topics":["consistent-hashing","elixir","hash-ring","sharding"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bitwalker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-09-29T07:08:39.000Z","updated_at":"2024-09-17T18:08:52.000Z","dependencies_parsed_at":"2024-05-30T17:18:21.416Z","dependency_job_id":"d5e76281-8329-4ce5-b55d-49c95f3fb71c","html_url":"https://github.com/bitwalker/libring","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Flibring","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Flibring/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Flibring/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Flibring/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bitwalker","download_url":"https://codeload.github.com/bitwalker/libring/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247485283,"owners_count":20946398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["consistent-hashing","elixir","hash-ring","sharding"],"created_at":"2024-11-05T18:14:29.784Z","updated_at":"2025-04-06T13:08:35.240Z","avatar_url":"https://github.com/bitwalker.png","language":"Elixir","readme":"# libring - A fast consistent hash ring for Elixir\n\n[![Module Version](https://img.shields.io/hexpm/v/libring.svg)](https://hex.pm/packages/libring)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/libring/)\n[![Total Download](https://img.shields.io/hexpm/dt/libring.svg)](https://hex.pm/packages/libring)\n[![License](https://img.shields.io/hexpm/l/libring.svg)](https://github.com/bitwalker/libring/blob/master/LICENSE)\n[![Last Updated](https://img.shields.io/github/last-commit/bitwalker/libring.svg)](https://github.com/bitwalker/libring/commits/master)\n\nThis library implements a stateful consistent hash ring. It's extremely fast\n(in benchmarks it's faster than all other implementations I've tested against,\nnamely [voicelayer/hash-ring](https://github.com/voicelayer/hash-ring) and\n[sile/hash_ring](https://github.com/sile/hash_ring)), it has no external dependencies,\nand is written in Elixir.\n\nThe algorithm is based on [libketama](https://github.com/rj/ketama). Nodes on the\nring are broken into shards and each one is assigned an integer value in the keyspace, which\nis the set of integers from 1 to 2^32-1. The distribution of these shards is random, but\ndeterministic.\n\nKeys are then mapped to a shard by converting the key to a binary, hashing it with SHA-256,\nconverting the hash to an integer in the keyspace, then finding the shard which is assigned\nthe next highest value, if there is no next highest value, the lowest integer is used, which\nis how the \"ring\" is formed.\n\nThis implementation uses a general balanced tree, via Erlang's `:gb_tree` module. Each shard\nis inserted into the tree, and we use this data structure to efficiently lookup next-highest\nkey and smallest key. I suspect this is why `libring` is faster than other implementations I've\nbenchmarked against.\n\n## Usage\n\nAdd `:libring` to your deps, and run `mix deps.get`.\n\n```elixir\ndef deps do\n  [\n    {:libring, \"~\u003e 1.0\"}\n  ]\nend\n```\n\nYou have two choices for managing hash rings in your application:\n\n### `HashRing`\n\nThis API works with the raw ring data structure. It is the fastest implementation,\nand is best suited for when you have a single process which will need to access the\nring, and which can hold the ring in its internal state.\n\n```elixir\nring = HashRing.new()\n       |\u003e HashRing.add_node(\"a\")\n       |\u003e HashRing.add_node(\"b\")\n\n\"a\" = HashRing.key_to_node(ring, {:myworker, 123})\n```\n\nYou can also specify the weight of each node, and add nodes in bulk:\n\n```elixir\nring = HashRing.new()\n       |\u003e HashRing.add_nodes([\"a\", {\"b\", 64}])\n       |\u003e HashRing.add_node(\"c\", 200)\n\"c\" = HashRing.key_to_node(ring, {:myworker, 123})\n```\n\n**NOTE**: Node names do not have to be strings, they can be atoms, tuples, etc.\n\n### `HashRing.Managed`\n\nThis API works with rings which are held in the internal state of a GenServer process.\nIt supports the same API as `HashRing`. Because of this, there is a performance overhead\ndue to the messaging, and the GenServer can be a potential bottleneck. If this is the case\nyou are better off exploring ways to use the raw `HashRing` API. However this API is best suited\nfor situations where you have multiple processes accessing the ring, or need to maintain multiple\nrings.\n\n**NOTE**: You must have the `:libring` application started to use this API.\n\n```elixir\n{:ok, pid} = HashRing.Managed.new(:myring)\n:ok = HashRing.Managed.add_node(:myring, \"a\")\n:ok = HashRing.Managed.add_node(:myring, \"b\", 64)\n:ok = HashRing.Managed.add_node(:myring, \"c\", 200)\n\"c\" = HashRing.Managed.key_to_node(:myring, {:myworker, 123})\n```\n\nYou can configure managed rings in `config.exs`, and they will be created and initialized\nwhen the `:libring` application starts. Configured rings take two shapes, static and dynamic\nrings. Static rings are simply those where the nodes are provided up front, although you can\nalways add/remove nodes manually at runtime; dynamic rings have Erlang node monitoring enabled,\nand add or remove nodes on the ring based on cluster membership.\n\nYou can whitelist/blacklist nodes when using dynamic rings, so that only those nodes which you\nactually want to distribute work to are used in calculations. This configuration is shown below as well.\n\nIf you provide a whitelist, the blacklist will have no effect, and only nodes matching the whitelist\nwill be added. If you do not provide a whitelist, the blacklist will be used to filter nodes. If you\ndo not provide either, a default blacklist containing the `~r/^remsh.*$/` pattern from the example below,\nwhich is a good default to prevent remote shell sessions (at least those done via releases) from causing\nthe ring to change.\n\nThe whitelist and blacklist only have an effect when `monitor_nodes: true`.\n\n## Configuration\n\nBelow is an example configuration:\n\n```elixir\nconfig :libring,\n  rings: [\n    # A ring which automatically changes based on Erlang cluster membership,\n    # but does not allow nodes named \"a\" or \"remsh*\" to be added to the ring\n    ring_a: [monitor_nodes: true,\n             node_type: :visible,\n             node_blacklist: [\"a\", ~r/^remsh.*$/]],\n    # A ring which is composed of three nodes, of which \"c\" has a non-default weight of 200\n    # The default weight is 128\n    ring_b: [nodes: [\"a\", \"b\", {\"c\", 200}]]\n  ]\n```\n\n## Contributing\n\nIf you have changes in mind that are significant or potentially time-consuming, please open an RFC-style PR first, where we\ncan discuss your plans first. I don't want you to spend all your time crafting a PR that I ultimately reject because I don't\nthink it's a good fit or is too large for me to review. Not that I plan to reject PRs in general, but I have to be careful to\nbalance features with maintenance burden, or I will quickly be unable to manage the project.\n\nPlease ensure that you adhere to a commit style where logically related changes are in a single commit, or broken up in a way that\neases review if necessary. Keep commit subject lines informative, but short, and provide additional detail in the extended message text\nif needed. If you can, mention relevant issue numbers in either the subject or the extended message.\n\n## Copyright and License\n\nCopyright (c) 2016 Paul Schoenfelder\n\nThis library is MIT licensed. See the\n[LICENSE](https://github.com/bitwalker/libring/blob/master/LICENSE) for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitwalker%2Flibring","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbitwalker%2Flibring","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitwalker%2Flibring/lists"}