Skip to content

Commit

Permalink
model input datastore options
Browse files Browse the repository at this point in the history
  • Loading branch information
ecordell committed Dec 6, 2021
1 parent 4395998 commit 3af7d50
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 169 deletions.
84 changes: 19 additions & 65 deletions cmd/spicedb/serve.go
Expand Up @@ -9,7 +9,6 @@ import (
"syscall"
"time"

"github.com/alecthomas/units"
"github.com/authzed/grpcutil"
"github.com/fatih/color"
grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
Expand All @@ -28,9 +27,6 @@ import (
"github.com/authzed/spicedb/internal/dashboard"
"github.com/authzed/spicedb/internal/datastore"
"github.com/authzed/spicedb/internal/datastore/common"
"github.com/authzed/spicedb/internal/datastore/crdb"
"github.com/authzed/spicedb/internal/datastore/memdb"
"github.com/authzed/spicedb/internal/datastore/postgres"
"github.com/authzed/spicedb/internal/datastore/proxy"
"github.com/authzed/spicedb/internal/dispatch/caching"
"github.com/authzed/spicedb/internal/dispatch/graph"
Expand Down Expand Up @@ -136,68 +132,26 @@ func serveRun(cmd *cobra.Command, args []string) {
}

datastoreEngine := cobrautil.MustGetStringExpanded(cmd, "datastore-engine")
datastoreURI := cobrautil.MustGetStringExpanded(cmd, "datastore-conn-uri")

revisionFuzzingTimedelta := cobrautil.MustGetDuration(cmd, "datastore-revision-fuzzing-duration")
gcWindow := cobrautil.MustGetDuration(cmd, "datastore-gc-window")
maxRetries := cobrautil.MustGetInt(cmd, "datastore-max-tx-retries")
overlapKey := cobrautil.MustGetStringExpanded(cmd, "datastore-tx-overlap-key")
overlapStrategy := cobrautil.MustGetStringExpanded(cmd, "datastore-tx-overlap-strategy")

splitQuerySize, err := units.ParseBase2Bytes(cobrautil.MustGetStringExpanded(cmd, "datastore-query-split-size"))
ds, err := datastore.NewDatastore(
datastore.Engine(datastoreEngine),
datastore.WithRevisionQuantization(cobrautil.MustGetDuration(cmd, "datastore-revision-fuzzing-duration")),
datastore.WithGCWindow(cobrautil.MustGetDuration(cmd, "datastore-gc-window")),
datastore.WithURI(cobrautil.MustGetStringExpanded(cmd, "datastore-conn-uri")),
datastore.WithMaxIdleTime(cobrautil.MustGetDuration(cmd, "datastore-conn-max-idletime")),
datastore.WithMaxLifetime(cobrautil.MustGetDuration(cmd, "datastore-conn-max-lifetime")),
datastore.WithMaxOpenConns(cobrautil.MustGetInt(cmd, "datastore-conn-max-open")),
datastore.WithMinOpenConns(cobrautil.MustGetInt(cmd, "datastore-conn-min-open")),
datastore.WithSplitQuerySize(cobrautil.MustGetStringExpanded(cmd, "datastore-query-split-size")),
datastore.WithFollowerReadDelay(cobrautil.MustGetDuration(cmd, "datastore-follower-read-delay-duration")),
datastore.WithMaxRetries(cobrautil.MustGetInt(cmd, "datastore-max-tx-retries")),
datastore.WithOverlapKey(cobrautil.MustGetStringExpanded(cmd, "datastore-tx-overlap-key")),
datastore.WithOverlapStrategy(cobrautil.MustGetStringExpanded(cmd, "datastore-tx-overlap-strategy")),
datastore.WithHealthCheckPeriod(cobrautil.MustGetDuration(cmd, "datastore-conn-healthcheck-interval")),
datastore.WithGCInterval(cobrautil.MustGetDuration(cmd, "datastore-gc-interval")),
datastore.WithGCMaxOperationTime(cobrautil.MustGetDuration(cmd, "datastore-gc-max-operation-time")),
)
if err != nil {
log.Fatal().Err(err).Msg("failed to parse datastore-query-split-size")
}

var ds datastore.Datastore
if datastoreEngine == "memory" {
log.Info().Msg("using in-memory datastore")
log.Warn().Msg("in-memory datastore is not persistent and not feasible to run in a high availability fashion")
ds, err = memdb.NewMemdbDatastore(0, revisionFuzzingTimedelta, gcWindow, 0)
if err != nil {
log.Fatal().Err(err).Msg("failed to init datastore")
}
} else if datastoreEngine == "cockroachdb" {
log.Info().Msg("using cockroachdb datastore")
ds, err = crdb.NewCRDBDatastore(
datastoreURI,
crdb.ConnMaxIdleTime(cobrautil.MustGetDuration(cmd, "datastore-conn-max-idletime")),
crdb.ConnMaxLifetime(cobrautil.MustGetDuration(cmd, "datastore-conn-max-lifetime")),
crdb.MaxOpenConns(cobrautil.MustGetInt(cmd, "datastore-conn-max-open")),
crdb.MinOpenConns(cobrautil.MustGetInt(cmd, "datastore-conn-min-open")),
crdb.RevisionQuantization(revisionFuzzingTimedelta),
crdb.FollowerReadDelay(cobrautil.MustGetDuration(cmd, "datastore-follower-read-delay-duration")),
crdb.GCWindow(gcWindow),
crdb.MaxRetries(maxRetries),
crdb.SplitAtEstimatedQuerySize(splitQuerySize),
crdb.OverlapKey(overlapKey),
crdb.OverlapStrategy(overlapStrategy),
)
if err != nil {
log.Fatal().Err(err).Msg("failed to init datastore")
}
} else if datastoreEngine == "postgres" {
log.Info().Msg("using postgres datastore")
ds, err = postgres.NewPostgresDatastore(
datastoreURI,
postgres.ConnMaxIdleTime(cobrautil.MustGetDuration(cmd, "datastore-conn-max-idletime")),
postgres.ConnMaxLifetime(cobrautil.MustGetDuration(cmd, "datastore-conn-max-lifetime")),
postgres.HealthCheckPeriod(cobrautil.MustGetDuration(cmd, "datastore-conn-healthcheck-interval")),
postgres.MaxOpenConns(cobrautil.MustGetInt(cmd, "datastore-conn-max-open")),
postgres.MinOpenConns(cobrautil.MustGetInt(cmd, "datastore-conn-min-open")),
postgres.RevisionFuzzingTimedelta(revisionFuzzingTimedelta),
postgres.GCInterval(cobrautil.MustGetDuration(cmd, "datastore-gc-interval")),
postgres.GCMaxOperationTime(cobrautil.MustGetDuration(cmd, "datastore-gc-max-operation-time")),
postgres.GCWindow(gcWindow),
postgres.EnablePrometheusStats(),
postgres.EnableTracing(),
postgres.SplitAtEstimatedQuerySize(splitQuerySize),
)
if err != nil {
log.Fatal().Err(err).Msg("failed to init datastore")
}
} else {
log.Fatal().Str("datastore-engine", datastoreEngine).Msg("unknown datastore engine type")
log.Fatal().Err(err).Msg("failed to init datastore")
}

bootstrapFilePaths := cobrautil.MustGetStringSlice(cmd, "datastore-bootstrap-files")
Expand Down
27 changes: 27 additions & 0 deletions internal/datastore/crdb/crdb.go
Expand Up @@ -49,8 +49,35 @@ const (
querySelectNow = "SELECT cluster_logical_timestamp()"
queryReturningTimestamp = "RETURNING cluster_logical_timestamp()"
queryShowZoneConfig = "SHOW ZONE CONFIGURATION FOR RANGE default;"

cockroachEngine datastore.Engine = "cockroach"
)

func init() {
datastore.RegisterEngine(cockroachEngine, newFromDatastoreOptions)
}

func newFromDatastoreOptions(opts datastore.Options) (datastore.Datastore, error) {
splitQuerySize, err := units.ParseBase2Bytes(opts.SplitQuerySize)
if err != nil {
return nil, fmt.Errorf("failed to parse split query size: %w", err)
}
return NewCRDBDatastore(
opts.URI,
GCWindow(opts.GCWindow),
RevisionQuantization(opts.RevisionQuantization),
ConnMaxIdleTime(opts.MaxIdleTime),
ConnMaxLifetime(opts.MaxLifetime),
MaxOpenConns(opts.MaxOpenConns),
MinOpenConns(opts.MinOpenConns),
SplitAtEstimatedQuerySize(splitQuerySize),
FollowerReadDelay(opts.FollowerReadDelay),
MaxRetries(opts.MaxRetries),
OverlapKey(opts.OverlapKey),
OverlapStrategy(opts.OverlapStrategy),
)
}

// NewCRDBDatastore initializes a SpiceDB datastore that uses a CockroachDB
// database while leveraging its AOST functionality.
func NewCRDBDatastore(url string, options ...Option) (datastore.Datastore, error) {
Expand Down
72 changes: 28 additions & 44 deletions internal/datastore/crdb/options.go
Expand Up @@ -2,7 +2,6 @@ package crdb

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

"github.com/alecthomas/units"
Expand All @@ -27,9 +26,6 @@ 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 @@ -49,19 +45,7 @@ const (

// Option provides the facility to configure how clients within the CRDB
// datastore interact with the running CockroachDB database.
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)
}
type Option func(o *crdbOptions)

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

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

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

// MinOpenConns is the minimum size of the connection pool.
Expand All @@ -128,47 +112,47 @@ func ConnMaxLifetime(lifetime time.Duration) Option {
//
// This value defaults to zero.
func MinOpenConns(conns int) Option {
return OptionFunc(func(po *crdbOptions) {
return 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 OptionFunc(func(po *crdbOptions) {
return 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 OptionFunc(func(po *crdbOptions) {
return 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 OptionFunc(func(po *crdbOptions) {
return 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 OptionFunc(func(po *crdbOptions) {
return func(po *crdbOptions) {
po.followerReadDelay = delay
})
}
}

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

0 comments on commit 3af7d50

Please sign in to comment.