goshard

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

goshard

Go Reference Go Coverage

goshard is a high-performance, concurrent-safe sharded map for Go. It is engineered for 1M+ RPS workloads where minimizing GC pressure and lock contention is critical for system stability.

Why goshard?

Production Ready: Battle-tested in high-load production environments, reliably handling 1M+ RPS with stable P99 latency.

Standard sync.Map is excellent for read-heavy workloads with stable keys. However, in high-churn environments (frequent writes, deletions, and TTL-based evictions), sync.Map can become a bottleneck due to its single-writer lock and heap allocations.

goshard solves this by:

  1. Eliminating Heap Allocations: For comparable keys and values, goshard achieves zero allocations on most hot paths.
  2. Reducing Contention: Distributes load across 64+ independent shards, each with its own lock.
  3. Predictable Latency: By avoiding GC garbage, it prevents P99 latency spikes caused by mark-and-sweep pauses.
Feature goshard.Map sync.Map
Hot-path Allocations Zero 1-3 per Store
Write Lock Contention Distributed (N Shards) Global (1 Mutex)
Batch Deletion Shard-aware Batching Sequential
GC Pressure Extremely Low High at scale
Read Performance Fast (RLock) Extremely Fast (Lock-free)

Features

  • Zero-Allocation Hot PathStore, Load, Delete, and Swap allocate nothing on the heap for comparable types.
  • Cache-Line Padding — Prevents "false sharing" by ensuring shard locks don't collide on the same CPU cache line.
  • Shard-aware BatchingDeleteMany and LoadAndDeleteMany pre-sort keys to process entire groups under a single lock acquisition per shard.
  • Atomic Compute — Perform complex read-modify-write logic atomically within a single shard lock.
  • Go 1.23+ Ready — Native support for for range iterators via All().
  • Serialization — Built-in GobEncode and GobDecode for easy persistence or network transfer.

Installation

go get github.com/darxnet/goshard

Quick Start

// The zero value is ready to use!
var m goshard.Map[string, any]

// Comparable map with 64 shards
mc := goshard.NewComparableMap[int, int](64)

// Simple Load/Store
m.Store("rps", 1_000_000)
if v, ok := m.Load("rps"); ok {
    fmt.Printf("Current load: %d\n", v)
}

// Atomic update
counter, loaded := m.Compute("counter", func(key string, current int, loaded bool) (next int, keep bool) {
    return current + 1, true // keep = true to store
})

// Batch delete
m.DeleteMany([]string{"expired_1", "expired_2"})

Benchmarks

Environment: Apple M3 Pro · darwin/arm64 · Go 1.26 go test -bench=. -benchmem -count=3 -cpu=1,4,8 ./...

Parallel Store
Implementation 1 CPU 4 CPUs 8 CPUs Allocs/op
goshard 65 ns 26 ns 21 ns 0
sync.Map 234 ns 59 ns 38 ns 3 (64 B)
sync.RWMutex map 43 ns 215 ns 172 ns 0

goshard is slower than a bare mutex at 1 CPU (no contention), but 8.2x faster at 4 CPUs where the global lock degrades under contention. sync.Map always allocates per-store regardless of CPU count.

Write-Read-Delete Cycle

Sequential Store + Load + Delete per worker iteration; parallelism from b.RunParallel only. This is the most representative high-churn workload: frequent writes and deletes with no idle keys.

Implementation 1 CPU 4 CPUs 8 CPUs Allocs/op
goshard 42 ns 31 ns 30 ns 0
sync.Map 63 ns 207 ns 221 ns 2-3 (63-74 B)
sync.RWMutex map 25 ns 159 ns 174 ns 0

goshard is 7.4x faster than sync.Map and 5.8x faster than a global mutex at 8 CPUs — with zero heap allocations. sync.Map degrades under write-heavy parallel load because its promotion mechanism serialises writers; the global mutex simply saturates.

Batch Delete: DeleteMany vs Sequential Loop

Shard-sorted batching (DeleteMany) vs sequential single-key Delete calls. Breakeven is around n=1,000 under parallel load; below that the sort overhead is net-negative single-threaded.

Batch size (n) Loop 1 CPU DeleteMany 1 CPU Loop 8 CPUs DeleteMany 8 CPUs
10 113 ns 113 ns 209 ns 222 ns
1,000 10,403 ns 25,428 ns 14,614 ns 5,551 ns
10,000 104,279 ns 280,367 ns 140,003 ns 43,180 ns
1,000,000 10.5 ms 41.7 ms 14.0 ms 6.4 ms

At n=10,000 with 8 goroutines, DeleteMany is 3.2x faster than a sequential loop. Single-threaded it is slower due to the slice sort; the batching benefit only materialises under parallel lock contention.

