redka

package module
v0.0.0-...-85961ca Latest Latest
Warning

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

Go to latest
Published: Mar 12, 2025 License: BSD-3-Clause Imports: 13 Imported by: 0

README

Redka

Redka aims to reimplement the core parts of Redis with SQLite, while remaining compatible with Redis API.

Notable features:

  • Data does not have to fit in RAM.
  • ACID transactions.
  • SQL views for better introspection and reporting.
  • Both in-process (Go API) and standalone (RESP) servers.
  • Redis-compatible commands and wire protocol.
  • Support for both SQLite and PostgreSQL backends.

Redka is functionally ready for 1.0. Feel free to try it in non-critical production scenarios and provide feedback in the issues.

Commands

Redka supports five core Redis data types:

  • Strings are the most basic Redis type, representing a sequence of bytes.
  • Lists are sequences of strings sorted by insertion order.
  • Sets are unordered collections of unique strings.
  • Hashes are field-value (hash)maps.
  • Sorted sets (zsets) are collections of unique strings ordered by each string's associated score.

Redka also provides commands for key management, server/connection management, and transactions.

Installation and usage

Redka comes in two flavors:

Database Backend

Redka supports two database backends:

  • SQLite (default): Great for single-node deployments, embedded applications, and when you need a self-contained database file.
  • PostgreSQL: Ideal for distributed setups, when you need better concurrency, or want to leverage PostgreSQL's robust features.

Using PostgreSQL

To use PostgreSQL instead of SQLite:

import (
    "github.com/flarco/redka"
    _ "github.com/lib/pq"
)

func main() {
    // PostgreSQL connection string
    connStr := "host=localhost port=5432 user=postgres password=postgres dbname=redka sslmode=disable"
    
    // Create options with PostgreSQL driver
    opts := &redka.Options{
        DriverName: "postgres",
    }
    
    // Open the database
    db, err := redka.Open(connStr, opts)
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    // Use the same Redka API as with SQLite
    err = db.Str().Set("name", "alice")
    // ...
}

For testing with PostgreSQL, you can use the included Docker Compose configuration:

# Start PostgreSQL
docker-compose up -d postgres

# Run tests
go test -v ./... -tags=postgres

Performance

According to the benchmarks, Redka is several times slower than Redis. Still, it can do up to 100K op/sec on a Macbook Air, which is pretty good if you ask me (and probably 10x more than most applications will ever need).

Redka stores data in a SQLite database with a simple schema and provides views for better introspection. With PostgreSQL backend, you get improved concurrency at the cost of slightly lower raw performance.

Contributing

Contributions are welcome. For anything other than bugfixes, please first open an issue to discuss what you want to change.

Be sure to add or update tests as appropriate.

Acknowledgements

Redka would not be possible without these great projects and their creators:

Logo font by Ek Type.

Support

Redka is mostly a one-man project, not backed by a VC fund or anything.

If you find Redka useful, please star it on GitHub and spread the word among your peers. It really helps to move the project forward.

If you use Redka for commercial purposes, consider purchasing support.

Subscribe to stay on top of new features.

Documentation

Overview

Package Redka implements Redis-like database backed by SQLite. It provides an API to interact with data structures like keys, strings and hashes.

Typically, you open a database with Open and use the returned DB instance methods like DB.Key or DB.Str to access the data structures. You should only use one instance of DB throughout your program and close it with DB.Close when the program exits.

Index

Examples

Constants

View Source
const (
	TypeAny    = core.TypeAny
	TypeString = core.TypeString
	TypeList   = core.TypeList
	TypeSet    = core.TypeSet
	TypeHash   = core.TypeHash
	TypeZSet   = core.TypeZSet
)

Variables

View Source
var (
	ErrKeyType   = core.ErrKeyType   // key type mismatch
	ErrNotFound  = core.ErrNotFound  // key or element not found
	ErrValueType = core.ErrValueType // invalid value type
)

Common errors returned by data structure methods.

Functions

This section is empty.

Types

type DB

type DB struct {
	*sqlx.DB[*Tx]
	// contains filtered or unexported fields
}

DB is a Redis-like database backed by SQLite. Provides access to data structures like keys, strings, and hashes.

DB is safe for concurrent use by multiple goroutines as long as you use a single instance of DB throughout your program.

func Open

func Open(path string, opts *Options) (*DB, error)

Open opens a new or existing database at the given path. Creates the database schema if necessary.

For SQLite, the path is a file path or ":memory:" for an in-memory database. For PostgreSQL, the path should be a PostgreSQL connection string.

The returned DB is safe for concurrent use by multiple goroutines as long as you use a single instance throughout your program. Typically, you only close the DB when the program exits.

The opts parameter is optional. If nil, uses default options.

Example
package main

import (
	"github.com/flarco/redka"
)

func main() {
	db, err := redka.Open("file:/data.db?vfs=memdb", nil)
	if err != nil {
		panic(err)
	}
	defer db.Close()
	// ...
}

func OpenDB

func OpenDB(rw *sql.DB, ro *sql.DB, opts *Options) (*DB, error)

OpenDB connects to an existing SQL database. Creates the database schema if necessary. The opts parameter is optional. If nil, uses default options.

func OpenRead

func OpenRead(path string, opts *Options) (*DB, error)

OpenRead opens an existing database at the given path in read-only mode.

Example
package main

import (
	"fmt"

	"github.com/flarco/redka"
)

func main() {
	// open a writable database
	db, err := redka.Open("data.db", nil)
	if err != nil {
		panic(err)
	}
	_ = db.Str().Set("name", "alice")
	db.Close()

	// open a read-only database
	db, err = redka.OpenRead("data.db", nil)
	if err != nil {
		panic(err)
	}
	// read operations work fine
	name, _ := db.Str().Get("name")
	fmt.Println(name)
	// write operations will fail
	err = db.Str().Set("name", "bob")
	fmt.Println(err)
	// attempt to write a readonly database
	db.Close()

}
Output:

alice
attempt to write a readonly database

func OpenReadDB

func OpenReadDB(db *sql.DB, opts *Options) (*DB, error)

OpenReadDB connects to an existing SQL database in read-only mode.

func (*DB) Close

func (db *DB) Close() error

Close closes the database. It's safe for concurrent use by multiple goroutines.

Example
package main

import (
	"github.com/flarco/redka"
)

func main() {
	db, err := redka.Open("file:/data.db?vfs=memdb", nil)
	if err != nil {
		panic(err)
	}
	defer db.Close()
	// ...
}

func (*DB) Hash

func (db *DB) Hash() *rhash.DB

Hash returns the hash repository. A hash (hashmap) is a field-value map associated with a key. Use the hash repository to work with individual hashmaps and their fields.

Example
package main

import (
	"fmt"

	"github.com/flarco/redka"
)

func main() {
	// Error handling is omitted for brevity.
	// In real code, always check for errors.

	db, _ := redka.Open("file:/data.db?vfs=memdb", nil)
	defer db.Close()

	ok, err := db.Hash().Set("user:1", "name", "alice")
	fmt.Printf("ok=%v, err=%v\n", ok, err)
	ok, err = db.Hash().Set("user:1", "age", 25)
	fmt.Printf("ok=%v, err=%v\n", ok, err)

	name, err := db.Hash().Get("user:1", "name")
	fmt.Printf("name=%v, err=%v\n", name, err)
	age, err := db.Hash().Get("user:1", "age")
	fmt.Printf("age=%v, err=%v\n", age, err)

}
Output:

ok=true, err=<nil>
ok=true, err=<nil>
name=alice, err=<nil>
age=25, err=<nil>

func (*DB) Key

func (db *DB) Key() *rkey.DB

Key returns the key repository. A key is a unique identifier for a data structure (string, list, hash, etc.). Use the key repository to manage all keys regardless of their type.

Example
package main

import (
	"fmt"
	"time"

	"github.com/flarco/redka"
)

func main() {
	// Error handling is omitted for brevity.
	// In real code, always check for errors.

	db, _ := redka.Open("file:/data.db?vfs=memdb", nil)
	defer db.Close()

	_ = db.Str().SetExpires("name", "alice", 60*time.Second)
	_ = db.Str().Set("city", "paris")

	key, _ := db.Key().Get("name")
	fmt.Printf("key=%v, type=%v, version=%v, exists=%v\n",
		key.Key, key.TypeName(), key.Version, key.Exists())

	key, _ = db.Key().Get("nonexistent")
	fmt.Printf("key=%v, type=%v, version=%v, exists=%v\n",
		key.Key, key.TypeName(), key.Version, key.Exists())

	scan, _ := db.Key().Scan(0, "*", redka.TypeString, 100)
	fmt.Print("keys:")
	for _, key := range scan.Keys {
		fmt.Print(" ", key.Key)
	}
	fmt.Println()

}
Output:

