ttlmap

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

ttlmap_xsync

A concurrent generic map with per-entry TTL, backed by xsync.Map (lock-free live reads). An expiry min-heap drives background cleanup. Optional snapshot persistence uses a cross-process file lock.

Targets working sets of thousands to hundreds of thousands of entries. Entries may expire after a duration or never expire (NoExpiration).

Relationship to ttlmap_std

This directory is the xsync-based version; ttlmap_std is the pure standard-library version (zero third-party dependencies). The two share the same API and the same snapshot file format (mutually readable). Only the underlying storage differs.

Version Storage Live read path
ttlmap_xsync xsync.Map Lock-free (Get/Peek of unexpired entries)
ttlmap_std Sharded map + per-shard RWMutex RLock

Prefer this version when reads dominate. Prefer ttlmap_std when you cannot take a third-party dependency.

Install

Requires Go 1.24+ (runtime.AddCleanup, iter.Seq2).

go get github.com/bagualing/ttlmap_xsync
import ttlmap "github.com/bagualing/ttlmap_xsync"

Quick start

m := ttlmap.New[string, []byte](30 * time.Second)
defer func() { _ = m.Close() }()

m.Set("session", data, 0)                 // 0 = map default TTL (30s)
m.Set("job", payload, 5*time.Minute)      // per-key TTL
m.Set("config", cfg, ttlmap.NoExpiration) // never expire

v, ok := m.Get("session")

v, loaded := m.GetOrSet("session", computed, 30*time.Second)
newVal, ok := m.Update("counter", time.Minute, func(n int) int { return n + 1 })

Always create with New (memory only) or Open (snapshot persistence), and call Close so background goroutines stop promptly. If you never call Close, they stop when the map is garbage-collected; no unsaved snapshot is written at GC time.

TTL rules

Value Meaning
New(defaultTTL) defaultTTL must be > 0 or NoExpiration (0 panics)
Open / Load Same as New, except 0 returns an error rather than panicking
ttl == 0 / DefaultExpiration Use the map default TTL
ttl < 0 / NoExpiration Never expire
ttl > 0 Expire ttl after now

GetTTL / GetWithTTL return NoExpiration (-1) for never-expire entries. Finite expiry is stored as an absolute Unix-nanosecond timestamp, so remaining TTL survives restarts. Never-expire is stored as math.MaxInt64 (older library versions read that as far-future expiry).

There is no separate “forever” API: pass NoExpiration to Set, GetOrSet, Update, SetTTL, or MSet. New(NoExpiration) makes the map default never-expire as well; an explicit positive ttl still expires.

Options

m := ttlmap.New[string, int](time.Second,
    ttlmap.WithCleanupInterval(500*time.Millisecond), // <= 0 disables the janitor
    ttlmap.WithClock(clk),                            // test injection; Now must be concurrency-safe
    ttlmap.WithCapacity(8192),                        // floor passed to xsync.WithPresize
)

API

Method Description
DefaultTTL() Map-level default TTL (may be NoExpiration)
Set(key, value, ttl) Insert or overwrite
Get(key) (V, bool) Live value; lazily deletes expired entries
Peek(key) (V, bool) Live value; does not delete expired entries
GetWithTTL(key) (V, time.Duration, bool) Value + remaining TTL (NoExpiration if never-expire)
Has(key) bool Live presence; lazy deletion
GetOrSet(key, value, ttl) (V, bool) Atomic get-or-store; loaded=false means this call stored
SetIfAbsent(key, value, ttl) bool Store only if absent or expired
Update(key, ttl, fn) (V, bool) Atomic read-modify-write + TTL refresh
Delete(key) / LoadAndDelete(key) Delete / delete and return a live value
SetTTL(key, ttl) bool / GetTTL(key) (time.Duration, bool) Refresh / read remaining TTL
MSet(values, ttl) / MGet(keys...) Batch write / batch read (not globally atomic)
Range / All Visit live entries, including never-expire; skip expired
Size() int Includes expired entries not yet cleaned up
DeleteExpired() int / Clear() Sweep expired entries / clear all
Close() error Stop background goroutines; write a final snapshot if persistence is on
Open / Load / EnablePersistence Optional snapshot persistence (off by default)
Save / SaveFile / WriteSnapshot / PersistErr / PersistenceEnabled Snapshot I/O

