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 }