📘 Database System Design

Consistent Hashing — Database Scaling

Consistent Hashing is a technique used to distribute keys across multiple nodes while minimizing data movement when nodes are added or removed.

S

Site Administrator

📅 Sep 26, 2026 🕒 7 min read 👁 27 views
☆ Save 𝕏 in f ✆
Consistent Hashing — Database Scaling

Consistent Hashing is a technique used to distribute keys across multiple nodes while minimizing data movement when nodes are added or removed.

It is especially useful in distributed systems such as sharding, distributed caches, and database routing.

Why Do We Need It?

Suppose we have 3 database shards and use simple modulo hashing:

Shard = Hash(CustomerId) % 3

If we add a fourth shard:

Shard = Hash(CustomerId) % 4

A large number of customers may map to different shards. This means a significant amount of data may need to move.

flowchart LR
    A[CustomerId] --> B[Hash % 3]
    B --> C[Shard 1]
    B --> D[Shard 2]
    B --> E[Shard 3]

    F[Add Shard 4] --> G[Hash % 4]
    G --> H[Many Keys Remapped]

Consistent hashing reduces this movement.

Consistent Hashing Ring

Instead of directly mapping a key to a server using modulo, both keys and nodes are placed on a logical hash ring.

flowchart LR
    A[Hash Ring]

    A --> B[Shard 1]
    A --> C[Shard 2]
    A --> D[Shard 3]

    E[Customer Key] --> F[Hash]
    F --> G[Nearest Shard on Ring]

The important idea is:

Hash(CustomerId)
      ↓
Position on Ring
      ↓
Move clockwise
      ↓
First Shard Found
      ↓
Store / Read Data

Adding a New Shard

Suppose we initially have:

Shard 1
Shard 2
Shard 3

We add:

Shard 4

Only keys belonging to the affected ring range generally need to move.

flowchart LR
    A[Before] --> B[Shard 1]
    A --> C[Shard 2]
    A --> D[Shard 3]

    E[Add Shard 4] --> F[Only Affected Range]
    F --> G[Shard 4]

Simple C# Implementation

Here is a simplified implementation to understand the routing idea.

using System.Security.Cryptography;
using System.Text;

public class ConsistentHashRing
{
    private readonly SortedDictionary<uint, string> _ring = new();

    public void AddNode(string node)
    {
        var hash = Hash(node);
        _ring[hash] = node;
    }

    public void RemoveNode(string node)
    {
        var hash = Hash(node);
        _ring.Remove(hash);
    }

    public string GetNode(string key)
    {
        if (_ring.Count == 0)
            throw new InvalidOperationException("No nodes available.");

        var hash = Hash(key);

        foreach (var node in _ring)
        {
            if (node.Key >= hash)
                return node.Value;
        }

        return _ring.First().Value;
    }

    private static uint Hash(string value)
    {
        using var sha256 = SHA256.Create();

        var bytes = SHA256.HashData(
            Encoding.UTF8.GetBytes(value));

        return BitConverter.ToUInt32(bytes, 0);
    }
}

ASP.NET Core Web API Example

Now let's connect consistent hashing with database routing.

sequenceDiagram
    participant Client
    participant API as ASP.NET Core Web API
    participant Ring as Consistent Hash Ring
    participant DB as SQL Server Shard

    Client->>API: GET /api/customers/1500000/orders
    API->>Ring: GetNode(CustomerId)
    Ring-->>API: Shard-2
    API->>DB: Query Orders
    DB-->>API: Orders
    API-->>Client: JSON Response

Shard Router

public interface IShardRouter
{
    string GetConnectionString(long customerId);
}

public class ConsistentHashShardRouter : IShardRouter
{
    private readonly ConsistentHashRing _ring = new();
    private readonly IConfiguration _configuration;

    public ConsistentHashShardRouter(
        IConfiguration configuration)
    {
        _configuration = configuration;

        _ring.AddNode("Shard1");
        _ring.AddNode("Shard2");
        _ring.AddNode("Shard3");
    }

    public string GetConnectionString(long customerId)
    {
        var shard = _ring.GetNode(customerId.ToString());

        return _configuration
            .GetConnectionString(shard)!;
    }
}

Controller

[ApiController]
[Route("api/customers")]
public class OrdersController : ControllerBase
{
    private readonly IShardRouter _router;

    public OrdersController(IShardRouter router)
    {
        _router = router;
    }

    [HttpGet("{customerId:long}/orders")]
    public async Task<IActionResult> GetOrders(
        long customerId)
    {
        var connectionString =
            _router.GetConnectionString(customerId);

        await using var connection =
            new SqlConnection(connectionString);

        await connection.OpenAsync();

        const string sql = """
            SELECT OrderId,
                   CustomerId,
                   Amount,
                   OrderDate,
                   Status
            FROM Orders
            WHERE CustomerId = @CustomerId;
            """;

        await using var command =
            new SqlCommand(sql, connection);

        command.Parameters.Add(
            "@CustomerId",
            SqlDbType.BigInt).Value = customerId;

        var orders = new List<object>();

        await using var reader =
            await command.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            orders.Add(new
            {
                OrderId = reader.GetInt64(0),
                CustomerId = reader.GetInt64(1),
                Amount = reader.GetDecimal(2),
                OrderDate = reader.GetDateTime(3),
                Status = reader.GetString(4)
            });
        }

        return Ok(orders);
    }
}