key=name, type=string, version=1, exists=true
key=, type=unknown, version=0, exists=false
keys: name city

func (*DB) List

func (db *DB) List() *rlist.DB

List returns the list repository. A list is a sequence of strings ordered by insertion order. Use the list repository to work with lists and their elements.

func (*DB) Set

func (db *DB) Set() *rset.DB

Set returns the set repository. A set is an unordered collection of unique strings. Use the set repository to work with individual sets and their elements, and to perform set operations.

func (*DB) Str

func (db *DB) Str() *rstring.DB

Str returns the string repository. A string is a slice of bytes associated with a key. Use the string repository to work with individual strings.

Example
package main

import (
	"fmt"

	"github.com/flarco/redka"
)

func main() {
	// Error handling is omitted for brevity.
	// In real code, always check for errors.

	db, _ := redka.Open("file:/data.db?vfs=memdb", nil)
	defer db.Close()

	_ = db.Str().Set("name", "alice")

	name, _ := db.Str().Get("name")
	fmt.Printf("name=%v\n", name)

	name, _ = db.Str().Get("nonexistent")
	fmt.Printf("name=%v\n", name)

}
Output:

name=alice
name=

func (*DB) Update

func (db *DB) Update(f func(tx *Tx) error) error

Update executes a function within a writable transaction. See the tx example for details.

Example
package main

import (
	"fmt"

	"github.com/flarco/redka"
)

func main() {
	db, err := redka.Open("file:/data.db?vfs=memdb", nil)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	updCount := 0
	err = db.Update(func(tx *redka.Tx) error {
		err := tx.Str().Set("name", "alice")
		if err != nil {
			return err
		}
		updCount++

		err = tx.Str().Set("age", 25)
		if err != nil {
			return err
		}
		updCount++

		return nil
	})
	fmt.Printf("updated: count=%v, err=%v\n", updCount, err)

}
Output:

updated: count=2, err=<nil>

func (*DB) UpdateContext

func (db *DB) UpdateContext(ctx context.Context, f func(tx *Tx) error) error

UpdateContext executes a function within a writable transaction. See the tx example for details.

func (*DB) View

func (db *DB) View(f func(tx *Tx) error) error

View executes a function within a read-only transaction. See the tx example for details.

Example
package main

import (
	"fmt"

	"github.com/flarco/redka"
)

func main() {
	// Error handling is omitted for brevity.
	// In real code, always check for errors.

	db, _ := redka.Open("file:/data.db?vfs=memdb", nil)
	defer db.Close()

	_ = db.Str().SetMany(map[string]any{
		"name": "alice",
		"age":  25,
	})

	type person struct {
		name string
		age  int
	}

	var p person
	err := db.View(func(tx *redka.Tx) error {
		name, err := tx.Str().Get("name")
		if err != nil {
			return err
		}
		p.name = name.String()

		age, err := tx.Str().Get("age")
		if err != nil {
			return err
		}
		// Only use MustInt() if you are sure that
		// the key exists and is an integer.
		p.age = age.MustInt()
		return nil
	})
	fmt.Printf("person=%+v, err=%v\n", p, err)

}
Output:

person={name:alice age:25}, err=<nil>

func (*DB) ViewContext

func (db *DB) ViewContext(ctx context.Context, f func(tx *Tx) error) error

ViewContext executes a function within a read-only transaction. See the tx example for details.

func (*DB) ZSet

func (db *DB) ZSet() *rzset.DB

ZSet returns the sorted set repository. A sorted set (zset) is a like a set, but each element has a score, and elements are ordered by score from low to high. Use the sorted set repository to work with individual sets and their elements, and to perform set operations.

Example
package main

import (
	"fmt"

	"github.com/flarco/redka"
)

func main() {
	// Error handling is omitted for brevity.
	// In real code, always check for errors.

	db, _ := redka.Open("file:/data.db?vfs=memdb", nil)
	defer db.Close()

	ok, err := db.ZSet().Add("race", "alice", 11)
	fmt.Printf("ok=%v, err=%v\n", ok, err)
	ok, err = db.ZSet().Add("race", "bob", 22)
	fmt.Printf("ok=%v, err=%v\n", ok, err)

	rank, score, err := db.ZSet().GetRank("race", "alice")
	fmt.Printf("alice: rank=%v, score=%v, err=%v\n", rank, score, err)

	rank, score, err = db.ZSet().GetRank("race", "bob")
	fmt.Printf("bob: rank=%v, score=%v, err=%v\n", rank, score, err)

}
Output:

ok=true, err=<nil>
ok=true, err=<nil>
alice: rank=0, score=11, err=<nil>
bob: rank=1, score=22, err=<nil>

type Key

type Key = core.Key

Key represents a key data structure. Each key uniquely identifies a data structure stored in the database (e.g. a string, a list, or a hash). There can be only one data structure with a given key, regardless of type. For example, you can't have a string and a hash map with the same key.

type Options

type Options struct {
	// SQL driver name.
	// If empty, uses "sqlite3".
	// Use "postgres" for PostgreSQL connections.
	DriverName string

	// SQL pragmas to set on the database connection.
	// If nil, uses the default pragmas:
	//  - journal_mode=wal
	//  - synchronous=normal
	//  - temp_store=memory
	//  - mmap_size=268435456
	//  - foreign_keys=on
	//
	// Note: Pragmas only apply to SQLite connections.
	Pragma map[string]string

	// Logger for the database. If nil, uses a silent logger.
	Logger *slog.Logger
	// contains filtered or unexported fields
}

Options is the configuration for the database.

type Tx

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

Tx is a Redis-like database transaction. Same as DB, Tx provides access to data structures like keys, strings, and hashes. The difference is that you call Tx methods within a transaction managed by DB.Update or DB.View.

See the tx example for details.

func (*Tx) Hash

func (tx *Tx) Hash() *rhash.Tx

Hash returns the hash transaction.

func (*Tx) Key

func (tx *Tx) Key() *rkey.Tx

Keys returns the key transaction.

func (*Tx) List

func (tx *Tx) List() *rlist.Tx

List returns the list transaction.

func (*Tx) Set

func (tx *Tx) Set() *rset.Tx

Set returns the set transaction.

func (*Tx) Str

func (tx *Tx) Str() *rstring.Tx

Str returns the string transaction.

func (*Tx) ZSet

func (tx *Tx) ZSet() *rzset.Tx

ZSet returns the sorted set transaction.

type TypeID

type TypeID = core.TypeID

A TypeID identifies the type of the key and thus the data structure of the value with that key.

type Value

type Value = core.Value

Value represents a value stored in a database (a byte slice). It can be converted to other scalar types.

Directories

Path Synopsis
cmd
cli command
Redka CLI.
Redka CLI.
redka command
Redka server.
Redka server.
internal
command
Package command implements Redis-compatible commands for operations on data structures.
Package command implements Redis-compatible commands for operations on data structures.
core
Package core provides the core types used by other Redka packages.
Package core provides the core types used by other Redka packages.
parser
Package parser implements command arguments parsing.
Package parser implements command arguments parsing.
redis
Package redis implements basis for Redis-compatible commands in Redka.
Package redis implements basis for Redis-compatible commands in Redka.
rhash
Package rhash is a database-backed hash repository.
Package rhash is a database-backed hash repository.
rkey
Package rkey is a database-backed key repository.
Package rkey is a database-backed key repository.
rlist
Package rlist is a database-backed list repository.
Package rlist is a database-backed list repository.
rset
Package rset is a database-backed set repository.
Package rset is a database-backed set repository.
rstring
Package rstring is a database-backed string repository.
Package rstring is a database-backed string repository.
rzset
Package rzset is a database-backed sorted set repository.
Package rzset is a database-backed sorted set repository.
server
Package server implements a Redis-compatible (RESP) server.
Package server implements a Redis-compatible (RESP) server.
sqlx
Package sqlx provides base types and helper functions to work with SQL databases.
Package sqlx provides base types and helper functions to work with SQL databases.
testx
Package testx provides helper functions for testing.
Package testx provides helper functions for testing.

Jump to

Keyboard shortcuts

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