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

     1  package postgres
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	pgxcommon "github.com/authzed/spicedb/internal/datastore/postgres/common"
     8  )
     9  
    10  type postgresOptions struct {
    11  	readPoolOpts, writePoolOpts pgxcommon.PoolOptions
    12  
    13  	maxRevisionStalenessPercent float64
    14  
    15  	credentialsProviderName string
    16  
    17  	watchBufferLength       uint16
    18  	watchBufferWriteTimeout time.Duration
    19  	revisionQuantization    time.Duration
    20  	gcWindow                time.Duration
    21  	gcInterval              time.Duration
    22  	gcMaxOperationTime      time.Duration
    23  	maxRetries              uint8
    24  
    25  	enablePrometheusStats   bool
    26  	analyzeBeforeStatistics bool
    27  	gcEnabled               bool
    28  
    29  	migrationPhase string
    30  
    31  	logger *tracingLogger
    32  
    33  	queryInterceptor pgxcommon.QueryInterceptor
    34  }
    35  
    36  type migrationPhase uint8
    37  
    38  const (
    39  	writeBothReadOld migrationPhase = iota
    40  	writeBothReadNew
    41  	complete
    42  )
    43  
    44  var migrationPhases = map[string]migrationPhase{
    45  	"write-both-read-old": writeBothReadOld,
    46  	"write-both-read-new": writeBothReadNew,
    47  	"":                    complete,
    48  }
    49  
    50  const (
    51  	errQuantizationTooLarge = "revision quantization interval (%s) must be less than GC window (%s)"
    52  
    53  	defaultWatchBufferLength                 = 128
    54  	defaultWatchBufferWriteTimeout           = 1 * time.Second
    55  	defaultGarbageCollectionWindow           = 24 * time.Hour
    56  	defaultGarbageCollectionInterval         = time.Minute * 3
    57  	defaultGarbageCollectionMaxOperationTime = time.Minute
    58  	defaultQuantization                      = 5 * time.Second
    59  	defaultMaxRevisionStalenessPercent       = 0.1
    60  	defaultEnablePrometheusStats             = false
    61  	defaultMaxRetries                        = 10
    62  	defaultGCEnabled                         = true
    63  	defaultCredentialsProviderName           = ""
    64  )
    65  
    66  // Option provides the facility to configure how clients within the
    67  // Postgres datastore interact with the running Postgres database.
    68  type Option func(*postgresOptions)
    69  
    70  func generateConfig(options []Option) (postgresOptions, error) {
    71  	computed := postgresOptions{
    72  		gcWindow:                    defaultGarbageCollectionWindow,
    73  		gcInterval:                  defaultGarbageCollectionInterval,
    74  		gcMaxOperationTime:          defaultGarbageCollectionMaxOperationTime,
    75  		watchBufferLength:           defaultWatchBufferLength,
    76  		watchBufferWriteTimeout:     defaultWatchBufferWriteTimeout,
    77  		revisionQuantization:        defaultQuantization,
    78  		maxRevisionStalenessPercent: defaultMaxRevisionStalenessPercent,
    79  		enablePrometheusStats:       defaultEnablePrometheusStats,
    80  		maxRetries:                  defaultMaxRetries,
    81  		gcEnabled:                   defaultGCEnabled,
    82  		credentialsProviderName:     defaultCredentialsProviderName,
    83  		queryInterceptor:            nil,
    84  	}
    85  
    86  	for _, option := range options {
    87  		option(&computed)
    88  	}
    89  
    90  	// Run any checks on the config that need to be done
    91  	if computed.revisionQuantization >= computed.gcWindow {
    92  		return computed, fmt.Errorf(
    93  			errQuantizationTooLarge,
    94  			computed.revisionQuantization,
    95  			computed.gcWindow,
    96  		)
    97  	}
    98  
    99  	if _, ok := migrationPhases[computed.migrationPhase]; !ok {
   100  		return computed, fmt.Errorf("unknown migration phase: %s", computed.migrationPhase)
   101  	}
   102  
   103  	return computed, nil
   104  }
   105  
   106  // ReadConnHealthCheckInterval is the frequency at which both idle and max
   107  // lifetime connections are checked, and also the frequency at which the
   108  // minimum number of connections is checked.
   109  //
   110  // This happens asynchronously.
   111  //
   112  // This is not the only approach to evaluate these counts; "connection idle/max
   113  // lifetime" is also checked when connections are released to the pool.
   114  //
   115  // There is no guarantee connections won't last longer than their specified
   116  // idle/max lifetime. It's largely dependent on the health-check goroutine
   117  // being able to pull them from the connection pool.
   118  //
   119  // The health-check may not be able to clean up those connections if they are
   120  // held by the application very frequently.
   121  //
   122  // This value defaults to 30s.
   123  func ReadConnHealthCheckInterval(interval time.Duration) Option {
   124  	return func(po *postgresOptions) { po.readPoolOpts.ConnHealthCheckInterval = &interval }
   125  }
   126  
   127  // WriteConnHealthCheckInterval is the frequency at which both idle and max
   128  // lifetime connections are checked, and also the frequency at which the
   129  // minimum number of connections is checked.
   130  //
   131  // This happens asynchronously.
   132  //
   133  // This is not the only approach to evaluate these counts; "connection idle/max
   134  // lifetime" is also checked when connections are released to the pool.
   135  //
   136  // There is no guarantee connections won't last longer than their specified
   137  // idle/max lifetime. It's largely dependent on the health-check goroutine
   138  // being able to pull them from the connection pool.
   139  //
   140  // The health-check may not be able to clean up those connections if they are
   141  // held by the application very frequently.
   142  //
   143  // This value defaults to 30s.
   144  func WriteConnHealthCheckInterval(interval time.Duration) Option {
   145  	return func(po *postgresOptions) { po.writePoolOpts.ConnHealthCheckInterval = &interval }
   146  }
   147  
   148  // ReadConnMaxIdleTime is the duration after which an idle read connection will
   149  // be automatically closed by the health check.
   150  //
   151  // This value defaults to having no maximum.
   152  func ReadConnMaxIdleTime(idle time.Duration) Option {
   153  	return func(po *postgresOptions) { po.readPoolOpts.ConnMaxIdleTime = &idle }
   154  }
   155  
   156  // WriteConnMaxIdleTime is the duration after which an idle write connection
   157  // will be automatically closed by the health check.
   158  //
   159  // This value defaults to having no maximum.
   160  func WriteConnMaxIdleTime(idle time.Duration) Option {
   161  	return func(po *postgresOptions) { po.writePoolOpts.ConnMaxIdleTime = &idle }
   162  }
   163  
   164  // ReadConnMaxLifetime is the duration since creation after which a read
   165  // connection will be automatically closed.
   166  //
   167  // This value defaults to having no maximum.
   168  func ReadConnMaxLifetime(lifetime time.Duration) Option {
   169  	return func(po *postgresOptions) { po.readPoolOpts.ConnMaxLifetime = &lifetime }
   170  }
   171  
   172  // WriteConnMaxLifetime is the duration since creation after which a write
   173  // connection will be automatically closed.
   174  //
   175  // This value defaults to having no maximum.
   176  func WriteConnMaxLifetime(lifetime time.Duration) Option {
   177  	return func(po *postgresOptions) { po.writePoolOpts.ConnMaxLifetime = &lifetime }
   178  }
   179  
   180  // ReadConnMaxLifetimeJitter is an interval to wait up to after the max lifetime
   181  // to close the connection.
   182  //
   183  // This value defaults to 20% of the max lifetime.
   184  func ReadConnMaxLifetimeJitter(jitter time.Duration) Option {
   185  	return func(po *postgresOptions) { po.readPoolOpts.ConnMaxLifetimeJitter = &jitter }
   186  }
   187  
   188  // WriteConnMaxLifetimeJitter is an interval to wait up to after the max lifetime
   189  // to close the connection.
   190  //
   191  // This value defaults to 20% of the max lifetime.
   192  func WriteConnMaxLifetimeJitter(jitter time.Duration) Option {
   193  	return func(po *postgresOptions) { po.writePoolOpts.ConnMaxLifetimeJitter = &jitter }
   194  }
   195  
   196  // ReadConnsMinOpen is the minimum size of the connection pool used for reads.
   197  //
   198  // The health check will increase the number of connections to this amount if
   199  // it had dropped below.
   200  //
   201  // This value defaults to the maximum open connections.
   202  func ReadConnsMinOpen(conns int) Option {
   203  	return func(po *postgresOptions) { po.readPoolOpts.MinOpenConns = &conns }
   204  }
   205  
   206  // WriteConnsMinOpen is the minimum size of the connection pool used for writes.
   207  //
   208  // The health check will increase the number of connections to this amount if
   209  // it had dropped below.
   210  //
   211  // This value defaults to the maximum open connections.
   212  func WriteConnsMinOpen(conns int) Option {
   213  	return func(po *postgresOptions) { po.writePoolOpts.MinOpenConns = &conns }
   214  }
   215  
   216  // ReadConnsMaxOpen is the maximum size of the connection pool used for reads.
   217  //
   218  // This value defaults to having no maximum.
   219  func ReadConnsMaxOpen(conns int) Option {
   220  	return func(po *postgresOptions) { po.readPoolOpts.MaxOpenConns = &conns }
   221  }
   222  
   223  // WriteConnsMaxOpen is the maximum size of the connection pool used for writes.
   224  //
   225  // This value defaults to having no maximum.
   226  func WriteConnsMaxOpen(conns int) Option {
   227  	return func(po *postgresOptions) { po.writePoolOpts.MaxOpenConns = &conns }
   228  }
   229  
   230  // WatchBufferLength is the number of entries that can be stored in the watch
   231  // buffer while awaiting read by the client.
   232  //
   233  // This value defaults to 128.
   234  func WatchBufferLength(watchBufferLength uint16) Option {
   235  	return func(po *postgresOptions) { po.watchBufferLength = watchBufferLength }
   236  }
   237  
   238  // WatchBufferWriteTimeout is the maximum timeout for writing to the watch buffer,
   239  // after which the caller to the watch will be disconnected.
   240  func WatchBufferWriteTimeout(watchBufferWriteTimeout time.Duration) Option {
   241  	return func(po *postgresOptions) { po.watchBufferWriteTimeout = watchBufferWriteTimeout }
   242  }
   243  
   244  // RevisionQuantization is the time bucket size to which advertised
   245  // revisions will be rounded.
   246  //
   247  // This value defaults to 5 seconds.
   248  func RevisionQuantization(quantization time.Duration) Option {
   249  	return func(po *postgresOptions) { po.revisionQuantization = quantization }
   250  }
   251  
   252  // MaxRevisionStalenessPercent is the amount of time, expressed as a percentage of
   253  // the revision quantization window, that a previously computed rounded revision
   254  // can still be advertised after the next rounded revision would otherwise be ready.
   255  //
   256  // This value defaults to 0.1 (10%).
   257  func MaxRevisionStalenessPercent(stalenessPercent float64) Option {
   258  	return func(po *postgresOptions) { po.maxRevisionStalenessPercent = stalenessPercent }
   259  }
   260  
   261  // GCWindow is the maximum age of a passed revision that will be considered
   262  // valid.
   263  //
   264  // This value defaults to 24 hours.
   265  func GCWindow(window time.Duration) Option {
   266  	return func(po *postgresOptions) { po.gcWindow = window }
   267  }
   268  
   269  // GCInterval is the the interval at which garbage collection will occur.
   270  //
   271  // This value defaults to 3 minutes.
   272  func GCInterval(interval time.Duration) Option {
   273  	return func(po *postgresOptions) { po.gcInterval = interval }
   274  }
   275  
   276  // GCMaxOperationTime is the maximum operation time of a garbage collection
   277  // pass before it times out.
   278  //
   279  // This value defaults to 1 minute.
   280  func GCMaxOperationTime(time time.Duration) Option {
   281  	return func(po *postgresOptions) { po.gcMaxOperationTime = time }
   282  }
   283  
   284  // MaxRetries is the maximum number of times a retriable transaction will be
   285  // client-side retried.
   286  // Default: 10
   287  func MaxRetries(maxRetries uint8) Option {
   288  	return func(po *postgresOptions) { po.maxRetries = maxRetries }
   289  }
   290  
   291  // WithEnablePrometheusStats marks whether Prometheus metrics provided by the Postgres
   292  // clients being used by the datastore are enabled.
   293  //
   294  // Prometheus metrics are disabled by default.
   295  func WithEnablePrometheusStats(enablePrometheusStats bool) Option {
   296  	return func(po *postgresOptions) { po.enablePrometheusStats = enablePrometheusStats }
   297  }
   298  
   299  // EnableTracing enables trace-level logging for the Postgres clients being
   300  // used by the datastore.
   301  //
   302  // Tracing is disabled by default.
   303  func EnableTracing() Option {
   304  	return func(po *postgresOptions) { po.logger = &tracingLogger{} }
   305  }
   306  
   307  // GCEnabled indicates whether garbage collection is enabled.
   308  //
   309  // GC is enabled by default.
   310  func GCEnabled(isGCEnabled bool) Option {
   311  	return func(po *postgresOptions) { po.gcEnabled = isGCEnabled }
   312  }
   313  
   314  // DebugAnalyzeBeforeStatistics signals to the Statistics method that it should
   315  // run Analyze on the database before returning statistics. This should only be
   316  // used for debug and testing.
   317  //
   318  // Disabled by default.
   319  func DebugAnalyzeBeforeStatistics() Option {
   320  	return func(po *postgresOptions) { po.analyzeBeforeStatistics = true }
   321  }
   322  
   323  // WithQueryInterceptor adds an interceptor to all underlying postgres queries
   324  //
   325  // By default, no query interceptor is used.
   326  func WithQueryInterceptor(interceptor pgxcommon.QueryInterceptor) Option {
   327  	return func(po *postgresOptions) {
   328  		po.queryInterceptor = interceptor
   329  	}
   330  }
   331  
   332  // MigrationPhase configures the postgres driver to the proper state of a
   333  // multi-phase migration.
   334  //
   335  // Steady-state configuration (e.g. fully migrated) by default
   336  func MigrationPhase(phase string) Option {
   337  	return func(po *postgresOptions) { po.migrationPhase = phase }
   338  }
   339  
   340  // CredentialsProviderName is the name of the CredentialsProvider implementation to use
   341  // for dynamically retrieving the datastore credentials at runtime
   342  //
   343  // Empty by default.
   344  func CredentialsProviderName(credentialsProviderName string) Option {
   345  	return func(po *postgresOptions) { po.credentialsProviderName = credentialsProviderName }
   346  }