github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/spanner/options.go (about)

     1  package spanner
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"runtime"
     7  	"time"
     8  )
     9  
    10  type spannerOptions struct {
    11  	watchBufferLength           uint16
    12  	watchBufferWriteTimeout     time.Duration
    13  	revisionQuantization        time.Duration
    14  	followerReadDelay           time.Duration
    15  	maxRevisionStalenessPercent float64
    16  	credentialsFilePath         string
    17  	emulatorHost                string
    18  	disableStats                bool
    19  	readMaxOpen                 int
    20  	writeMaxOpen                int
    21  	minSessions                 uint64
    22  	maxSessions                 uint64
    23  	migrationPhase              string
    24  }
    25  
    26  type migrationPhase uint8
    27  
    28  const (
    29  	complete migrationPhase = iota
    30  )
    31  
    32  var migrationPhases = map[string]migrationPhase{
    33  	"": complete,
    34  }
    35  
    36  const (
    37  	errQuantizationTooLarge = "revision quantization (%s) must be less than (%s)"
    38  
    39  	defaultRevisionQuantization        = 5 * time.Second
    40  	defaultFollowerReadDelay           = 0 * time.Second
    41  	defaultMaxRevisionStalenessPercent = 0.1
    42  	defaultWatchBufferLength           = 128
    43  	defaultWatchBufferWriteTimeout     = 1 * time.Second
    44  	defaultDisableStats                = false
    45  	maxRevisionQuantization            = 24 * time.Hour
    46  )
    47  
    48  // Option provides the facility to configure how clients within the Spanner
    49  // datastore interact with the running Spanner database.
    50  type Option func(*spannerOptions)
    51  
    52  func generateConfig(options []Option) (spannerOptions, error) {
    53  	// originally SpiceDB didn't use connection pools for Spanner SDK, so it opened 1 single connection
    54  	// This determines if there are more CPU cores to increase the default number of connections
    55  	defaultNumberConnections := max(1, math.Round(float64(runtime.GOMAXPROCS(0))))
    56  	computed := spannerOptions{
    57  		watchBufferLength:           defaultWatchBufferLength,
    58  		watchBufferWriteTimeout:     defaultWatchBufferWriteTimeout,
    59  		revisionQuantization:        defaultRevisionQuantization,
    60  		followerReadDelay:           defaultFollowerReadDelay,
    61  		maxRevisionStalenessPercent: defaultMaxRevisionStalenessPercent,
    62  		disableStats:                defaultDisableStats,
    63  		readMaxOpen:                 int(defaultNumberConnections),
    64  		writeMaxOpen:                int(defaultNumberConnections),
    65  		minSessions:                 100,
    66  		maxSessions:                 400,
    67  		migrationPhase:              "", // no migration
    68  	}
    69  
    70  	for _, option := range options {
    71  		option(&computed)
    72  	}
    73  
    74  	// Run any checks on the config that need to be done
    75  	if computed.revisionQuantization >= maxRevisionQuantization {
    76  		return computed, fmt.Errorf(
    77  			errQuantizationTooLarge,
    78  			computed.revisionQuantization,
    79  			maxRevisionQuantization,
    80  		)
    81  	}
    82  
    83  	if _, ok := migrationPhases[computed.migrationPhase]; !ok {
    84  		return computed, fmt.Errorf("unknown migration phase: %s", computed.migrationPhase)
    85  	}
    86  
    87  	return computed, nil
    88  }
    89  
    90  // WatchBufferLength is the number of entries that can be stored in the watch
    91  // buffer while awaiting read by the client.
    92  //
    93  // This value defaults to 128.
    94  func WatchBufferLength(watchBufferLength uint16) Option {
    95  	return func(so *spannerOptions) {
    96  		so.watchBufferLength = watchBufferLength
    97  	}
    98  }
    99  
   100  // WatchBufferWriteTimeout is the maximum timeout for writing to the watch buffer,
   101  // after which the caller to the watch will be disconnected.
   102  func WatchBufferWriteTimeout(watchBufferWriteTimeout time.Duration) Option {
   103  	return func(so *spannerOptions) { so.watchBufferWriteTimeout = watchBufferWriteTimeout }
   104  }
   105  
   106  // RevisionQuantization is the time bucket size to which advertised revisions
   107  // will be rounded.
   108  //
   109  // This value defaults to 5 seconds.
   110  func RevisionQuantization(bucketSize time.Duration) Option {
   111  	return func(so *spannerOptions) {
   112  		so.revisionQuantization = bucketSize
   113  	}
   114  }
   115  
   116  // FollowerReadDelay is the time delay to apply to enable historial reads.
   117  //
   118  // This value defaults to 0 seconds.
   119  func FollowerReadDelay(delay time.Duration) Option {
   120  	return func(so *spannerOptions) {
   121  		so.followerReadDelay = delay
   122  	}
   123  }
   124  
   125  // MaxRevisionStalenessPercent is the amount of time, expressed as a percentage of
   126  // the revision quantization window, that a previously computed rounded revision
   127  // can still be advertised after the next rounded revision would otherwise be ready.
   128  //
   129  // This value defaults to 0.1 (10%).
   130  func MaxRevisionStalenessPercent(stalenessPercent float64) Option {
   131  	return func(so *spannerOptions) {
   132  		so.maxRevisionStalenessPercent = stalenessPercent
   133  	}
   134  }
   135  
   136  // CredentialsFile is the path to a file containing credentials for a service
   137  // account that can access the cloud spanner instance
   138  func CredentialsFile(path string) Option {
   139  	return func(so *spannerOptions) {
   140  		so.credentialsFilePath = path
   141  	}
   142  }
   143  
   144  // EmulatorHost is the URI of a Spanner emulator to connect to for
   145  // development and testing use
   146  func EmulatorHost(uri string) Option {
   147  	return func(so *spannerOptions) {
   148  		so.emulatorHost = uri
   149  	}
   150  }
   151  
   152  // DisableStats disables recording counts to the stats table
   153  func DisableStats(disable bool) Option {
   154  	return func(po *spannerOptions) {
   155  		po.disableStats = disable
   156  	}
   157  }
   158  
   159  // ReadConnsMaxOpen is the maximum size of the connection pool used for reads.
   160  //
   161  // This value defaults to having 20 connections.
   162  func ReadConnsMaxOpen(conns int) Option {
   163  	return func(po *spannerOptions) { po.readMaxOpen = conns }
   164  }
   165  
   166  // WriteConnsMaxOpen is the maximum size of the connection pool used for writes.
   167  //
   168  // This value defaults to having 10 connections.
   169  func WriteConnsMaxOpen(conns int) Option {
   170  	return func(po *spannerOptions) { po.writeMaxOpen = conns }
   171  }
   172  
   173  // MinSessionCount minimum number of session the Spanner client can have
   174  // at a given time.
   175  //
   176  // Defaults to 100.
   177  func MinSessionCount(minSessions uint64) Option {
   178  	return func(po *spannerOptions) { po.minSessions = minSessions }
   179  }
   180  
   181  // MaxSessionCount maximum number of session the Spanner client can have
   182  // at a given time.
   183  //
   184  // Defaults to 400 sessions.
   185  func MaxSessionCount(maxSessions uint64) Option {
   186  	return func(po *spannerOptions) { po.maxSessions = maxSessions }
   187  }
   188  
   189  // MigrationPhase configures the spanner driver to the proper state of a
   190  // multi-phase migration.
   191  //
   192  // Steady-state configuration (e.g. fully migrated) by default
   193  func MigrationPhase(phase string) Option {
   194  	return func(po *spannerOptions) { po.migrationPhase = phase }
   195  }