Skip to content

Commit

Permalink
wip datastore
Browse files Browse the repository at this point in the history
  • Loading branch information
ecordell committed Dec 2, 2021
1 parent d82bb4e commit b56ba37
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 56 deletions.
72 changes: 44 additions & 28 deletions internal/datastore/crdb/options.go
Expand Up @@ -2,6 +2,7 @@ package crdb

import (
"fmt"
"github.com/authzed/spicedb/internal/datastore"
"time"

"github.com/alecthomas/units"
Expand All @@ -26,6 +27,9 @@ type crdbOptions struct {
overlapKey string
}

// Option satisfies the DatastoreOptions interface
func (o *crdbOptions) Option() {}

const (
errQuantizationTooLarge = "revision quantization (%s) must be less than GC window (%s)"

Expand All @@ -45,7 +49,19 @@ const (

// Option provides the facility to configure how clients within the CRDB
// datastore interact with the running CockroachDB database.
type Option func(*crdbOptions)
type Option interface {
apply(datastore.DatastoreOptions)
}

type OptionFunc func(options *crdbOptions)

func (f OptionFunc) apply(o datastore.DatastoreOptions) {
co, ok := o.(*crdbOptions)
if !ok {
panic("wrong type of option")
}
f(co)
}

func generateConfig(options []Option) (crdbOptions, error) {
computed := crdbOptions{
Expand All @@ -61,7 +77,7 @@ func generateConfig(options []Option) (crdbOptions, error) {
}

for _, option := range options {
option(&computed)
option.apply(&computed)
}

// Run any checks on the config that need to be done
Expand All @@ -81,29 +97,29 @@ func generateConfig(options []Option) (crdbOptions, error) {
//
// This value defaults to `common.DefaultSplitAtEstimatedQuerySize`.
func SplitAtEstimatedQuerySize(splitAtEstimatedQuerySize units.Base2Bytes) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.splitAtEstimatedQuerySize = splitAtEstimatedQuerySize
}
})
}

// ConnMaxIdleTime is the duration after which an idle connection will be
// automatically closed by the health check.
//
// This value defaults to having no maximum.
func ConnMaxIdleTime(idle time.Duration) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.connMaxIdleTime = &idle
}
})
}

// ConnMaxLifetime is the duration since creation after which a connection will
// be automatically closed.
//
// This value defaults to having no maximum.
func ConnMaxLifetime(lifetime time.Duration) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.connMaxLifetime = &lifetime
}
})
}

// MinOpenConns is the minimum size of the connection pool.
Expand All @@ -112,47 +128,47 @@ func ConnMaxLifetime(lifetime time.Duration) Option {
//
// This value defaults to zero.
func MinOpenConns(conns int) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.minOpenConns = &conns
}
})
}

// MaxOpenConns is the maximum size of the connection pool.
//
// This value defaults to having no maximum.
func MaxOpenConns(conns int) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.maxOpenConns = &conns
}
})
}

// WatchBufferLength is the number of entries that can be stored in the watch
// buffer while awaiting read by the client.
//
// This value defaults to 128.
func WatchBufferLength(watchBufferLength uint16) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.watchBufferLength = watchBufferLength
}
})
}

// RevisionQuantization is the time bucket size to which advertised revisions
// will be rounded.
//
// This value defaults to 5 seconds.
func RevisionQuantization(bucketSize time.Duration) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.revisionQuantization = bucketSize
}
})
}

// FollowerReadDelay is the time delay to apply to enable historial reads.
//
// This value defaults to 0 seconds.
func FollowerReadDelay(delay time.Duration) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.followerReadDelay = delay
}
})
}

// MaxRevisionStalenessPercent is the amount of time, expressed as a percentage of
Expand All @@ -161,42 +177,42 @@ func FollowerReadDelay(delay time.Duration) Option {
//
// This value defaults to 0.1 (10%).
func MaxRevisionStalenessPercent(stalenessPercent float64) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.maxRevisionStalenessPercent = stalenessPercent
}
})
}

// GCWindow is the maximum age of a passed revision that will be considered
// valid.
//
// This value defaults to 24 hours.
func GCWindow(window time.Duration) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.gcWindow = window
}
})
}

// MaxRetries is the maximum number of times a retriable transaction will be
// client-side retried.
// Default: 50
func MaxRetries(maxRetries int) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.maxRetries = maxRetries
}
})
}

// OverlapStrategy is the strategy used to generate overlap keys on write.
// Default: 'static'
func OverlapStrategy(strategy string) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.overlapStrategy = strategy
}
})
}

// OverlapKey is a key touched on every write if OverlapStrategy is "static"
// Default: 'key'
func OverlapKey(key string) Option {
return func(po *crdbOptions) {
return OptionFunc(func(po *crdbOptions) {
po.overlapKey = key
}
})
}
57 changes: 57 additions & 0 deletions internal/datastore/datastore.go
Expand Up @@ -2,6 +2,9 @@ package datastore

import (
"context"
"fmt"
"github.com/authzed/spicedb/internal/datastore/crdb"
"github.com/authzed/spicedb/internal/datastore/postgres"

v0 "github.com/authzed/authzed-go/proto/authzed/api/v0"
v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
Expand Down Expand Up @@ -150,3 +153,57 @@ type Revision = decimal.Decimal
// revision type in the future a bit easier if necessary. Implementations
// should use any time they want to signal an empty/error revision.
var NoRevision Revision

type DatastoreOptions interface {
Option()
}

type Option interface {
apply(DatastoreOptions)
}

type OptionFunc func(DatastoreOptions)

func (f OptionFunc) apply(o DatastoreOptions) {
f(o)
}

type Kind string

const (
CockroachKind Kind = "cockroach"
PostgresKind Kind = "postgres"
MemoryKind Kind = "memory"
)

// NewDatastore initializes a datastore given the options
func NewDatastore(kind Kind, options ...Option) (Datastore, error) {
// TODO: make this a map lookup so that you can add your own DatastoreKinds?
switch kind {
case CockroachKind:
crdbOpts := make([]crdb.Option, 0, len(options))
for _, o := range options {
crdbOpt, ok := o.(crdb.Option)
if !ok {
return nil, fmt.Errorf("incompatible option")
}
crdbOpts = append(crdbOpts, crdbOpt)
}
// TODO: url should be an option
return crdb.NewCRDBDatastore("", crdbOpts...)
case PostgresKind:
pgOpts:= make([]postgres.Option, 0, len(options))
for _, o := range options {
pgOpt, ok := o.(postgres.Option)
if !ok {
return nil, fmt.Errorf("incompatible option")
}
pgOpts = append(pgOpts, pgOpt)
}
// TODO: url should be an option
return postgres.NewPostgresDatastore("", pgOpts...)
case MemoryKind:
// TODO: memory datastore needs options
}
return nil, fmt.Errorf("unknown datastore kind")
}

0 comments on commit b56ba37

Please sign in to comment.