npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

consul-resolver

v1.3.0

Published

A load balancer for Consul services with Redis-based metrics

Downloads

11

Readme

consul-resolver

A simple consul library for load balancing and metrics tracking with redis. This package provides multiple load balancing algorithms, such as Round Robin, Least Connection, and Weighted Round Robin.

Features

  • Multiple load balancing algorithms:
    • Round Robin
    • Least Connection
    • Weighted Round Robin
  • Consul Integration
  • Configurable caching with Redis
  • Debug logging support

Installation

yarn add consul-resolver
npm install consul-resolver

Prerequisites

  • Node.js >= 14
  • Redis server (optional, if caching is enabled)
  • Consul server

Quick Start

import { ConsulResolver, SelectionAlgorithm } from "consul-resolver";
import Redis from "ioredis";
import https from "https";

const redis = new Redis({
  host: "localhost",
  port: 6379,
});

const resolver = new ConsulResolver({
  redis,
  host: "127.0.0.1",
  port: 8500,
  secure: false,
  cachePrefix: "mydb",
  token: process.env.CONSUL_TOKEN,
  agent: new https.Agent({
    rejectUnauthorized: false,
    minVersion: "TLSv1.2",
    maxVersion: "TLSv1.3",
  }),
  cacheEnabled: true, // Enable Redis caching
  cacheTTL: 60000, // Cache TTL in milliseconds (default: 60s)
  debug: false // Enable debug logging
});

const service = await resolver.selectOptimalService(
  "grafana",
  SelectionAlgorithm.LeastConnection,
);

await resolver.incrementConnections(service.selected.id);

await resolver.decrementConnections(service.selected.id);

Configuration

The ConsulResolver constructor accepts the following configuration options:

interface ConsulResolverConfig {
  cachePrefix: string; // Prefix for Redis cache keys
  redis?: Redis; // Redis instance (required if cacheEnabled is true)
  host: string; // Consul host
  port: number; // Consul port
  secure: boolean; // Use HTTPS
  token?: string; // Consul ACL token
  agent?: Agent; // Optional HTTPS agent configuration
  cacheEnabled?: boolean; // Enable/disable Redis caching (default: false)
  cacheTTL?: number; // Cache TTL in milliseconds (default: 60000)
  debug?: boolean; // Enable debug logging (default: false)
  weights?: {
    health: number;
    responseTime: number;
    errorRate: number;
    resources: number;
    connections: number;
    distribution: number;
  }; // Custom weights for the weighted round robin algorithm
  metrics?: {
    responseTime: number;
    errorRate: number;
    cpuUsage: number;
    memoryUsage: number;
    activeConnections: number;
  }; // Custom metrics for the weighted round robin algorithm
}

API Reference

selectOptimalService(service: string, algorithm?: SelectionAlgorithm): Promise<OptimalServiceResult | null>

Selects the optimal service instance based on the specified algorithm.

getSelectionMetrics(serviceId: string): Promise<ServiceMetrics | null>

Retrieves current metrics for a specific service. Returns null if caching is disabled.

incrementConnections(serviceId: string): Promise<void>

Increments the active connection count for a service.

decrementConnections(serviceId: string): Promise<void>

Decrements the active connection count for a service.

refresh(): Promise<void>

Clears all stored metrics from Redis. No-op if caching is disabled.

Types

OptimalServiceResult

interface OptimalServiceResult {
  selected: {
    ip: string;
    port: number;
  } | null;
  services: Array<{
    ip: string;
    port: number;
  }>;
}

ServiceMetrics

interface ServiceMetrics {
  responseTime: number;
  errorRate: number;
  cpuUsage: number;
  memoryUsage: number;
  activeConnections: number;
  lastSelectedTime?: number;
}

Service Selection Weights and Metrics

Selection Weights

The Weighted Round Robin algorithm uses the following weight distribution to calculate service scores:

const DEFAULT_WEIGHTS = {
  health: 0.25, // Service health status (25%)
  responseTime: 0.2, // Response time performance (20%)
  errorRate: 0.2, // Error rate of the service (20%)
  resources: 0.15, // CPU and memory usage (15%)
  connections: 0.1, // Active connection count (10%)
  distribution: 0.1, // Time since last selection (10%)
};

Weight Explanations

  • health (25%): Prioritizes services with passing health checks

    • Calculated as ratio of passing checks to total checks
    • Most heavily weighted as service health is critical
  • responseTime (20%): Favors services with lower response times

    • Normalized against a 500ms baseline
    • Higher weight indicates better performance
  • errorRate (20%): Considers service reliability

    • Normalized against a 100% scale
    • Lower error rates result in higher scores
  • resources (15%): Accounts for service load

    • Combines CPU and memory utilization
    • Prevents overloading of busy instances
  • connections (10%): Active connection count

    • Helps distribute load across instances
    • Prevents any single instance from being overwhelmed
  • distribution (10%): Time since last selection

    • Ensures fair rotation among services
    • Prevents "hot spot" instances

Default Metrics

Each service starts with these default metrics if no historical data is available:

const DEFAULT_METRICS = {
  responseTime: 100, // Default 100ms response time
  errorRate: 0, // Start with 0% error rate
  cpuUsage: 50, // Assume 50% CPU usage
  memoryUsage: 50, // Assume 50% memory usage
  activeConnections: 0, // Start with no active connections
};

Metrics Explanation

  • responseTime: Initial 100ms baseline

    • Conservative default for new services
    • Updated based on actual performance
  • errorRate: Starts at 0%

    • Optimistic initial error rate
    • Adjusted based on actual failures
  • cpuUsage: Default 50%

    • Moderate initial CPU load assumption
    • Updated with actual metrics when available
  • memoryUsage: Default 50%

    • Moderate initial memory usage assumption
    • Updated with actual metrics when available
  • activeConnections: Starts at 0

    • Fresh services begin with no connections
    • Incremented/decremented as connections are established/closed

Usage with Express (Best used as a middleware)

import express from "express";
import { ConsulResolver, SelectionAlgorithm } from "consul-resolver";

const app = express();
const resolver = new ConsulResolver({
  ...config,
  cacheEnabled: true, // Enable caching
  debug: true // Enable debug logging
});

app.use(async (req, res, next) => {
  const service = await resolver.selectOptimalService("api-service");
  if (!service) {
    return res.status(503).json({ error: "No service available" });
  }

  await resolver.incrementConnections(service.selected.id);

  req.serviceInfo = service.selected;

  res.on("close", async () => {
    await resolver.decrementConnections(service.selected.id);
  });

  next();
});

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Author

Muritala David Ilerioluwa