Help us shape the future of Go! Take the 2026 Go Developer Survey

store

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 17 Imported by: 2

README

Store Package - List Operations

The Store package provides a unified interface for key-value storage with support for MongoDB-style list operations across multiple backend implementations.

Features

  • Unified Interface: Consistent API across LRU cache, Redis, MongoDB, and Xun implementations
  • Complete API: 25+ methods covering key-value and list operations
  • Wildcard Delete: Pattern-based key deletion with * wildcard support
  • Pattern-based Keys/Len: Query keys and count by pattern (e.g., Keys("user:*"), Len("chat:*"))
  • MongoDB-style API: Familiar operations for developers using MongoDB
  • List Operations: Full support for array/list data structures
  • Pagination Support: Built-in pagination with ArrayPage and ArraySlice
  • Thread Safety: Full concurrency support with comprehensive stress testing
  • Type Safety: Strongly typed interface with Go generics support
  • Database Storage: Xun driver for database-backed persistent storage with LRU cache layer

Supported Backends

  • LRU Cache: In-memory cache with LRU eviction
  • Redis: Distributed cache using Redis lists
  • MongoDB: Document-based storage using MongoDB arrays
  • Xun: Database-backed storage with LRU cache and async persistence

Complete API Reference

Key-Value Operations
Get

Get a value by key.

value, ok := store.Get("mykey")
if ok {
    fmt.Printf("Value: %v\n", value)
}
Set

Set a value with optional TTL.

// Set without expiration
err := store.Set("mykey", "myvalue", 0)

// Set with 1 hour expiration
err := store.Set("mykey", "myvalue", time.Hour)
Del

Delete a key or keys matching a pattern.

// Delete a single key
err := store.Del("mykey")

// Delete all keys matching a pattern (wildcard support)
err := store.Del("user:123:*")    // Delete all keys starting with "user:123:"
err := store.Del("chat:*")        // Delete all keys starting with "chat:"
err := store.Del("session:456:*") // Delete all keys starting with "session:456:"

Wildcard Pattern:

  • Use * as a suffix wildcard to match multiple keys
  • Pattern user:123:* matches user:123:name, user:123:email, user:123:settings, etc.
  • Useful for cleaning up namespaced data (e.g., user sessions, chat history, cache invalidation)
Has

Check if a key exists.

exists := store.Has("mykey")
Len

Get the total number of keys in the store, optionally filtered by pattern.

// Get total count
count := store.Len()

// Get count matching pattern
userCount := store.Len("user:*")           // Count all user keys
user123Count := store.Len("user:123:*")    // Count all keys for user 123

Pattern Support:

  • Use * as a suffix wildcard to match multiple keys
  • Pattern user:* matches user:123:name, user:456:email, etc.
  • Empty pattern or no pattern returns total count
Keys

Get all keys in the store, optionally filtered by pattern.

// Get all keys
allKeys := store.Keys()

// Get keys matching pattern
userKeys := store.Keys("user:*")           // All user keys
user123Keys := store.Keys("user:123:*")    // All keys for user 123
chatKeys := store.Keys("chat:*")           // All chat keys

Pattern Support:

  • Use * as a suffix wildcard to match multiple keys
  • Pattern user:123:* matches user:123:name, user:123:email, user:123:settings, etc.
  • Empty pattern or no pattern returns all keys
  • Useful for listing namespaced data (e.g., user data, chat history, cache entries)
Clear

Remove all keys from the store.

store.Clear()
GetSet

Get a value, or set it if it doesn't exist (atomic operation).

value, err := store.GetSet("mykey", time.Hour, func(key string) (interface{}, error) {
    return "default_value", nil
})
GetDel

Get a value and delete it atomically.

value, ok := store.GetDel("mykey")
Batch Operations
GetMulti

Get multiple values at once.

keys := []string{"key1", "key2", "key3"}
values := store.GetMulti(keys)
for key, value := range values {
    fmt.Printf("%s: %v\n", key, value)
}
SetMulti

Set multiple key-value pairs at once.

values := map[string]interface{}{
    "key1": "value1",
    "key2": 42,
    "key3": true,
}
store.SetMulti(values, time.Hour)
DelMulti

Delete multiple keys at once.

keys := []string{"key1", "key2", "key3"}
store.DelMulti(keys)
GetSetMulti

Get multiple values, setting defaults for missing ones.

keys := []string{"key1", "key2", "key3"}
values := store.GetSetMulti(keys, time.Hour, func(key string) (interface{}, error) {
    return fmt.Sprintf("default_%s", key), nil
})
Atomic Counter Operations
Incr

Increment a numeric value and return the new value.

// Increment by 1
newValue, err := store.Incr("counter", 1)

// Increment by 10
newValue, err := store.Incr("counter", 10)

// Increment non-existent key (starts from 0)
newValue, err := store.Incr("new_counter", 5) // Returns 5
Decr

Decrement a numeric value and return the new value.

// Decrement by 1
newValue, err := store.Decr("counter", 1)

// Decrement by 10 (can go negative)
newValue, err := store.Decr("counter", 10)

Use Cases:

  • Page view counters
  • Rate limiting
  • Inventory tracking
  • Session counters
  • Distributed locks with timeouts

List Operations API

Basic List Operations
Push

Add elements to the end of a list (similar to MongoDB $push).

err := store.Push("mylist", "item1", "item2", "item3")
Pop

Remove and return an element from a list.

// Pop from end (position = 1)
value, err := store.Pop("mylist", 1)

// Pop from beginning (position = -1)
value, err := store.Pop("mylist", -1)

Parameters:

  • position = 1: Remove from the end of the list (like RPOP in Redis)
  • position = -1: Remove from the beginning of the list (like LPOP in Redis)

Returns: The removed element and an error if the list is empty or doesn't exist.

ArrayLen

Get the length of a list.

length := store.ArrayLen("mylist")
Element Access
ArrayGet

Get an element at a specific index.

value, err := store.ArrayGet("mylist", 0) // Get first element
value, err := store.ArrayGet("mylist", -1) // Get last element (if supported)

Parameters:

  • index: Zero-based index. Most implementations support positive indices only.

Returns: The element at the specified index, or an error if index is out of range.

ArraySet

Set an element at a specific index.

err := store.ArraySet("mylist", 1, "new_value")

Parameters:

  • index: Zero-based index. Must be within the current list bounds.
  • value: New value to set at the specified index.

Note: This operation modifies an existing element and cannot extend the list.

ArrayAll

Get all elements in a list.

allItems, err := store.ArrayAll("mylist")
Pagination
ArrayPage

Get a specific page of elements (page starts from 1).

// Get page 1 with 10 items per page
page1, err := store.ArrayPage("mylist", 1, 10)

// Get page 2 with 10 items per page
page2, err := store.ArrayPage("mylist", 2, 10)

Parameters:

  • page: Page number starting from 1
  • pageSize: Number of items per page

Returns: Elements for the specified page. Returns empty slice if page is beyond list bounds.

ArraySlice

Get a slice of elements with skip and limit.

// Skip 5 elements, return next 10
slice, err := store.ArraySlice("mylist", 5, 10)

Parameters:

  • skip: Number of elements to skip from the beginning
  • limit: Maximum number of elements to return

Returns: A slice of elements. Returns empty slice if skip is beyond list bounds.

Element Removal
Pull

Remove all occurrences of a specific value (similar to MongoDB $pull).

err := store.Pull("mylist", "unwanted_value")

Parameters:

  • value: The value to remove from the list

Behavior: Removes ALL occurrences of the specified value. This is an atomic operation.

PullAll

Remove all occurrences of multiple values (similar to MongoDB $pullAll).

err := store.PullAll("mylist", []interface{}{"value1", "value2", "value3"})

Parameters:

  • values: Slice of values to remove from the list

Behavior: Removes ALL occurrences of ANY of the specified values. More efficient than multiple Pull calls.

Set Operations
AddToSet

Add elements only if they don't already exist (similar to MongoDB $addToSet).

err := store.AddToSet("mylist", "unique1", "unique2", "existing_value")

Parameters:

  • values: Variable number of values to add

Behavior:

  • Only adds values that don't already exist in the list
  • Maintains list uniqueness
  • Multiple values can be added in a single atomic operation
  • Duplicate values in the input are also deduplicated

Usage Examples

Basic List Operations
package main

import (
    "fmt"
    "github.com/yaoapp/gou/store"
)

func main() {
    // Create a store instance (LRU example)
    s, err := store.New(nil, store.Option{"size": 1000})
    if err != nil {
        panic(err)
    }

    // Add items to a list
    err = s.Push("fruits", "apple", "banana", "cherry")
    if err != nil {
        panic(err)
    }

    // Get list length
    fmt.Printf("List length: %d\n", s.ArrayLen("fruits")) // Output: 3

    // Get all items
    items, err := s.ArrayAll("fruits")
    if err != nil {
        panic(err)
    }
    fmt.Printf("All items: %v\n", items) // Output: [apple banana cherry]

    // Get specific item
    item, err := s.ArrayGet("fruits", 1)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Second item: %v\n", item) // Output: banana
}
Pagination Example
// Add many items
for i := 0; i < 100; i++ {
    s.Push("numbers", i)
}

// Get first page (10 items per page)
page1, err := s.ArrayPage("numbers", 1, 10)
if err != nil {
    panic(err)
}
fmt.Printf("Page 1: %v\n", page1) // Output: [0 1 2 3 4 5 6 7 8 9]

// Get second page
page2, err := s.ArrayPage("numbers", 2, 10)
if err != nil {
    panic(err)
}
fmt.Printf("Page 2: %v\n", page2) // Output: [10 11 12 13 14 15 16 17 18 19]
Set Operations Example
// Add unique items only
err = s.AddToSet("tags", "go", "database", "cache", "go") // "go" won't be added twice
if err != nil {
    panic(err)
}

tags, err := s.ArrayAll("tags")
if err != nil {
    panic(err)
}
fmt.Printf("Unique tags: %v\n", tags) // Output: [go database cache]

Implementation Details

LRU Cache Implementation
  • Stores lists as []interface{} in memory
  • Fast access but limited by memory
  • No persistence across restarts
  • TTL support: Lazy expiration check on Get/Has operations
  • Thread-safe operations with read-write mutex protection
  • Copy-on-write semantics to prevent data races
  • Returns data copies to prevent external modification
  • Wildcard delete: Iterates all keys and matches prefix pattern
Redis Implementation
  • Uses Redis LIST commands (RPUSH, LPOP, LRANGE, etc.)
  • Distributed and persistent
  • Atomic operations
  • JSON serialization for complex data types
  • Uses Lua scripts for complex operations like AddToSet
  • Wildcard delete: Uses SCAN + DEL for safe pattern deletion
MongoDB Implementation
  • Uses MongoDB array operators ($push, $pull, $addToSet, etc.)
  • Document-based storage with atomic updates
  • Aggregation pipeline for complex queries
  • Native support for array operations
  • Persistent and scalable
  • Wildcard delete: Uses regex pattern with DeleteMany
Xun Implementation
  • Database-backed storage using the Xun ORM
  • LRU cache layer for read performance optimization
  • Asynchronous batch persistence (configurable interval, default 1 minute)
  • Lazy loading - data loaded from database on first access
  • Automatic table creation and schema management
  • TTL support with background cleanup goroutine
  • Write-through cache with dirty tracking
  • Supports MySQL, PostgreSQL, SQLite, and other databases via Xun connectors
  • Wildcard delete: Uses SQL LIKE pattern for efficient batch deletion

Configuration

LRU Cache
store, err := store.New(nil, store.Option{"size": 10000})
Redis
// Redis connector must be configured first
store, err := store.New(redisConnector, store.Option{})
MongoDB
// MongoDB connector must be configured first
store, err := store.New(mongoConnector, store.Option{})
Xun (Database)
// Using default database connector
store, err := store.New(nil, store.Option{
    "type": "xun",
    "table": "__my_store",      // Table name (default: __store_default)
    "connector": "default",      // Database connector (default: default)
})

// With custom cache size and intervals
store, err := store.New(nil, store.Option{
    "type": "xun",
    "table": "__my_store",
    "connector": "default",
    "cache_size": 10240,         // LRU cache size (default: 10240)
    "persist_interval": 60,      // Async persistence interval in seconds (default: 60)
    "cleanup_interval": 5,       // Expired data cleanup interval in minutes (default: 5)
})

Testing

Run the comprehensive test suite:

# Set up test environment
source env.local.sh

# Run all tests
go test ./store -v

# Run specific test
go test ./store -v -run TestLRU

# Run concurrency tests
go test ./store -v -run TestLRUConcurrency
go test ./store -v -run TestRedisConcurrency
go test ./store -v -run TestMongoConcurrency
go test ./store -v -run TestXunConcurrency

# Run TTL tests
go test ./store -v -run TestLRUTTL
go test ./store -v -run TestRedisTTL
go test ./store -v -run TestMongoTTL
go test ./store -v -run TestXunTTL

# Run benchmarks
go test ./store -bench=BenchmarkLRU -v
go test ./store -bench=BenchmarkRedis -v
go test ./store -bench=BenchmarkMongo -v

The test suite covers:

  • All list operations across all backends
  • Edge cases and error conditions
  • Pagination functionality
  • Set operations and uniqueness
  • Wildcard pattern deletion across all backends
  • Atomic counter operations (Incr/Decr) across all backends
  • TTL expiration testing for LRU, Redis, MongoDB, and Xun
  • Concurrency stress testing (100 goroutines)
  • Memory leak detection with 10MB threshold
  • Goroutine leak detection
  • Performance characteristics and benchmarks

Concurrency and Thread Safety

All store implementations are designed to be thread-safe and support concurrent access:

LRU Cache
  • Uses sync.RWMutex for reader-writer lock protection
  • Read operations (Get, ArrayGet, ArrayLen, etc.) use read locks for better concurrency
  • Write operations (Set, Push, Pop, etc.) use exclusive write locks
  • Copy-on-write semantics prevent data races during list modifications
  • Returns defensive copies to prevent external modification
Redis
  • Inherently thread-safe due to Redis's single-threaded nature
  • All operations are atomic at the Redis server level
  • Network I/O is handled safely by the Redis client library
  • Lua scripts ensure atomicity for complex operations like AddToSet
MongoDB
  • Thread-safe through MongoDB driver's connection pooling
  • All array operations use MongoDB's atomic update operators
  • Document-level locking ensures consistency
  • Aggregation pipelines are atomic and isolated
Xun
  • Uses sync.RWMutex for reader-writer lock protection
  • LRU cache layer with thread-safe access
  • Asynchronous batch writes reduce database contention
  • Dirty tracking ensures data consistency
  • Background goroutines for persistence and cleanup
  • Database transactions for atomic operations
Stress Testing

The store package includes comprehensive concurrency tests:

  • 100 concurrent goroutines performing mixed read/write operations
  • Memory leak detection with sub-10MB growth threshold
  • Goroutine leak detection ensuring proper cleanup
  • Race condition detection using Go's race detector

Example concurrent usage:

var wg sync.WaitGroup
for i := 0; i < 100; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()

        // Safe concurrent operations
        store.Push("shared_list", fmt.Sprintf("item_%d", id))
        value, _ := store.Pop("shared_list", 1)
        store.ArrayLen("shared_list")
    }(i)
}
wg.Wait()

Error Handling

All list operations return appropriate errors for:

  • Non-existent keys
  • Index out of range
  • Type mismatches
  • Backend connection issues

Example error handling:

value, err := store.ArrayGet("mylist", 10)
if err != nil {
    if strings.Contains(err.Error(), "index out of range") {
        fmt.Println("Index is too large")
    } else {
        fmt.Printf("Other error: %v\n", err)
    }
}

Performance Considerations

LRU Cache
  • Best for: Frequent access, small datasets
  • O(1) for most operations
  • Memory limited
Redis
  • Best for: Distributed applications, medium to large datasets
  • O(1) for Push/Pop operations
  • O(N) for Pull operations
  • Network latency considerations
MongoDB
  • Best for: Complex queries, very large datasets
  • O(1) for indexed operations
  • O(N) for array operations
  • Supports complex aggregation queries
Xun
  • Best for: Applications using existing database infrastructure
  • O(1) for cached reads (LRU cache hit)
  • O(log N) for database reads (with indexing)
  • Async persistence reduces write latency
  • Leverages existing database for persistence
  • Suitable for applications with database connectivity
  • Note: Up to persist_interval seconds of data may be lost on crash

Migration Guide

If you're upgrading from the basic Store interface, the new list operations are additive and won't break existing code. Simply start using the new methods where needed.

License

This package is part of the GOU framework and follows the same licensing terms.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Pools = map[string]Store{}

Pools LRU pools

View Source
var StoreHandlers = map[string]process.Handler{
	"get":    processStoreGet,
	"set":    processStoreSet,
	"has":    processStoreHas,
	"del":    processStoreDel,
	"getdel": processStoreGetDel,
	"len":    processStoreLen,
	"keys":   processStoreKeys,
	"clear":  processStoreClear,

	"push":       processStorePush,
	"pop":        processStorePop,
	"pull":       processStorePull,
	"pullall":    processStorePullAll,
	"addtoset":   processStoreAddToSet,
	"arraylen":   processStoreArrayLen,
	"arrayget":   processStoreArrayGet,
	"arrayset":   processStoreArraySet,
	"arrayslice": processStoreArraySlice,
	"arraypage":  processStoreArrayPage,
	"arrayall":   processStoreArrayAll,
}

StoreHandlers store process handlers

Functions

This section is empty.

Types

type FreshReader added in v1.0.0

type FreshReader interface {
	GetFresh(key string) (value interface{}, ok bool)
}

FreshReader is an optional interface for stores with internal caching. GetFresh bypasses the in-process cache and reads directly from the backing store (e.g. database), then updates the cache with fresh data. Stores without internal caching (redis, mongo) do not need this; their Get already reads from the external store.

type Instance added in v0.10.3

type Instance struct {
	types.MetaInfo
	Name      string                 `json:"name"`
	Connector string                 `json:"connector,omitempty"`
	Type      string                 `json:"type,omitempty"` // warning: type is deprecated in the future new version
	Option    map[string]interface{} `json:"option,omitempty"`
}

Instance the kv-store setting

type Option

type Option map[string]interface{}

Option the store option

type Store

type Store interface {
	Get(key string) (value interface{}, ok bool)
	Set(key string, value interface{}, ttl time.Duration) error
	Del(key string) error // Supports wildcard pattern with * (e.g., "user:123:*")
	Has(key string) bool
	Len(pattern ...string) int       // Optional pattern with * wildcard (e.g., "user:*")
	Keys(pattern ...string) []string // Optional pattern with * wildcard (e.g., "user:*")
	Clear()
	GetSet(key string, ttl time.Duration, getValue func(key string) (interface{}, error)) (interface{}, error)
	GetDel(key string) (value interface{}, ok bool)
	GetMulti(keys []string) map[string]interface{}
	SetMulti(values map[string]interface{}, ttl time.Duration)
	DelMulti(keys []string)
	GetSetMulti(keys []string, ttl time.Duration, getValue func(key string) (interface{}, error)) map[string]interface{}

	// Atomic counter operations
	Incr(key string, delta int64) (int64, error) // Increment a numeric value, returns new value
	Decr(key string, delta int64) (int64, error) // Decrement a numeric value, returns new value

	// List operations - MongoDB-style API
	Push(key string, values ...interface{}) error
	Pop(key string, position int) (interface{}, error) // position: 1=last, -1=first
	Pull(key string, value interface{}) error
	PullAll(key string, values []interface{}) error
	AddToSet(key string, values ...interface{}) error
	ArrayLen(key string) int
	ArrayGet(key string, index int) (interface{}, error)
	ArraySet(key string, index int, value interface{}) error
	ArraySlice(key string, skip, limit int) ([]interface{}, error)
	ArrayPage(key string, page, pageSize int) ([]interface{}, error)
	ArrayAll(key string) ([]interface{}, error)

	// Flush persists any pending writes to the backing store.
	// For async-persist stores (e.g. xun) this drains the dirty queue;
	// for purely in-memory stores (e.g. lru) this is a no-op.
	Flush()
}

Store The interface of a key-value store

func Get added in v1.0.0

func Get(name string) (Store, error)

Get Get the store from the pool

func Load added in v0.10.3

func Load(file string, name string) (Store, error)

Load load kv store

func LoadSource added in v1.0.0

func LoadSource(data []byte, id string, file string) (Store, error)

LoadSource load store from source

func LoadSourceSync added in v1.0.0

func LoadSourceSync(data []byte, id string, file string) (Store, error)

LoadSourceSync load store from source sync

func LoadSync added in v1.0.0

func LoadSync(file string, name string) (Store, error)

LoadSync load store sync

func New

func New(c connector.Connector, option Option) (Store, error)

New create a store via connector

func NewXunStore added in v1.0.0

func NewXunStore(option Option) (Store, error)

NewXunStore create a new xun store from option

func Select added in v0.10.3

func Select(name string) Store

Select Select loaded kv store

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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