License

Apache License 2.0. See LICENSE for details.

Documentation

Overview

Package goshard provides a high-performance, concurrent-safe sharded map.

It is designed to minimize lock contention in high-throughput environments by distributing entries across multiple independent shards, each with its own RWMutex. This approach significantly outperforms a single sync.RWMutex for write-heavy workloads and scales better with the number of CPU cores.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ComparableMap

type ComparableMap[K comparable, V comparable] struct {
	Map[K, V]
}

ComparableMap is a Map with specialized atomic operations for comparable values.

Example (CompareAndDelete)

ExampleComparableMap_compareAndDelete demonstrates deleting a key only when its value matches the expected one.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	m := goshard.NewComparableMap[string, int](0)
	m.Store("session", 42)

	fmt.Println(m.CompareAndDelete("session", 0))  // wrong value — no-op
	fmt.Println(m.CompareAndDelete("session", 42)) // matches — deleted

	_, ok := m.Load("session")
	fmt.Println(ok)

}
Output:
false
true
false
Example (CompareAndSwap)

ExampleComparableMap_compareAndSwap demonstrates the lock-free-style compare-and-swap for comparable values.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	m := goshard.NewComparableMap[string, string](0)
	m.Store("status", "idle")

	// Succeeds: current value matches "idle".
	fmt.Println(m.CompareAndSwap("status", "idle", "busy"))

	// Fails: current value is "busy", not "idle".
	fmt.Println(m.CompareAndSwap("status", "idle", "busy"))

}
Output:
true
false

func NewComparableMap

func NewComparableMap[K comparable, V comparable](n int) *ComparableMap[K, V]

NewComparableMap returns a sharded map for comparable values. If n is zero, NewComparableMap chooses a concurrency-oriented default; otherwise n is rounded up to the next power of two.

func (*ComparableMap[K, V]) CompareAndDelete

func (sm *ComparableMap[K, V]) CompareAndDelete(key K, old V) (deleted bool)

CompareAndDelete deletes the entry for key if its value is equal to old.

func (*ComparableMap[K, V]) CompareAndSwap

func (sm *ComparableMap[K, V]) CompareAndSwap(key K, old, new V) (swapped bool)

CompareAndSwap swaps the old and new values for key if the value stored in the map is equal to old.

type Map

type Map[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Map is a concurrent-safe sharded map optimized for reduced lock contention compared to a Go map paired with a sync.RWMutex.

The zero Map is empty and ready for use. A Map must not be copied after first use.

Example

ExampleMap demonstrates basic store, load, and delete operations.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	m := goshard.NewMap[string, int](0)

	m.Store("active_users", 1024)

	if v, ok := m.Load("active_users"); ok {
		fmt.Println(v)
	}

	m.Delete("active_users")

	if _, ok := m.Load("active_users"); !ok {
		fmt.Println("gone")
	}

}
Output:
1024
gone
Example (Compute)

ExampleMap_compute demonstrates an atomic read-modify-write under the shard lock — safe to call concurrently.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	m := goshard.NewMap[string, int](0)
	m.Store("counter", 0)

	m.Compute("counter", func(_ string, current int, loaded bool) (next int, keep bool) {
		return current + 1, true
	})

	v, _ := m.Load("counter")
	fmt.Println(v)

}
Output:
1
Example (DeleteMany)

ExampleMap_deleteMany demonstrates efficient batch deletion. Keys are sorted by shard internally to minimise lock acquisitions.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	m := goshard.NewMap[string, int](0)
	m.Store("s1", 1)
	m.Store("s2", 2)
	m.Store("s3", 3)

	m.DeleteMany([]string{"s1", "s2", "s3"})

	fmt.Println(m.Empty())

}
Output:
true
Example (GobEncode)

ExampleMap_gobEncode demonstrates encoding a map to bytes and decoding it back into a new map using encoding/gob.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	src := goshard.NewMap[string, int](0)
	src.Store("a", 1)

	data, err := src.GobEncode()
	if err != nil {
		panic(err)
	}

	dst := goshard.NewMap[string, int](0)
	if err := dst.GobDecode(data); err != nil {
		panic(err)
	}

	v, ok := dst.Load("a")
	fmt.Println(v, ok)

}
Output:
1 true
Example (LoadOrStore)

ExampleMap_loadOrStore demonstrates get-or-set semantics: the first call stores the value, the second returns the existing one.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	m := goshard.NewMap[string, int](0)

	actual, loaded := m.LoadOrStore("limit", 100)
	fmt.Println(actual, loaded) // stored

	actual, loaded = m.LoadOrStore("limit", 999)
	fmt.Println(actual, loaded) // existing value returned

}
Output:
100 false
100 true
Example (Zero)

ExampleMap_zero shows that the zero value of Map is ready to use — no constructor call required.

package main

import (
	"fmt"

	"github.com/darxnet/goshard"
)

func main() {
	var m goshard.Map[string, int]

	m.Store("score", 42)

	v, ok := m.Load("score")
	fmt.Println(v, ok)

}
Output:
42 true

func NewMap

func NewMap[K comparable, V any](n int) *Map[K, V]

NewMap returns a sharded map with n shards. If n is zero, NewMap chooses a concurrency-oriented default; otherwise n is rounded up to the next power of two.

func (*Map[K, V]) All

func (sm *Map[K, V]) All() iter.Seq2[K, V]

All returns an iterator over each key and value present in the map.

The iterator does not necessarily correspond to any consistent snapshot of the Map's contents: no key will be visited more than once, but if the value for any key is stored or deleted concurrently (including by yield), the iterator may reflect any mapping for that key from any point during iteration. The iterator does not block other methods on the receiver; even yield itself may call any method on the Map.

func (*Map[K, V]) Clear

func (sm *Map[K, V]) Clear()

Clear deletes all entries from the map.

func (*Map[K, V]) CompareAndDelete

func (sm *Map[K, V]) CompareAndDelete(key K, old V, eq func(current, old V) bool) (deleted bool)

CompareAndDelete deletes the entry for key if its value is equal to old according to the eq function. The eq function is called while the shard for the key is locked.

func (*Map[K, V]) CompareAndSwap

func (sm *Map[K, V]) CompareAndSwap(key K, old, new V, eq func(current, old V) bool) (swapped bool)

CompareAndSwap swaps the old and new values for a key if the value stored in the map is equal to old according to the eq function. The eq function is called while the shard for the key is locked.

func (*Map[K, V]) Compute

func (sm *Map[K, V]) Compute(key K, f func(key K, current V, loaded bool) (next V, keep bool)) (value V, loaded bool)

Compute replaces or deletes the value for a key using f. If f returns keep=false, the key is deleted. The function f is called while the key's shard is locked.

func (*Map[K, V]) Delete

func (sm *Map[K, V]) Delete(key K)

Delete deletes the value for a key.

func (*Map[K, V]) DeleteMany

func (sm *Map[K, V]) DeleteMany(keys []K)

DeleteMany deletes each key in keys from the map.

func (*Map[K, V]) Empty

func (sm *Map[K, V]) Empty() bool

Empty returns true if the map contains no elements.

func (*Map[K, V]) GobDecode

func (sm *Map[K, V]) GobDecode(bs []byte) error

GobDecode merges gob-encoded shard maps into the map. It applies each decoded gob map immediately, so malformed input can leave earlier decoded entries merged before GobDecode returns an error.

func (*Map[K, V]) GobEncode

func (sm *Map[K, V]) GobEncode() ([]byte, error)

GobEncode encodes all non-empty map shards as a sequence of gob maps. Empty shards are skipped; GobDecode reads until EOF and handles a variable number of encoded maps correctly.

func (*Map[K, V]) Len

func (sm *Map[K, V]) Len() int

Len returns the number of elements in the map.

func (*Map[K, V]) Load

func (sm *Map[K, V]) Load(key K) (value V, ok bool)

Load returns the value stored in the map for a key, or the zero value if no value is present. The ok result indicates whether value was found in the map.

func (*Map[K, V]) LoadAndDelete

func (sm *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool)

LoadAndDelete deletes the value for a key, returning the previous value if any. The loaded result reports whether the key was present.

func (*Map[K, V]) LoadAndDeleteMany

func (sm *Map[K, V]) LoadAndDeleteMany(keys []K, f func(K, V))

LoadAndDeleteMany deletes each key in keys from the map and calls f with each key/value pair that was present. The function f is called after the key's shard lock is released.

func (*Map[K, V]) LoadOrStore

func (sm *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool)

LoadOrStore returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.

func (*Map[K, V]) Range

func (sm *Map[K, V]) Range(yield func(K, V) bool)

Range calls f sequentially for each key and value present in the map. If f returns false, range stops the iteration.

This exists for compatibility with sync.Map; All should be preferred.

func (*Map[K, V]) Store

func (sm *Map[K, V]) Store(key K, value V)

Store sets the value for a key.

func (*Map[K, V]) Swap

func (sm *Map[K, V]) Swap(key K, value V) (previous V, loaded bool)

Swap swaps the value for a key and returns the previous value if any. The loaded result reports whether the key was present.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL