A consistent hashring compatible with ketama hashing and python's hash_ring
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
HashRing provides consistent hashing that is compatible with the original libketama library and Python's hash_ring module. It enables distributed systems to consistently map keys to servers in a way that minimizes redistribution when servers are added or removed from the ring.
npm install hashringconst HashRing = require('hashring');For ES6 modules (with transpilation tools like Babel):
import HashRing from 'hashring';Note: HashRing uses CommonJS exports. ES6 import syntax requires transpilation or bundling tools.
const HashRing = require('hashring');
// Create a hash ring with multiple servers
const ring = new HashRing([
'127.0.0.1:11211',
'127.0.0.2:11211',
'127.0.0.3:11211'
]);
// Find the server for a specific key
const server = ring.get('mykey');
console.log(server); // Returns server address like '127.0.0.1:11211'
// Get multiple servers for replication
const servers = ring.range('mykey', 2);
console.log(servers); // Returns array of server addresses
// Add or remove servers dynamically
ring.add('127.0.0.4:11211').remove('127.0.0.1:11211');HashRing implements consistent hashing using the ketama algorithm with the following key components:
Create a new HashRing instance with servers and optional configuration.
/**
* HashRing constructor for consistent hashing
* @param {string|Array|Object} servers - Server(s) to add to the ring
* @param {string|Function} algorithm - Hash algorithm ('md5' default) or custom hasher
* @param {Object} options - Configuration options
*/
function HashRing(servers, algorithm, options);Server Formats:
// Single server string
new HashRing('127.0.0.1:11211');
// Array of servers
new HashRing(['127.0.0.1:11211', '127.0.0.2:11211']);
// Object with weights
new HashRing({
'127.0.0.1:11211': 200,
'127.0.0.2:11211': { weight: 300 },
'127.0.0.3:11211': { vnodes: 50 }
});Configuration Options:
interface HashRingOptions {
/** Number of virtual nodes per server (default: 40) */
'vnode count'?: number;
/** Compatibility mode: 'hash_ring' for Python compatibility */
compatibility?: string;
/** Number of replicas per vnode (default: 4, or 3 for hash_ring) */
replicas?: number;
/** LRU cache size for key lookups (default: 5000) */
'max cache size'?: number;
/** Default port to exclude from hashing for ketama compatibility */
'default port'?: number;
}Find the correct server for a given key using consistent hashing.
/**
* Find the server for a key using consistent hashing
* @param {string} key - Key to find server for
* @returns {string|undefined} Server address or undefined if no servers
*/
get(key);Usage:
const server = ring.get('user:123');
// Returns: '127.0.0.1:11211'Get multiple servers for replication or load distribution.
/**
* Get multiple servers for a key, useful for replication
* @param {string} key - Key to find servers for
* @param {number} size - Number of servers to return (default: all servers)
* @param {boolean} unique - Return only unique servers (default: true)
* @returns {Array<string>} Array of server addresses
*/
range(key, size, unique);Usage:
// Get 3 servers for replication
const servers = ring.range('user:123', 3);
// Returns: ['127.0.0.1:11211', '127.0.0.2:11211', '127.0.0.3:11211']
// Get all available servers
const allServers = ring.range('user:123');Add new servers to the ring without full reinitialization.
/**
* Add servers to the ring
* @param {string|Array|Object} servers - Server(s) to add (same format as constructor)
* @returns {HashRing} Returns this for method chaining
*/
add(servers);Remove servers from the ring.
/**
* Remove a server from the ring
* @param {string} server - Server to remove
* @returns {HashRing} Returns this for method chaining
*/
remove(server);Replace one server with another without redistributing other servers.
/**
* Hot-swap one server with another
* @param {string} from - Server to replace
* @param {string} to - Replacement server
* @returns {HashRing} Returns this for method chaining
*/
swap(from, to);Usage:
// Add servers
ring.add(['127.0.0.4:11211', '127.0.0.5:11211']);
// Remove a server
ring.remove('127.0.0.1:11211');
// Hot-swap servers (preserves distribution for other servers)
ring.swap('127.0.0.2:11211', '127.0.0.6:11211');
// Method chaining
ring.add('127.0.0.7:11211').remove('127.0.0.1:11211');Check if a server exists in the ring.
/**
* Check if a server exists in the ring
* @param {string} server - Server to check for
* @returns {boolean} True if server exists in ring
*/
has(server);Get hash ring points for servers.
/**
* Get hash ring points per server
* @param {Array<string>} [servers] - Optional servers to filter (default: all servers)
* @returns {Object} Mapping of server to array of hash points
*/
points(servers);Find position of a hash value in the ring.
/**
* Find position of hash value in ring (advanced usage)
* @param {number} hashValue - Hash value to find position for
* @returns {number} Position index in the ring
*/
find(hashValue);Usage:
// Check server existence
const exists = ring.has('127.0.0.1:11211');
// Get hash points for all servers
const allPoints = ring.points();
// Returns: { '127.0.0.1:11211': [123456, 789012, ...], ... }
// Get points for specific servers
const specificPoints = ring.points(['127.0.0.1:11211']);
// Advanced: find hash position (rarely needed)
// Note: hashValue is computed internally, find() is mainly for debugging
const position = ring.find(12345); // Use a computed hash valueReset the ring and clear cache.
/**
* Reset the ring and clear cache
* @returns {HashRing} Returns this for method chaining
*/
reset();End the ring and clean up all references.
/**
* End the ring and clean up all references
* @returns {HashRing} Returns this for method chaining
*/
end();Usage:
// Clear ring but keep server configuration
ring.reset();
// Complete cleanup
ring.end();Rebuild the hash ring continuum (advanced usage).
/**
* Regenerate the hash ring continuum based on current servers
* @returns {HashRing} Returns this for method chaining
*/
continuum();Usage:
// Manually rebuild the ring (usually called automatically)
ring.continuum();Get the computed hash value for a key (advanced usage).
/**
* Get the hash value for a key (used internally for consistent hashing)
* @param {string} key - Key to compute hash for
* @returns {number} Computed hash value
*/
hashValue(key);Usage:
// Get hash value for debugging or advanced usage
const hashVal = ring.hashValue('mykey');
console.log(hashVal); // Returns numeric hash value like 3446378249
// Often used with find() for advanced ring inspection
const position = ring.find(ring.hashValue('mykey'));Access package version information.
/**
* Package version string
* @type {string}
*/
HashRing.version;Usage:
console.log(HashRing.version); // '3.2.0'/** HashRing instance properties (read-only) */
interface HashRingInstance {
/** Array of parsed server objects */
servers: Array<{
string: string;
host: string;
port: number;
weight: number;
}>;
/** Array of hash ring nodes */
ring: Array<{
value: number;
server: string;
}>;
/** Number of nodes in the ring */
size: number;
/** Server to vnode count mapping */
vnodes: { [server: string]: number };
/** Hash algorithm being used */
algorithm: string | Function;
/** Default number of virtual nodes per server */
vnode: number;
/** Number of replicas per hash */
replicas: number;
/** LRU cache instance */
cache: Object;
}HashRing handles common error scenarios gracefully:
get() returns undefined when no servers are configuredadd() ignores servers that already exist in the ring// Use a different crypto algorithm
const ring = new HashRing(servers, 'sha1');
// Use a custom hash function
const ring = new HashRing(servers, function customHash(key) {
// Your custom hashing logic
return crypto.createHash('sha256').update(key).digest();
});// Default ketama mode (4 replicas per vnode)
const ketamaRing = new HashRing(servers);
// Python hash_ring compatibility (3 replicas per vnode)
const pythonRing = new HashRing(servers, 'md5', {
compatibility: 'hash_ring'
});// Larger cache for high-traffic scenarios
const ring = new HashRing(servers, 'md5', {
'max cache size': 50000
});
// Custom vnode distribution for uneven server capacity
const ring = new HashRing({
'small-server:11211': { vnodes: 20 },
'medium-server:11211': { vnodes: 40 },
'large-server:11211': { vnodes: 80 }
});