SQL Server Complete Example

The database schema remains the same on every shard.

Shard 1

CREATE DATABASE OrdersShard1;
GO

USE OrdersShard1;
GO

CREATE TABLE Orders
(
    OrderId BIGINT PRIMARY KEY,
    CustomerId BIGINT NOT NULL,
    Amount DECIMAL(18,2) NOT NULL,
    OrderDate DATE NOT NULL,
    Status VARCHAR(30) NOT NULL
);
GO

INSERT INTO Orders
VALUES
(101, 1001, 1000.00, '2026-09-01', 'Completed'),
(102, 2001, 1500.00, '2026-09-02', 'Pending');
GO

Shard 2

CREATE DATABASE OrdersShard2;
GO

USE OrdersShard2;
GO

CREATE TABLE Orders
(
    OrderId BIGINT PRIMARY KEY,
    CustomerId BIGINT NOT NULL,
    Amount DECIMAL(18,2) NOT NULL,
    OrderDate DATE NOT NULL,
    Status VARCHAR(30) NOT NULL
);
GO

INSERT INTO Orders
VALUES
(201, 3001, 2500.00, '2026-09-03', 'Completed'),
(202, 4001, 3500.00, '2026-09-04', 'Pending');
GO

Shard 3

CREATE DATABASE OrdersShard3;
GO

USE OrdersShard3;
GO

CREATE TABLE Orders
(
    OrderId BIGINT PRIMARY KEY,
    CustomerId BIGINT NOT NULL,
    Amount DECIMAL(18,2) NOT NULL,
    OrderDate DATE NOT NULL,
    Status VARCHAR(30) NOT NULL
);
GO

INSERT INTO Orders
VALUES
(301, 5001, 4500.00, '2026-09-05', 'Completed'),
(302, 6001, 5500.00, '2026-09-06', 'Pending');
GO

Query a Specific Shard

Once the router determines the shard, the SQL query is normal:

SELECT
    OrderId,
    CustomerId,
    Amount,
    OrderDate,
    Status
FROM Orders
WHERE CustomerId = 5001;
GO

Expected output:

OrderId  CustomerId  Amount   OrderDate    Status
-------  ----------  -------  ----------   ----------
301      5001        4500.00  2026-09-05   Completed

appsettings.json

{
  "ConnectionStrings": {
    "Shard1": "Server=sql01;Database=OrdersShard1;...",
    "Shard2": "Server=sql02;Database=OrdersShard2;...",
    "Shard3": "Server=sql03;Database=OrdersShard3;..."
  }
}

Register the Router

builder.Services.AddSingleton<IShardRouter,
    ConsistentHashShardRouter>();

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

Virtual Nodes

In real distributed systems, a single hash position per shard may not distribute traffic evenly.

A common improvement is virtual nodes.

Shard 1 → 100 virtual nodes
Shard 2 → 100 virtual nodes
Shard 3 → 100 virtual nodes
flowchart LR
    A[Shard 1] --> B[Virtual Node 1]
    A --> C[Virtual Node 2]
    A --> D[Virtual Node 3]

    E[Shard 2] --> F[Virtual Node 1]
    E --> G[Virtual Node 2]
    E --> H[Virtual Node 3]

This gives each physical shard multiple positions on the ring and generally improves distribution.

Consistent Hashing vs Modulo Hashing

Modulo Hashing Consistent Hashing
Hash(key) % N Hash key onto a ring
Adding nodes can remap many keys Usually only affected ranges move
Simple More complex
Good for fixed node counts Useful when nodes change dynamically

Important Interview Point

Consistent hashing does not mean that no data moves when a node changes.

It means the amount of remapping can be significantly reduced compared with simple modulo hashing.

When Should You Use It?

  • Large distributed systems.
  • Dynamic shard environments.
  • Distributed caching.
  • Systems where nodes can be added or removed.
  • Large-scale database routing.

Interview Scenario

Interviewer: You have 10 database shards and need to add an 11th shard. How can you avoid moving most of the data?

Answer: I could use consistent hashing. Instead of using Hash(key) % N, I would place shards and keys on a hash ring. When the new shard is added, only the keys in the affected ring range need to be redistributed. Virtual nodes can further improve distribution.

Common Interview Questions

  • What is consistent hashing?
  • Why is it better than simple modulo hashing when nodes change?
  • What happens when a shard is added?
  • What happens when a shard is removed?
  • What is a hash ring?
  • What are virtual nodes?
  • How can consistent hashing be used for database sharding?
  • How would ASP.NET Core route a request using consistent hashing?

Key Takeaways

  • Consistent hashing distributes keys using a hash ring.
  • It reduces data movement when nodes are added or removed.
  • Virtual nodes improve distribution.
  • It is useful for dynamic distributed systems.
  • ASP.NET Core can use a consistent-hash router before accessing SQL Server.
  • It is especially useful when the number of shards can change.

Conclusion

CustomerId
    ↓
Hash Function
    ↓
Consistent Hash Ring
    ↓
Shard
    ↓
SQL Server
    ↓
ASP.NET Core Response

Remember: modulo hashing is simple, but consistent hashing is useful when the number of nodes changes and minimizing data movement matters.

Comments & Ratings (0)

Log in or create a student account to comment and rate this article.

Be the first to comment on this article.

Never miss a new post 📬

Get an email whenever a new article, project, cheatsheet or video drops.

✅

ℹ️