Set / GetOrSet / Update / MSet copy V itself. If V contains slices or pointers (such as []byte), do not mutate the underlying data after writing. Update's fn runs inside xsync's bucket lock: it must be fast, must not block, and must not call back into this Map.

Persistence

Disabled by default. Open enables full snapshots (not an append-only log). Load restores a new map from a snapshot and leaves persistence off.

m, err := ttlmap.Open[string, []byte](30*time.Second, ttlmap.PersistConfig[string, []byte]{
    Path:  "ttlmap.snap",
    Codec: ttlmap.StringBytesCodec{},
    // Interval 0 (default) = 5s dirty snapshot; negative = Save/Close only
})
if err != nil {
    log.Fatal(err)
}
defer func() { _ = m.Close() }()
Situation Behavior
File missing Not an error; starts empty
File present Open loads live records, including never-expire; skips entries that expired while stopped
Interval 0 → 5s; negative → no periodic writes
Dirty flag Live writes and expiry deletions mark dirty; periodic writes skip when unchanged
Close / Save Always write a snapshot (even if not dirty)
Crash safety

Snapshots are published as “temp file → fsync(file) → atomic renamefsync(parent directory)” (both fsyncs skipped with NoSync):

  • On disk, Path is always a complete new or old snapshot — never half-written.
  • After Save / Close / SaveFile returns successfully, that snapshot remains loadable after a process crash or power loss.
  • .tmp-* leftovers from a previous crash are cleaned up on the next Open / Save.

Reads and writes of the same path hold a cross-process exclusive lock (POSIX flock / Windows LockFileEx, lock file <path>.lock). This is not a distributed lock: do not share one path across hosts or network filesystems.

Snapshots are little-endian with a CRC64(ECMA) trailer, readable across OS/architectures. Decoding is streaming, bounded by 1 GiB total, 256 MiB per record, and 1 million records. flags in the header is reserved (currently 0).

Codecs:

Codec Use
JSONCodec[K, V] Any JSON-serializable type
StringBytesCodec Map[string, []byte] raw bytes
StringBinaryCodec[V] Map[string, V] where V implements BinaryMarshaler / Unmarshaler
BinaryCodec[K, V] K and V both implement those interfaces

A codec Marshal must not call this map's persistence methods (Save / SaveFile / WriteSnapshot / Close).

Concurrency

All methods are concurrency-safe. A Map must not be copied after first use. A custom Clock.Now is called from the janitor and from map operations; it must be non-blocking and concurrency-safe.

Stop all writers before Close / Save. Close is idempotent. After Close the map remains readable and writable; only background cleanup and periodic flushing stop.

Range / All: xsync copies each bucket, then runs the callback without holding the bucket lock, so the callback may write to the same map. Concurrent Sets during that pass may be missed.

When Get observes an expired entry at the same moment a concurrent Set refreshes it, this version returns a miss (the std version re-checks after a lock upgrade and may return the new value). Both are valid under their concurrency models.

Production notes

  • Snapshot memory: Save / periodic snapshots buffer all live records in memory before writing (peak ≈ snapshot size, up to ~2× with values still in the map). Codec Marshal runs outside map locks (xsync.Range copies the bucket first). A slow codec does not hold bucket locks.
  • Expiry heap: each finite-TTL write pushes one heap item; never-expire entries are not pushed. Between janitor ticks, heap size ≈ finite-TTL write rate × WithCleanupInterval. Heap items copy the key (costly for large by-value keys such as [N]byte).
  • Close: with persistence enabled, each Close writes a final snapshot; repeated calls re-flush. With persistence disabled, Close only stops background goroutines.
  • Lock granularity: Update / SetTTL / GetOrSet run inside xsync's bucket lock (coarser than per-key). Keep Update's fn short.

Run go test -bench . in this module for current numbers on your machine.

When not to use this

  • Millions of keys — expiry is O(log n) per finite-TTL write plus O(expired) per janitor tick, but resident memory is yours to budget.
  • LRU/LFU capacity eviction — this package does not evict by capacity.
  • Sub-millisecond expiry SLAs — the janitor period only bounds memory reclamation; Get is precise.
  • Durable primary storage where every write must hit disk — snapshots only lower the loss bound to the last successful dump.

Acknowledgments

Built on xsync (Apache License 2.0).

License

Apache License 2.0. Copyright © 2026 bagualing. See NOTICE.

Documentation

Overview

Package ttlmap provides a concurrent generic map with per-entry TTL, backed by xsync.Map.

It targets working sets of thousands to hundreds of thousands of entries; reads and writes are both concurrency-safe. Entries may expire after a duration or never expire (see NoExpiration). Expiry is handled in two layers: read paths such as Get/Has/GetTTL/GetWithTTL/SetTTL/Update lazily evict expired entries, while a background janitor uses an expiry min-heap to clean up only entries that have actually expired (O(expired)), with an O(1) idle tick, instead of a per-second full-table scan.

You must create a map with New (memory-only) or Open (snapshot persistence) and call Close to stop background goroutines promptly. If Close is not called, they stop when the map is garbage collected (see Close); no unsaved snapshot is written on GC. Persistence is disabled by default and must be enabled explicitly; see PersistConfig.

After Set/GetOrSet/Update/MSet, treat the stored value as immutable: if V contains a slice or pointer (e.g. []byte), only the header is copied, and mutating the underlying array from the caller races with Get and snapshot encoding.

Example
package main

import (
	"fmt"
	"time"

	ttlmap "github.com/bagualing/ttlmap_xsync"
)

func main() {
	m := ttlmap.New[string, []byte](30 * time.Second)
	defer m.Close()

	m.Set("session", []byte("payload"), 0) // 0 = map default TTL
	m.Set("config", []byte("cfg"), ttlmap.NoExpiration)

	v, ok := m.Get("session")
	fmt.Println(ok, string(v))
	v, ok = m.Get("config")
	fmt.Println(ok, string(v))
}
Output:
true payload
true cfg
Example (Snapshot)

Example_snapshot demonstrates saving and restoring a snapshot: live entries are encoded to a writer and then restored into a new map from a reader.

package main

import (
	"bytes"
	"fmt"
	"time"

	ttlmap "github.com/bagualing/ttlmap_xsync"
)

func main() {
	var buf bytes.Buffer
	m := ttlmap.New[string, int](time.Minute)
	m.Set("a", 1, time.Hour)
	if err := m.WriteSnapshot(&buf, ttlmap.JSONCodec[string, int]{}); err != nil {
		panic(err)
	}
	m.Close()

	restored, err := ttlmap.Load(&buf, ttlmap.JSONCodec[string, int]{}, time.Minute)
	if err != nil {
		panic(err)
	}
	defer restored.Close()

	v, _ := restored.Get("a")
	fmt.Println(v)
}
Output:
1

Index

Examples

Constants

View Source
const DefaultCleanupInterval = time.Second

DefaultCleanupInterval is the cleanup period used when WithCleanupInterval is not specified.

View Source
const DefaultExpiration time.Duration = 0

DefaultExpiration, when used as the ttl argument to Set/SetTTL/GetOrSet/Update/MSet, means "use the map default TTL".

View Source
const DefaultPersistInterval = 5 * time.Second

DefaultPersistInterval is used when PersistConfig.Interval is 0.

View Source
const NoExpiration time.Duration = -1

NoExpiration, when used as the ttl argument to Set/SetTTL/GetOrSet/Update/MSet, means the entry never expires. Any negative duration is treated the same way. GetTTL and GetWithTTL return NoExpiration for such entries.

Variables

View Source
var (
	ErrPersistenceNotEnabled     = errors.New("ttlmap: persistence not enabled")
	ErrPersistenceAlreadyEnabled = errors.New("ttlmap: persistence already enabled")
	ErrClosed                    = errors.New("ttlmap: map is closed")
	ErrCorruptSnapshot           = errors.New("ttlmap: corrupt snapshot")
	ErrUnsupportedSnapshot       = errors.New("ttlmap: unsupported snapshot version")
	ErrSnapshotTooLarge          = errors.New("ttlmap: snapshot exceeds configured limits")
	ErrSnapshotSync              = errors.New("ttlmap: snapshot published but directory sync failed")
)

Sentinel errors for snapshot persistence.

Functions

This section is empty.

Types

type BinaryCodec

type BinaryCodec[K comparable, V any] struct{}

BinaryCodec encodes and decodes K and V via encoding.BinaryMarshaler / BinaryUnmarshaler. It suits user-defined structs: you decide the binary layout, and the size and speed are determined by the type's implementation.

UnmarshalBinary typically uses a pointer receiver; K and V should be struct value types (e.g. Map[ID, Item]), not pointer types.

func (BinaryCodec[K, V]) MarshalKey

func (BinaryCodec[K, V]) MarshalKey(k K) ([]byte, error)

func (BinaryCodec[K, V]) MarshalValue

func (BinaryCodec[K, V]) MarshalValue(v V) ([]byte, error)

func (BinaryCodec[K, V]) UnmarshalKey

func (BinaryCodec[K, V]) UnmarshalKey(b []byte) (K, error)

func (BinaryCodec[K, V]) UnmarshalValue

func (BinaryCodec[K, V]) UnmarshalValue(b []byte) (V, error)

type Clock

type Clock interface {
	Now() time.Time
}

Clock provides the current time. Tests can inject a fake implementation via WithClock. Now must be concurrency-safe: the janitor and map operations call it from multiple goroutines concurrently, and it must not block.

type Codec

type Codec[K comparable, V any] interface {
	MarshalKey(K) ([]byte, error)
	UnmarshalKey([]byte) (K, error)
	MarshalValue(V) ([]byte, error)
	UnmarshalValue([]byte) (V, error)
}

Codec serializes keys and values for snapshot persistence.

Marshal is called while a snapshot is being written: it must be fast, must not block, and must not call this Map's persistence methods (Save/SaveFile/WriteSnapshot/Close). On the Save/Close path, Marshal holds the persistence mutex, and re-entry deadlocks on that lock; on the WriteSnapshot/SaveFile path it does not hold that lock, and re-entry is infinite recursion (re-entering SaveFile also self-deadlocks against the cross-process file lock it holds). The core constraint is unchanged: Marshal must never re-enter persistence. Writes (Set/Delete etc.) do not deadlock but can make the snapshot being produced inconsistent, so avoid them. Marshal's return value may be retained by the snapshot writer until Save returns; implementations must not reuse the returned buffer across calls. If Unmarshal needs to keep the result, it must copy it out of b.

type JSONCodec

type JSONCodec[K comparable, V any] struct{}

JSONCodec encodes keys and values with encoding/json.

func (JSONCodec[K, V]) MarshalKey

func (JSONCodec[K, V]) MarshalKey(k K) ([]byte, error)

func (JSONCodec[K, V]) MarshalValue

func (JSONCodec[K, V]) MarshalValue(v V) ([]byte, error)

func (JSONCodec[K, V]) UnmarshalKey

func (JSONCodec[K, V]) UnmarshalKey(b []byte) (K, error)

func (JSONCodec[K, V]) UnmarshalValue

func (JSONCodec[K, V]) UnmarshalValue(b []byte) (V, error)

type Map

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

Map is a concurrent map in which each key-value pair may carry a TTL or never expire. It must not be copied after first use; the zero value is invalid — use New or Open.

func Load

func Load[K comparable, V any](r io.Reader, codec Codec[K, V], defaultTTL time.Duration, opts ...Option) (*Map[K, V], error)

Load decodes a snapshot from r and returns a new Map. defaultTTL follows the same rules as New; 0 returns an error instead of panicking. Expired records are skipped; never-expire records are kept. It does not enable persistence; if the result needs to be written to disk, call EnablePersistence or use Open instead.

func New

func New[K comparable, V any](defaultTTL time.Duration, opts ...Option) *Map[K, V]

New creates a Map with default TTL defaultTTL. defaultTTL must be > 0 or NoExpiration (any negative duration is stored as NoExpiration); 0 is invalid and panics. When the ttl argument to Set/SetTTL/GetOrSet/Update/MSet is DefaultExpiration (0), this default is used; a negative ttl means the entry never expires.

func Open

func Open[K comparable, V any](defaultTTL time.Duration, cfg PersistConfig[K, V], opts ...Option) (*Map[K, V], error)

Open creates a Map with snapshot persistence. defaultTTL follows the same rules as New (> 0 or NoExpiration); 0 returns an error instead of panicking. If Path exists, its unexpired records are loaded into the new map (never-expire entries included). A missing file is not an error; a corrupt or incompatible file is. On failure the created map is closed.

func (*Map[K, V]) All

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

All returns an iterator over unexpired key-value pairs. The same caveats as Range apply.

func (*Map[K, V]) Clear

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

Clear deletes all entries. Clear is not atomic: racing with concurrent Set, a few newly written entries may remain.

Note: this deliberately does not clear the expiry heap. Clearing the heap here would race with a concurrent Set's pushHeap — a write completed between "clear the map" and "clear the heap" would have its heap index wrongly removed, leaving that entry unreclaimable by the janitor once it expires. Residual stale heap entries are safe: after popping, the janitor re-reads the map to check for absence, and rebuilds the whole heap when it grows too large.

func (*Map[K, V]) Close

func (m *Map[K, V]) Close() error

Close stops the background goroutines (the janitor and the periodic snapshotter) and waits for them to exit. If persistence is enabled, it then writes a final snapshot and returns the write error (also recorded in PersistErr). Close is idempotent and concurrency-safe.

Note: Close only guarantees that writes completed before it began make it into the final snapshot; writes concurrent with or after Close no longer have a goroutine to flush them. Therefore stop all writes before calling Close/Save; if you keep writing after Close, you must call Save again explicitly to persist the new data. After Close, expired entries are cleaned up only by lazy eviction or DeleteExpired.

func (*Map[K, V]) DefaultTTL

func (m *Map[K, V]) DefaultTTL() time.Duration

DefaultTTL returns the map-level default TTL. A negative value passed to New is normalized to NoExpiration.

func (*Map[K, V]) Delete

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

Delete removes key, whether or not the entry has expired.

func (*Map[K, V]) DeleteExpired

func (m *Map[K, V]) DeleteExpired() int

DeleteExpired deletes all currently expired entries and returns the number deleted. Never-expire entries are not deleted.

func (*Map[K, V]) EnablePersistence

func (m *Map[K, V]) EnablePersistence(cfg PersistConfig[K, V]) error

EnablePersistence turns on snapshot persistence for an existing map. It does not read Path: use Open (or Load) for restore. If the map already contains entries, it marks dirty so the first periodic/Save write persists them.

It should be called before concurrent use. After Close it returns ErrClosed.

func (*Map[K, V]) Get

func (m *Map[K, V]) Get(key K) (V, bool)

Get returns the unexpired value for key. Never-expire entries are always live. A live hit is lock-free; an expired entry is deleted (taking the bucket lock) and treated as absent.

func (*Map[K, V]) GetOrSet

func (m *Map[K, V]) GetOrSet(key K, value V, ttl time.Duration) (V, bool)

GetOrSet atomically returns the unexpired value for key; if key is missing or expired, it stores value and returns it. The returned loaded matches sync.Map.LoadOrStore: true means an existing value was hit, false means this call performed the write.

Note: value is evaluated before the call, so GetOrSet only guarantees the atomicity of the write, not that "the same value is computed only once". For true single-flight computation, deduplicate before calling.

Example
package main

import (
	"fmt"
	"time"

	ttlmap "github.com/bagualing/ttlmap_xsync"
)

func main() {
	m := ttlmap.New[string, int](time.Minute)
	defer m.Close()

	// First write (loaded=false means this call performed the store).
	v, loaded := m.GetOrSet("hits", 1, time.Minute)
	fmt.Println(v, loaded)

	// Hit the existing value (loaded=true).
	v, loaded = m.GetOrSet("hits", 99, time.Minute)
	fmt.Println(v, loaded)
}
Output:
1 false
1 true

