Skip to content

Repository files navigation

🧠 RamStorage (r9e)

main branch GitHub go.mod Go version Go Reference Go Report Card CodeQL license Release release

RamStorage (r9e) is a small, thread-safe, generic, dependency-free Go library of in-memory key-value containers with a rich set of convenient methods to store, retrieve, transform, and iterate data.

It is focused on usability and simplicity β€” without giving up performance. Only the Go standard library is used; there are zero third-party dependencies.

✨ Features

  • πŸ”’ Thread-safe β€” every operation is safe for concurrent use.
  • 🧬 Generic β€” MapKeyValue[K comparable, T any] stores any comparable key and any value type.
  • 🧱 Two containers, one API β€” a sync.RWMutex-backed map and a sync.Map-backed store expose the same method set, so you can switch by workload.
  • πŸ” Range-over-func iterators β€” for k, v := range kv.All() (Go 1.23+).
  • 🧰 Functional helpers β€” Map, Filter, Partition, Merge, Clone, GetOrSet, SortKeys, SortValues β€” all non-mutating where it matters.
  • πŸ—„οΈ JSON-ready β€” implements json.Marshaler / json.Unmarshaler.
  • ⚑ O(1) Size() on both containers.
  • 🚫 Zero dependencies β€” standard library only.
  • πŸ“„ Apache-2.0 licensed.

🧭 Overview

r9e takes advantage of Go generics and the standard library's own concurrency primitives to provide a simple way to store and retrieve data from memory. Two containers share the same method set:

flowchart TD
    API["Shared API<br/>Set Β· Get Β· GetOrSet Β· Delete Β· All Β· Map Β· Filter Β· Partition Β· Merge Β· JSON"]
    API --> M["MapKeyValue[K, T]<br/>native map + sync.RWMutex"]
    API --> S["SMapKeyValue[K, T]<br/>sync.Map + atomic counter"]
    M --> M1["Best for read-heavy / mixed<br/>consistent bulk snapshots"]
    S --> S1["Best for disjoint-key writes<br/>write-once / read-many"]
Loading

Available Containers

Container Backing Best for
MapKeyValue[K, T] map + sync.RWMutex Read-heavy / mixed workloads, consistent snapshots
SMapKeyValue[K, T] sync.Map + atomic counter Disjoint-key writes, write-once/read-many

Not sure which to use? Start with MapKeyValue. Switch to SMapKeyValue only if profiling shows the sync.Map access pattern fits your workload better. See docs/containers.md.

πŸ“‹ Requirements

  • Go 1.26 or newer
  • No external Go modules

πŸ“¦ Installation

Add the latest release to your module:

go get github.com/slashdevops/r9e@latest

Pin a specific version:

go get github.com/slashdevops/r9e@vX.Y.Z

Update to the newest available version later:

go get -u github.com/slashdevops/r9e

Then import it:

import "github.com/slashdevops/r9e"

πŸš€ Quick Start

package main

import (
    "fmt"

    "github.com/slashdevops/r9e"
)

func main() {
    kv := r9e.NewMapKeyValue[string, int]()

    kv.Set("answer", 42)

    if v, ok := kv.GetAndCheck("answer"); ok {
        fmt.Println("answer =", v) // answer = 42
    }

    // Range-over-func iteration (Go 1.23+).
    for k, v := range kv.All() {
        fmt.Printf("%s = %d\n", k, v)
    }
}

A richer example

type Constant struct {
    Name  string
    Value float64
}

kv := r9e.NewMapKeyValue[string, Constant](r9e.WithCapacity(8))

kv.Set("pi", Constant{"Archimedes' constant", 3.141592})
kv.Set("e", Constant{"Euler's number", 2.718281})
kv.Set("phi", Constant{"Golden ratio", 1.618033})

// Keep only the "large" constants (returns a NEW container).
large := kv.FilterValue(func(c Constant) bool { return c.Value > 2.0 })

// Sort the survivors by value.
sorted := large.SortValues(func(a, b Constant) bool { return a.Value > b.Value })
for _, c := range sorted {
    fmt.Printf("%s = %v\n", c.Name, c.Value)
}

🧩 API at a glance

Both containers implement the same methods:

Group Methods
Access Set, Get, GetAndCheck, GetOrSet, GetAndDelete, Delete, Clear
Query Size, IsEmpty, ContainsKey, ContainsValue
Bulk Keys, Values, All (iterator), ForEach, ForEachKey, ForEachValue
Copy/Combine Clone, CloneAndClear, Merge, DeepEqual
Transform Map, MapKey, MapValue, Filter, FilterKey, FilterValue
Split Partition, PartitionKey, PartitionValue
Sort SortKeys, SortValues
Serialize MarshalJSON, UnmarshalJSON

Full reference: pkg.go.dev/github.com/slashdevops/r9e

πŸ“š Documentation

Extensive guides with runnable examples and diagrams live in docs/:

Guide What it covers
Getting Started Install, first program, core types.
Containers MapKeyValue vs SMapKeyValue, and how to choose.
Concurrency Thread-safety model and locking rules.
Operations Map / Filter / Partition / Merge / Clone / sorting.
Iteration All() iterators and the ForEach family.
JSON Marshaling containers to and from JSON.
Performance Cost model, benchmarks, and tuning.
Migration What's New in v1.0.0 and breaking changes.
FAQ Common questions and gotchas.

Runnable examples also live in example_test.go.

πŸ†• What's New in v1.0.0

v1.0.0 is the first stable release. It modernizes the library for Go 1.26 and adds several long-requested capabilities.

  • πŸ” Range-over-func iterators β€” All() returns an iter.Seq2[K, T].
  • 🧰 GetOrSet β€” atomic get-or-insert.
  • πŸ”— Merge β€” combine one container into another.
  • πŸ—„οΈ JSON support β€” MarshalJSON / UnmarshalJSON on both containers.
  • 🐞 Correctness fixes:
    • Fixed potential deadlocks in MapKeyValue caused by recursive read locking in Clone, Map, Filter, Partition, DeepEqual, and sorting.
    • Fixed SMapKeyValue.Size() over-counting when overwriting existing keys.
    • Fixed a non-atomic counter update race in SMapKeyValue.
  • 🧹 Modern internals β€” uses maps, slices, the clear builtin, and sync.Map.Clear.

πŸ’₯ Breaking Changes

Before After Why
go 1.19 go 1.26 Iterators, maps/slices, clear, sync.Map.Clear.
GetAnDelete(...) GetAndDelete(...) Fixes a spelling typo in the public API.
IsFull() bool removed β€” use !IsEmpty() The old method meant "not empty", which was misleading.
Key(k) K removed β€” use ContainsKey(k) The old method returned the key or a zero value; ContainsKey is clearer.
SortKeys(...) []*K SortKeys(...) []K Returning pointers into internal state was unsafe and unidiomatic.
SortValues(...) []*T SortValues(...) []T Same as above.

Migration guide with before/after snippets: docs/migration.md.

⚑ Performance

r9e keeps the common operations cheap:

  • Set, Get, GetAndCheck, GetOrSet, Delete, Size are O(1).
  • ContainsValue and the bulk Map / Filter / Partition / sort operations are O(n).
  • WithCapacity(n) presizes MapKeyValue to avoid incremental map growth.

Run the benchmarks yourself:

git clone git@github.com:slashdevops/r9e.git
cd r9e/
make bench
# or:
go test -run '^$' -bench . -benchmem ./...

See docs/performance.md for the cost model and tuning tips.

βœ… Local Quality Gate

The same checks CI runs:

make check      # fmt + vet + race tests + build
# individually:
go fmt ./...
go vet ./...
go test -race -covermode=atomic -coverprofile=coverage.txt ./...
go build ./...

🀝 Contributing

Issues and pull requests are welcome at github.com/slashdevops/r9e. Please keep changes small, idiomatic, tested, documented, and dependency-free unless there is a clear reason to expand the project scope. See .github/copilot-instructions.md (also linked as AGENTS.md) for conventions.

πŸ“„ License

RamStorage (r9e) is released under the Apache License 2.0:

About

ramstorage (r9e) is a Golang Memory storage library

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages