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

appendstore

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 16 Imported by: 0

README

appendstore

appendstore is a small, embeddable append-only key/value store for Go. It provides crash recovery, checksummed records, optional in-memory secondary indexes, an LRU value cache, and atomic compaction using only the Go standard library.

The module is intended for applications that need a durable local store owned by one process. It is not a distributed database or a transactional SQL engine.

Install

go get github.com/sirrobot01/appendstore

Usage

package main

import (
	"fmt"
	"log"

	"github.com/sirrobot01/appendstore"
)

func main() {
	store, err := appendstore.Open("records.db", appendstore.Options{
		IndexedFields: []string{"kind"},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer store.Close()

	err = store.Put("greeting", []byte("hello"), &appendstore.PutOptions{
		Attributes: map[string]string{"kind": "message"},
	})
	if err != nil {
		log.Fatal(err)
	}

	value, err := store.Get("greeting")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(value))
	fmt.Println(store.KeysBy("kind", "message"))
}

Process model

Only one process may open a database path at a time. Open acquires an exclusive operating-system lock and returns ErrStoreLocked when another process owns the store. The adjacent .lock file remains after Close by design; the operating-system lock, not the file's presence, indicates ownership. Locks are released automatically when a process exits.

Calls on one Store are safe for concurrent use by multiple goroutines.

Durability

Each mutation is appended immediately. Options.SyncInterval controls when data is synchronized to durable storage:

  • 0 synchronizes every mutation.
  • A positive duration synchronizes periodically.
  • A negative duration disables automatic synchronization, including on Close; call Sync explicitly when needed.

On startup, appendstore rebuilds its indexes, discards an incomplete trailing record, and rejects checksum corruption. Compaction writes and synchronizes a replacement before atomically installing it. Compaction blocks all reads and writes until the replacement is installed; the pause grows with the size of the live data.

Only version 4 logs are supported. Open returns ErrUnsupportedVersion for version 1–3 logs; open them once with appendstore v0.1.x to migrate them.

Limits

  • All keys and index metadata stay in memory. The store is not suitable for keyspaces that exceed available RAM.
  • Each value is encoded in one in-memory buffer on write and read. Very large values increase memory use accordingly (hard limit: 2 GiB per value).
  • Secondary indexes support exact-match equality only; there are no range or prefix queries.

Status

The on-disk format is versioned, but the public API should be considered pre-1.0. Back up important data before upgrading between early releases.

Documentation

Overview

Package appendstore provides a high-performance append-only log storage engine with in-memory indexing, LRU caching, and secondary indexes.

Architecture:

  • Append-only log file for durability
  • In-memory primary index for O(1) key lookups
  • LRU cache for frequently accessed entries
  • Memory-mapped reads with a file-read fallback
  • Optional secondary indexes over application-defined attributes
  • Background compaction to reclaim deleted space

Thread Safety:

  • All operations are thread-safe via RWMutex
  • Reads can proceed concurrently
  • Writes are serialized
  • Open acquires an exclusive process lock for the database path

Durability:

  • All writes are appended to the log immediately
  • Index is rebuilt from log on startup (crash recovery)
  • Optional periodic sync for fsync guarantees
Example
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/sirrobot01/appendstore"
)

func main() {
	dir, err := os.MkdirTemp("", "appendstore-example-")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	store, err := appendstore.Open(filepath.Join(dir, "example.db"), appendstore.Options{
		IndexedFields: []string{"kind"},
	})
	if err != nil {
		panic(err)
	}
	defer store.Close()

	if err := store.Put("greeting", []byte("hello"), &appendstore.PutOptions{
		Attributes: map[string]string{"kind": "message"},
	}); err != nil {
		panic(err)
	}
	value, err := store.Get("greeting")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(value))
	fmt.Println(store.KeysBy("kind", "message"))

}
Output:
hello
[greeting]

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrStoreClosed          = errors.New("store is closed")
	ErrStoreLocked          = errors.New("store is already open by another process")
	ErrCorruptedData        = errors.New("corrupted data detected")
	ErrUnsupportedVersion   = errors.New("unsupported log version")
	ErrCompactionInProgress = errors.New("compaction already in progress")
	ErrKeyNotFound          = errors.New("key not found")
	ErrInvalidKey           = errors.New("invalid key")
	ErrInvalidAttribute     = errors.New("invalid attribute name")
	ErrValueTooLarge        = errors.New("value too large")
	ErrAttributesTooLarge   = errors.New("attributes too large")
)

Common errors

Functions

This section is empty.

Types

type Metadata

type Metadata struct {
	Attributes map[string]string
}

Metadata is the application-defined metadata associated with a value. Its Attributes map is always a defensive copy owned by the caller.

func (Metadata) Attribute

func (m Metadata) Attribute(name string) string

Attribute returns one metadata attribute.

type Options

type Options struct {
	// CacheSize is the maximum number of entries to cache (default: 1000)
	CacheSize int

	// SyncInterval controls automatic fsync behavior: 0 syncs every mutation,
	// a positive duration syncs periodically, and a negative duration disables
	// automatic syncing (including on Close). Sync can always be called explicitly.
	SyncInterval time.Duration

	// CompactionThreshold is the deleted entry ratio that triggers compaction (default: 0.2)
	CompactionThreshold float64

	// AutoCompact enables automatic background compaction
	AutoCompact bool

	// IndexedFields lists attribute names that should have in-memory secondary
	// indexes. Only these fields can be queried with KeysBy efficiently.
	IndexedFields []string

	// OnError receives errors from background sync and compaction. It is never
	// called while the store mutex is held.
	OnError func(error)
}

Options configures a Store.

type PutOptions

type PutOptions struct {
	Attributes map[string]string
}

PutOptions describes application-defined metadata for a value.

type Stats

type Stats struct {
	Writes      int64
	Reads       int64
	CacheHits   int64
	CacheMisses int64
	Deletes     int64
	Compactions int64
}

Stats is an instantaneous snapshot of operational counters. Reads counts every value returned by Get and ForEach, whether served from the cache or from disk; CacheHits and CacheMisses partition the Get calls.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is an append-only storage engine.

func Open

func Open(path string, options Options) (*Store, error)

Open opens or creates the append-only store at path.

func (*Store) AttributeValues

func (s *Store) AttributeValues(field string) []string

AttributeValues returns the sorted values present for an indexed field.

func (*Store) Close

func (s *Store) Close() error

Close shuts down the store gracefully

func (*Store) Compact

func (s *Store) Compact() error

Compact removes deleted entries and rewrites the log

func (*Store) CountBy

func (s *Store) CountBy(field, value string) int

CountBy returns the number of keys matching an indexed attribute value.

func (*Store) Delete

func (s *Store) Delete(key string) error

Delete removes a key

func (*Store) DiskSize

func (s *Store) DiskSize() int64

DiskSize returns the current log file size

func (*Store) Exists

func (s *Store) Exists(key string) bool

Exists checks if a key exists (O(1) from index)

func (*Store) ForEach

func (s *Store) ForEach(fn func(key string, value []byte) error) error

ForEach iterates over all entries in disk order (optimized for sequential I/O). The value passed to fn is owned by ForEach and only valid for the duration of that call — callers that retain it must copy.

Scans deliberately bypass the LRU cache: a full pass touches every entry once and never reuses them, so caching would evict the genuinely-hot working set (active streams) and replace it with single-use scan data. Each value is read under a short RLock so writers interleave between keys and reads observe the current log even across a compaction swap. A single reusable buffer keeps the scan allocation-free per record.

func (*Store) ForEachMetadata

func (s *Store) ForEachMetadata(fn func(key string, meta *Metadata) error) error

ForEachMetadata iterates over a snapshot of metadata only (no disk reads).

func (*Store) Get

func (s *Store) Get(key string) ([]byte, error)

Get retrieves a value by key

func (*Store) GetMetadata

func (s *Store) GetMetadata(key string) (*Metadata, error)

GetMetadata retrieves metadata without reading the full value. This is O(1) from the in-memory index - no disk access

func (*Store) Keys

func (s *Store) Keys() []string

Keys returns all keys (snapshot)

func (*Store) KeysBy

func (s *Store) KeysBy(field, value string) []string

KeysBy returns keys with an indexed attribute equal to value.

func (*Store) Len

func (s *Store) Len() int

Len returns the number of entries

func (*Store) MemoryUsage

func (s *Store) MemoryUsage() int64

MemoryUsage returns approximate in-memory usage

func (*Store) NeedsCompaction

func (s *Store) NeedsCompaction() bool

NeedsCompaction returns true if compaction would reclaim significant space

func (*Store) Put

func (s *Store) Put(key string, value []byte, options *PutOptions) error

Put stores a key-value pair with optional application-defined attributes.

func (*Store) Stats

func (s *Store) Stats() Stats

Stats returns an instantaneous snapshot of operational counters.

func (*Store) Sync

func (s *Store) Sync() error

Sync flushes all appended records to stable storage.

Jump to

Keyboard shortcuts

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