func (*Map[K, V]) GetTTL

func (m *Map[K, V]) GetTTL(key K) (time.Duration, bool)

GetTTL returns the remaining TTL of an unexpired entry. Never-expire entries return NoExpiration. Missing or expired keys return ok=false; expired entries are deleted.

func (*Map[K, V]) GetWithTTL

func (m *Map[K, V]) GetWithTTL(key K) (V, time.Duration, bool)

GetWithTTL returns the unexpired value for key along with its remaining TTL. Never-expire entries return remaining = NoExpiration. Expired entries are deleted.

func (*Map[K, V]) Has

func (m *Map[K, V]) Has(key K) bool

Has reports whether key has an unexpired value (including never-expire). Expired entries are deleted as a side effect.

func (*Map[K, V]) LoadAndDelete

func (m *Map[K, V]) LoadAndDelete(key K) (V, bool)

LoadAndDelete removes key, returning its value if it previously existed and was unexpired.

func (*Map[K, V]) MGet

func (m *Map[K, V]) MGet(keys ...K) map[K]V

MGet reads unexpired values for multiple keys. It is not a globally atomic snapshot and does not delete expired entries.

func (*Map[K, V]) MSet

func (m *Map[K, V]) MSet(values map[K]V, ttl time.Duration)

MSet writes multiple values with the same TTL (interpreted as in Set). It is a convenience wrapper (sharing one now/deadline computation and one dirty mark), but underneath it is still a per-key Store: it is not globally atomic.

func (*Map[K, V]) Peek

func (m *Map[K, V]) Peek(key K) (V, bool)

Peek returns the unexpired value for key (including never-expire entries) but does not delete the entry on expiry. The read is lock-free.

func (*Map[K, V]) PersistErr

func (m *Map[K, V]) PersistErr() error

PersistErr returns the most recent snapshot write error, if any.

func (*Map[K, V]) PersistenceEnabled

func (m *Map[K, V]) PersistenceEnabled() bool

PersistenceEnabled reports whether Open or EnablePersistence has succeeded.

func (*Map[K, V]) Range

func (m *Map[K, V]) Range(f func(key K, value V) bool)

Range calls f for each unexpired entry, including never-expire entries. Expired entries are skipped and are not deleted. Iteration stops when f returns false. xsync.Range copies each bucket then invokes the callback without holding the bucket lock, so f may safely call other methods of this Map (concurrent Sets during the iteration may not be reflected in this pass).

func (*Map[K, V]) Save

func (m *Map[K, V]) Save() error

Save writes a snapshot to the configured Path even if the map is not dirty. If persistence was never enabled it returns ErrPersistenceNotEnabled.

func (*Map[K, V]) SaveFile

func (m *Map[K, V]) SaveFile(path string, codec Codec[K, V]) error

SaveFile atomically writes a snapshot to path. Persistence need not be enabled. The parent directory must already exist. It fsyncs the file and the parent directory (unlike Save with PersistConfig.NoSync). If persistence is already configured, use Save.

func (*Map[K, V]) Set

func (m *Map[K, V]) Set(key K, value V, ttl time.Duration)

Set stores value under key. ttl semantics: 0 / DefaultExpiration uses the map default TTL; a negative ttl / NoExpiration means never expire; ttl > 0 expires ttl from now. Do not mutate value's underlying data after Set.

func (*Map[K, V]) SetIfAbsent

func (m *Map[K, V]) SetIfAbsent(key K, value V, ttl time.Duration) bool

SetIfAbsent stores a value only if key is absent or expired, and reports whether a write occurred.

func (*Map[K, V]) SetTTL

func (m *Map[K, V]) SetTTL(key K, ttl time.Duration) bool

SetTTL replaces the TTL of an existing unexpired entry. It returns false if key is absent or expired (an expired entry is deleted). ttl is interpreted the same way as in Set.

func (*Map[K, V]) Size

func (m *Map[K, V]) Size() int

Size returns the number of stored entries (including expired ones not yet cleaned up). For the unexpired count, call DeleteExpired first.

func (*Map[K, V]) Update

func (m *Map[K, V]) Update(key K, ttl time.Duration, fn func(V) V) (v V, ok bool)

Update atomically performs one read-modify-write on key's current value: if it exists and is unexpired, the value is replaced with the result of fn(old value), the TTL is refreshed, and the new value and true are returned; otherwise the expired entry is deleted and the zero value and false are returned. fn runs under xsync's bucket lock: it must be fast, must not block, and must not call any method of this Map (that would re-enter and deadlock). If fn panics, the lock is released and this write is abandoned (the panic propagates unchanged), without blocking subsequent operations.

func (*Map[K, V]) WriteSnapshot

func (m *Map[K, V]) WriteSnapshot(w io.Writer, codec Codec[K, V]) error

WriteSnapshot encodes the unexpired entries to w, including never-expire entries. Persistence need not be enabled. Expired entries are skipped. The stream is a complete snapshot, not an incremental merge.

type Option

type Option func(*options)

Option configures a Map.

func WithCapacity

func WithCapacity(sizeHint int) Option

WithCapacity hints the initial capacity, which is passed to xsync.WithPresize. It is only a lower bound on capacity: it does not limit the number of entries nor trigger capacity-based eviction. A value <= 0 keeps the default.

func WithCleanupInterval

func WithCleanupInterval(d time.Duration) Option

WithCleanupInterval sets the background janitor's scan period. A value <= 0 disables the janitor, leaving expired entries to be handled only by lazy eviction or DeleteExpired.

func WithClock

func WithClock(c Clock) Option

WithClock replaces the time source used for expiry computation. A nil c is ignored.

type PersistConfig

type PersistConfig[K comparable, V any] struct {
	// Path is the snapshot file path. Its parent directory must already exist. Required.
	Path string
	// Interval is the interval between dirty-snapshot writes. 0 means DefaultPersistInterval
	// (5s); a negative value disables periodic writes, taking a snapshot only on Save and Close.
	Interval time.Duration
	// Codec serializes keys and values. Required.
	Codec Codec[K, V]
	// NoSync skips both the file fsync and the parent-directory fsync. Faster, but more likely
	// to lose data on host crash.
	NoSync bool
	// OnError is called asynchronously when a periodic snapshot write fails (errors from
	// Close/Save are returned by their return values). It must not block for long. May be nil.
	// The callback runs in a separate goroutine; re-entering the same Map (including Close) is
	// safe, but on concurrent failures it may be skipped.
	OnError func(error)
}

PersistConfig enables snapshot persistence for a Map. Unless you use Open or EnablePersistence, persistence stays disabled.

type StringBinaryCodec

type StringBinaryCodec[V any] struct{}

StringBinaryCodec uses raw string keys and BinaryMarshaler values. It corresponds to the most common Map[string, struct] shape.

func (StringBinaryCodec[V]) MarshalKey

func (StringBinaryCodec[V]) MarshalKey(k string) ([]byte, error)

func (StringBinaryCodec[V]) MarshalValue

func (StringBinaryCodec[V]) MarshalValue(v V) ([]byte, error)

func (StringBinaryCodec[V]) UnmarshalKey

func (StringBinaryCodec[V]) UnmarshalKey(b []byte) (string, error)

func (StringBinaryCodec[V]) UnmarshalValue

func (StringBinaryCodec[V]) UnmarshalValue(b []byte) (V, error)

type StringBytesCodec

type StringBytesCodec struct{}

StringBytesCodec encodes a Map[string, []byte] as raw bytes with no extra framing. It copies values to avoid the snapshot aliasing the caller's buffer.

func (StringBytesCodec) MarshalKey

func (StringBytesCodec) MarshalKey(k string) ([]byte, error)

func (StringBytesCodec) MarshalValue

func (StringBytesCodec) MarshalValue(v []byte) ([]byte, error)

func (StringBytesCodec) UnmarshalKey

func (StringBytesCodec) UnmarshalKey(b []byte) (string, error)

func (StringBytesCodec) UnmarshalValue

func (StringBytesCodec) UnmarshalValue(b []byte) ([]byte, error)

Jump to

Keyboard shortcuts

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