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

     1  package crdb
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	pgxcommon "github.com/authzed/spicedb/internal/datastore/postgres/common"
     8  )
     9  
    10  type crdbOptions struct {
    11  	readPoolOpts, writePoolOpts pgxcommon.PoolOptions
    12  	connectRate                 time.Duration
    13  
    14  	watchBufferLength           uint16
    15  	watchBufferWriteTimeout     time.Duration
    16  	revisionQuantization        time.Duration
    17  	followerReadDelay           time.Duration
    18  	maxRevisionStalenessPercent float64
    19  	gcWindow                    time.Duration
    20  	maxRetries                  uint8
    21  	overlapStrategy             string
    22  	overlapKey                  string
    23  	enableConnectionBalancing   bool
    24  	analyzeBeforeStatistics     bool
    25  
    26  	enablePrometheusStats bool
    27  }
    28  
    29  const (
    30  	errQuantizationTooLarge = "revision quantization (%s) must be less than GC window (%s)"
    31  
    32  	overlapStrategyRequest  = "request"
    33  	overlapStrategyPrefix   = "prefix"
    34  	overlapStrategyStatic   = "static"
    35  	overlapStrategyInsecure = "insecure"
    36  
    37  	defaultRevisionQuantization        = 5 * time.Second
    38  	defaultFollowerReadDelay           = 0 * time.Second
    39  	defaultMaxRevisionStalenessPercent = 0.1
    40  	defaultWatchBufferLength           = 128
    41  	defaultWatchBufferWriteTimeout     = 1 * time.Second
    42  	defaultSplitSize                   = 1024
    43  
    44  	defaultMaxRetries      = 5
    45  	defaultOverlapKey      = "defaultsynckey"
    46  	defaultOverlapStrategy = overlapStrategyStatic
    47  
    48  	defaultEnablePrometheusStats     = false
    49  	defaultEnableConnectionBalancing = true
    50  	defaultConnectRate               = 100 * time.Millisecond
    51  )
    52  
    53  // Option provides the facility to configure how clients within the CRDB
    54  // datastore interact with the running CockroachDB database.
    55  type Option func(*crdbOptions)
    56  
    57  func generateConfig(options []Option) (crdbOptions, error) {
    58  	computed := crdbOptions{
    59  		gcWindow:                    24 * time.Hour,
    60  		watchBufferLength:           defaultWatchBufferLength,
    61  		watchBufferWriteTimeout:     defaultWatchBufferWriteTimeout,
    62  		revisionQuantization:        defaultRevisionQuantization,
    63  		followerReadDelay:           defaultFollowerReadDelay,
    64  		maxRevisionStalenessPercent: defaultMaxRevisionStalenessPercent,
    65  		maxRetries:                  defaultMaxRetries,
    66  		overlapKey:                  defaultOverlapKey,
    67  		overlapStrategy:             defaultOverlapStrategy,
    68  		enablePrometheusStats:       defaultEnablePrometheusStats,
    69  		enableConnectionBalancing:   defaultEnableConnectionBalancing,
    70  		connectRate:                 defaultConnectRate,
    71  	}
    72  
    73  	for _, option := range options {
    74  		option(&computed)
    75  	}
    76  
    77  	// Run any checks on the config that need to be done
    78  	if computed.revisionQuantization >= computed.gcWindow {
    79  		return computed, fmt.Errorf(
    80  			errQuantizationTooLarge,
    81  			computed.revisionQuantization,
    82  			computed.gcWindow,
    83  		)
    84  	}
    85  
    86  	return computed, nil
    87  }
    88  
    89  // ReadConnHealthCheckInterval is the frequency at which both idle and max
    90  // lifetime connections are checked, and also the frequency at which the
    91  // minimum number of connections is checked.
    92  //
    93  // This happens asynchronously.
    94  //
    95  // This is not the only approach to evaluate these counts; "connection idle/max
    96  // lifetime" is also checked when connections are released to the pool.
    97  //
    98  // There is no guarantee connections won't last longer than their specified
    99  // idle/max lifetime. It's largely dependent on the health-check goroutine
   100  // being able to pull them from the connection pool.
   101  //
   102  // The health-check may not be able to clean up those connections if they are
   103  // held by the application very frequently.
   104  //
   105  // This value defaults to 30s.
   106  func ReadConnHealthCheckInterval(interval time.Duration) Option {
   107  	return func(po *crdbOptions) { po.readPoolOpts.ConnHealthCheckInterval = &interval }
   108  }
   109  
   110  // WriteConnHealthCheckInterval is the frequency at which both idle and max
   111  // lifetime connections are checked, and also the frequency at which the
   112  // minimum number of connections is checked.
   113  //
   114  // This happens asynchronously.
   115  //
   116  // This is not the only approach to evaluate these counts; "connection idle/max
   117  // lifetime" is also checked when connections are released to the pool.
   118  //
   119  // There is no guarantee connections won't last longer than their specified
   120  // idle/max lifetime. It's largely dependent on the health-check goroutine
   121  // being able to pull them from the connection pool.
   122  //
   123  // The health-check may not be able to clean up those connections if they are
   124  // held by the application very frequently.
   125  //
   126  // This value defaults to 30s.
   127  func WriteConnHealthCheckInterval(interval time.Duration) Option {
   128  	return func(po *crdbOptions) { po.writePoolOpts.ConnHealthCheckInterval = &interval }
   129  }
   130  
   131  // ReadConnMaxIdleTime is the duration after which an idle read connection will
   132  // be automatically closed by the health check.
   133  //
   134  // This value defaults to having no maximum.
   135  func ReadConnMaxIdleTime(idle time.Duration) Option {
   136  	return func(po *crdbOptions) { po.readPoolOpts.ConnMaxIdleTime = &idle }
   137  }
   138  
   139  // WriteConnMaxIdleTime is the duration after which an idle write connection
   140  // will be automatically closed by the health check.
   141  //
   142  // This value defaults to having no maximum.
   143  func WriteConnMaxIdleTime(idle time.Duration) Option {
   144  	return func(po *crdbOptions) { po.writePoolOpts.ConnMaxIdleTime = &idle }
   145  }
   146  
   147  // ReadConnMaxLifetime is the duration since creation after which a read
   148  // connection will be automatically closed.
   149  //
   150  // This value defaults to having no maximum.
   151  func ReadConnMaxLifetime(lifetime time.Duration) Option {
   152  	return func(po *crdbOptions) { po.readPoolOpts.ConnMaxLifetime = &lifetime }
   153  }
   154  
   155  // ReadConnMaxLifetimeJitter is an interval to wait up to after the max lifetime
   156  // to close the connection.
   157  //
   158  // This value defaults to 20% of the max lifetime.
   159  func ReadConnMaxLifetimeJitter(jitter time.Duration) Option {
   160  	return func(po *crdbOptions) { po.readPoolOpts.ConnMaxLifetimeJitter = &jitter }
   161  }
   162  
   163  // WriteConnMaxLifetime is the duration since creation after which a write
   164  // connection will be automatically closed.
   165  //
   166  // This value defaults to having no maximum.
   167  func WriteConnMaxLifetime(lifetime time.Duration) Option {
   168  	return func(po *crdbOptions) { po.writePoolOpts.ConnMaxLifetime = &lifetime }
   169  }
   170  
   171  // WriteConnMaxLifetimeJitter is an interval to wait up to after the max lifetime
   172  // to close the connection.
   173  //
   174  // This value defaults to 20% of the max lifetime.
   175  func WriteConnMaxLifetimeJitter(jitter time.Duration) Option {
   176  	return func(po *crdbOptions) { po.writePoolOpts.ConnMaxLifetimeJitter = &jitter }
   177  }
   178  
   179  // ReadConnsMinOpen is the minimum size of the connection pool used for reads.
   180  //
   181  // The health check will increase the number of connections to this amount if
   182  // it had dropped below.
   183  //
   184  // This value defaults to the maximum open connections.
   185  func ReadConnsMinOpen(conns int) Option {
   186  	return func(po *crdbOptions) { po.readPoolOpts.MinOpenConns = &conns }
   187  }
   188  
   189  // WriteConnsMinOpen is the minimum size of the connection pool used for writes.
   190  //
   191  // The health check will increase the number of connections to this amount if
   192  // it had dropped below.
   193  //
   194  // This value defaults to the maximum open connections.
   195  func WriteConnsMinOpen(conns int) Option {
   196  	return func(po *crdbOptions) { po.writePoolOpts.MinOpenConns = &conns }
   197  }
   198  
   199  // ReadConnsMaxOpen is the maximum size of the connection pool used for reads.
   200  //
   201  // This value defaults to having no maximum.
   202  func ReadConnsMaxOpen(conns int) Option {
   203  	return func(po *crdbOptions) { po.readPoolOpts.MaxOpenConns = &conns }
   204  }
   205  
   206  // WriteConnsMaxOpen is the maximum size of the connection pool used for writes.
   207  //
   208  // This value defaults to having no maximum.
   209  func WriteConnsMaxOpen(conns int) Option {
   210  	return func(po *crdbOptions) { po.writePoolOpts.MaxOpenConns = &conns }
   211  }
   212  
   213  // WatchBufferLength is the number of entries that can be stored in the watch
   214  // buffer while awaiting read by the client.
   215  //
   216  // This value defaults to 128.
   217  func WatchBufferLength(watchBufferLength uint16) Option {
   218  	return func(po *crdbOptions) { po.watchBufferLength = watchBufferLength }
   219  }
   220  
   221  // WatchBufferWriteTimeout is the maximum timeout for writing to the watch buffer,
   222  // after which the caller to the watch will be disconnected.
   223  func WatchBufferWriteTimeout(watchBufferWriteTimeout time.Duration) Option {
   224  	return func(po *crdbOptions) { po.watchBufferWriteTimeout = watchBufferWriteTimeout }
   225  }
   226  
   227  // RevisionQuantization is the time bucket size to which advertised revisions
   228  // will be rounded.
   229  //
   230  // This value defaults to 5 seconds.
   231  func RevisionQuantization(bucketSize time.Duration) Option {
   232  	return func(po *crdbOptions) { po.revisionQuantization = bucketSize }
   233  }
   234  
   235  // FollowerReadDelay is the time delay to apply to enable historial reads.
   236  //
   237  // This value defaults to 0 seconds.
   238  func FollowerReadDelay(delay time.Duration) Option {
   239  	return func(po *crdbOptions) { po.followerReadDelay = delay }
   240  }
   241  
   242  // MaxRevisionStalenessPercent is the amount of time, expressed as a percentage of
   243  // the revision quantization window, that a previously computed rounded revision
   244  // can still be advertised after the next rounded revision would otherwise be ready.
   245  //
   246  // This value defaults to 0.1 (10%).
   247  func MaxRevisionStalenessPercent(stalenessPercent float64) Option {
   248  	return func(po *crdbOptions) { po.maxRevisionStalenessPercent = stalenessPercent }
   249  }
   250  
   251  // GCWindow is the maximum age of a passed revision that will be considered
   252  // valid.
   253  //
   254  // This value defaults to 24 hours.
   255  func GCWindow(window time.Duration) Option {
   256  	return func(po *crdbOptions) { po.gcWindow = window }
   257  }
   258  
   259  // ConnectRate is the rate at which new datastore connections can be made.
   260  //
   261  // This is a duration, the rate is 1/period.
   262  func ConnectRate(rate time.Duration) Option {
   263  	return func(po *crdbOptions) { po.connectRate = rate }
   264  }
   265  
   266  // MaxRetries is the maximum number of times a retriable transaction will be
   267  // client-side retried.
   268  // Default: 5
   269  func MaxRetries(maxRetries uint8) Option {
   270  	return func(po *crdbOptions) { po.maxRetries = maxRetries }
   271  }
   272  
   273  // OverlapStrategy is the strategy used to generate overlap keys on write.
   274  // Default: 'static'
   275  func OverlapStrategy(strategy string) Option {
   276  	return func(po *crdbOptions) { po.overlapStrategy = strategy }
   277  }
   278  
   279  // OverlapKey is a key touched on every write if OverlapStrategy is "static"
   280  // Default: 'key'
   281  func OverlapKey(key string) Option {
   282  	return func(po *crdbOptions) { po.overlapKey = key }
   283  }
   284  
   285  // WithEnablePrometheusStats marks whether Prometheus metrics provided by the Postgres
   286  // clients being used by the datastore are enabled.
   287  //
   288  // Prometheus metrics are disabled by default.
   289  func WithEnablePrometheusStats(enablePrometheusStats bool) Option {
   290  	return func(po *crdbOptions) { po.enablePrometheusStats = enablePrometheusStats }
   291  }
   292  
   293  // WithEnableConnectionBalancing marks whether Prometheus metrics provided by the Postgres
   294  // clients being used by the datastore are enabled.
   295  //
   296  // Prometheus metrics are disabled by default.
   297  func WithEnableConnectionBalancing(connectionBalancing bool) Option {
   298  	return func(po *crdbOptions) { po.enableConnectionBalancing = connectionBalancing }
   299  }
   300  
   301  // DebugAnalyzeBeforeStatistics signals to the Statistics method that it should
   302  // run Analyze on the database before returning statistics. This should only be
   303  // used for debug and testing.
   304  //
   305  // Disabled by default.
   306  func DebugAnalyzeBeforeStatistics() Option {
   307  	return func(po *crdbOptions) { po.analyzeBeforeStatistics = true }
   308  }