Skip to content

Commit 3698360

Browse files
authored
CBG-5357: removal of fast fail retry by default (#8308)
1 parent 33d9bf9 commit 3698360

13 files changed

Lines changed: 229 additions & 151 deletions

base/bootstrap.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"encoding/json"
1414
"errors"
1515
"fmt"
16+
"net/http"
1617
"reflect"
1718
"sort"
1819
"sync"
@@ -91,13 +92,14 @@ type BootstrapConnection interface {
9192

9293
// CouchbaseClusterSpec define how to make a connection to Couchbase Server
9394
type CouchbaseClusterSpec struct {
94-
Server string // connection string to connect to the Couchbase cluster
95-
Username string // RBAC username to authenticate with the cluster
96-
Password string // RBAC password to authenticate with the cluster
97-
X509Certpath string // X.509 cert path to authenticate with the cluster
98-
X509Keypath string // X.509 key path to authenticate with the cluster
99-
CACertpath string // CA cert path to use for TLS connections
100-
TLSSkipVerify bool // If true, do not validate TLS certificate
95+
Server string // connection string to connect to the Couchbase cluster
96+
Username string // RBAC username to authenticate with the cluster
97+
Password string // RBAC password to authenticate with the cluster
98+
X509Certpath string // X.509 cert path to authenticate with the cluster
99+
X509Keypath string // X.509 key path to authenticate with the cluster
100+
CACertpath string // CA cert path to use for TLS connections
101+
TLSSkipVerify bool // If true, do not validate TLS certificate
102+
UseGOCBFastFailRetry bool // When true, readiness checks fail fast instead of using the best-effort retry strategy
101103
}
102104

103105
// CouchbaseCluster is a GoCBv2 implementation of BootstrapConnection
@@ -117,6 +119,7 @@ type CouchbaseCluster struct {
117119
// Resolved lazily on first interaction (or eagerly via SetBucketBootstrapTargetHint). Values
118120
// are bucketBootstrapTarget; absence of an entry means "fall back to the connection-wide flag."
119121
bucketBootstrapTargets sync.Map
122+
useGOCBFastFailRetry bool // When true, readiness checks fail fast instead of using the best-effort retry strategy
120123
}
121124

122125
// bucketBootstrapTarget records where bootstrap docs (registry, dbconfig, cbgt cfg) live for a
@@ -255,6 +258,7 @@ func NewCouchbaseCluster(ctx context.Context, clusterSpec CouchbaseClusterSpec,
255258
clusterOptions: clusterOptions,
256259
bucketConnectionMode: bucketMode,
257260
useSystemMetadataCollection: useSystemMetadataCollection,
261+
useGOCBFastFailRetry: clusterSpec.UseGOCBFastFailRetry,
258262
}
259263

260264
if bucketMode == CachedClusterConnections {
@@ -287,7 +291,7 @@ func (cc *CouchbaseCluster) connect(auth *gocb.Authenticator) (*gocb.Cluster, er
287291
err = cluster.WaitUntilReady(time.Second*10, &gocb.WaitUntilReadyOptions{
288292
DesiredState: gocb.ClusterStateOnline,
289293
ServiceTypes: []gocb.ServiceType{gocb.ServiceTypeManagement},
290-
RetryStrategy: &goCBv2FailFastRetryStrategy{},
294+
RetryStrategy: gocbRetryStrategy(cc.useGOCBFastFailRetry),
291295
})
292296
if err != nil {
293297
_ = cluster.Close(nil)
@@ -1049,7 +1053,7 @@ func (cc *CouchbaseCluster) connectToBucket(ctx context.Context, bucketName stri
10491053
b = connection.Bucket(bucketName)
10501054
err = b.WaitUntilReady(time.Second*10, &gocb.WaitUntilReadyOptions{
10511055
DesiredState: gocb.ClusterStateOnline,
1052-
RetryStrategy: &goCBv2FailFastRetryStrategy{},
1056+
RetryStrategy: gocbRetryStrategy(cc.useGOCBFastFailRetry),
10531057
ServiceTypes: []gocb.ServiceType{gocb.ServiceTypeKeyValue},
10541058
})
10551059
if err != nil {
@@ -1059,6 +1063,13 @@ func (cc *CouchbaseCluster) connectToBucket(ctx context.Context, bucketName stri
10591063
return nil, nil, ErrAuthError
10601064
}
10611065

1066+
// In best-effort retry mode a missing/unreachable bucket surfaces as a timeout rather than an
1067+
// auth failure. Classify it as a connection error so callers translate it to a 502 instead of a
1068+
// generic 500, mirroring the per-database connection path (see db.connectToBucketErrorHandling).
1069+
if !cc.useGOCBFastFailRetry {
1070+
return nil, nil, HTTPErrorf(http.StatusBadGateway,
1071+
"Unable to connect to Couchbase Server. Please ensure it is running and reachable, and that bucket %q exists. Error: %s", MD(bucketName).Redact(), err)
1072+
}
10621073
return nil, nil, err
10631074
}
10641075

base/bucket.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ type BucketSpec struct {
121121
ViewQueryTimeoutSecs *uint32 // the view query timeout in seconds (default: 75 seconds)
122122
MaxConcurrentQueryOps *int // maximum number of concurrent query operations (default: DefaultMaxConcurrentQueryOps)
123123
BucketOpTimeout *time.Duration // How long bucket ops should block returning "operation timed out". If nil, uses GoCB default. GoCB buckets only.
124+
FastFailOnInitialConnection bool // When true, gocb cluster and bucket readiness checks fail fast instead of using the best-effort retry strategy
124125
}
125126

126127
const defaultNumRetries = 10

base/bucket_gocb_test.go

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2087,31 +2087,41 @@ func TestCouchbaseServerIncorrectLogin(t *testing.T) {
20872087
if UnitTestUrlIsWalrus() {
20882088
t.Skip("This test only works against Couchbase Server")
20892089
}
2090+
LongRunningTest(t)
20902091

20912092
ctx := TestCtx(t)
20922093
for _, tls := range []bool{true, false} {
2093-
t.Run(fmt.Sprintf("tls=%v", tls), func(t *testing.T) {
2094-
testBucket := GetTestBucket(t)
2095-
defer testBucket.Close(ctx)
2096-
2097-
// Override test bucket spec with invalid creds
2098-
testBucket.BucketSpec.Auth = TestAuthenticator{
2099-
Username: "invalid_username",
2100-
Password: "invalid_password",
2101-
BucketName: testBucket.BucketSpec.BucketName,
2102-
}
2103-
if tls {
2104-
testBucket.BucketSpec.Server = strings.ReplaceAll(testBucket.BucketSpec.Server, "couchbase://", "couchbases://")
2105-
testBucket.BucketSpec.TLSSkipVerify = true // test env isn't always using valid certs
2106-
} else {
2107-
testBucket.BucketSpec.Server = strings.ReplaceAll(testBucket.BucketSpec.Server, "couchbases://", "couchbase://")
2108-
}
2094+
for _, fastFail := range []bool{true, false} {
2095+
t.Run(fmt.Sprintf("tls=%v/fastFail=%v", tls, fastFail), func(t *testing.T) {
2096+
testBucket := GetTestBucket(t)
2097+
defer testBucket.Close(ctx)
2098+
2099+
// Override test bucket spec with invalid creds
2100+
testBucket.BucketSpec.Auth = TestAuthenticator{
2101+
Username: "invalid_username",
2102+
Password: "invalid_password",
2103+
BucketName: testBucket.BucketSpec.BucketName,
2104+
}
2105+
testBucket.BucketSpec.FastFailOnInitialConnection = fastFail
2106+
if tls {
2107+
testBucket.BucketSpec.Server = strings.ReplaceAll(testBucket.BucketSpec.Server, "couchbase://", "couchbases://")
2108+
testBucket.BucketSpec.TLSSkipVerify = true // test env isn't always using valid certs
2109+
} else {
2110+
testBucket.BucketSpec.Server = strings.ReplaceAll(testBucket.BucketSpec.Server, "couchbases://", "couchbase://")
2111+
}
21092112

2110-
// Attempt to open the bucket again using invalid creds. We should expect an error.
2111-
bucket, err := GetBucket(TestCtx(t), testBucket.BucketSpec)
2112-
assert.Equal(t, ErrAuthError, err)
2113-
assert.Nil(t, bucket)
2114-
})
2113+
// Attempt to open the bucket again using invalid creds. We should expect an error.
2114+
bucket, err := GetBucket(TestCtx(t), testBucket.BucketSpec)
2115+
assert.Nil(t, bucket)
2116+
if fastFail {
2117+
// Fail-fast strategy surfaces the authentication failure directly.
2118+
assert.Equal(t, ErrAuthError, err)
2119+
} else {
2120+
// Best-effort strategy retries the auth failure until WaitUntilReady times out.
2121+
assert.ErrorIs(t, err, gocb.ErrUnambiguousTimeout)
2122+
}
2123+
})
2124+
}
21152125
}
21162126
}
21172127

base/collection.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func GetGoCBv2Bucket(ctx context.Context, spec BucketSpec) (*GocbV2Bucket, error
7777
err = cluster.WaitUntilReady(time.Second*30, &gocb.WaitUntilReadyOptions{
7878
DesiredState: gocb.ClusterStateOnline,
7979
ServiceTypes: []gocb.ServiceType{gocb.ServiceTypeManagement},
80-
RetryStrategy: &goCBv2FailFastRetryStrategy{},
80+
RetryStrategy: gocbRetryStrategy(spec.FastFailOnInitialConnection),
8181
})
8282

8383
if err != nil {
@@ -89,7 +89,7 @@ func GetGoCBv2Bucket(ctx context.Context, spec BucketSpec) (*GocbV2Bucket, error
8989
return nil, err
9090
}
9191

92-
return GetGocbV2BucketFromCluster(ctx, cluster, spec, connString, time.Second*30, true)
92+
return GetGocbV2BucketFromCluster(ctx, cluster, spec, connString, time.Second*30, spec.FastFailOnInitialConnection)
9393

9494
}
9595

@@ -111,14 +111,8 @@ func GetGocbV2BucketFromCluster(ctx context.Context, cluster *gocb.Cluster, spec
111111
// Connect to bucket
112112
bucket := cluster.Bucket(spec.BucketName)
113113

114-
var retryStrategy gocb.RetryStrategy
115-
if failFast {
116-
retryStrategy = &goCBv2FailFastRetryStrategy{}
117-
} else {
118-
retryStrategy = gocb.NewBestEffortRetryStrategy(nil)
119-
}
120114
err := bucket.WaitUntilReady(waitUntilReady, &gocb.WaitUntilReadyOptions{
121-
RetryStrategy: retryStrategy,
115+
RetryStrategy: gocbRetryStrategy(failFast),
122116
})
123117
if err != nil {
124118
_ = cluster.Close(&gocb.ClusterCloseOptions{})

base/collection_n1ql_common.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@ type indexManager struct {
109109
}
110110

111111
func (im *indexManager) GetAllIndexes() ([]gocb.QueryIndex, error) {
112-
opts := &gocb.GetAllQueryIndexesOptions{
113-
RetryStrategy: &goCBv2FailFastRetryStrategy{},
114-
}
112+
opts := &gocb.GetAllQueryIndexesOptions{}
115113

116114
if im.collection != nil {
117115
return im.collection.GetAllIndexes(opts)

base/gocb_utils.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ func (rs *goCBv2FailFastRetryStrategy) RetryAfter(req gocb.RetryRequest, reason
8383
return &gocb.NoRetryRetryAction{}
8484
}
8585

86+
// gocbRetryStrategy returns the fail-fast retry strategy when useFailFast is true, otherwise the best-effort
87+
// strategy that retries until the operation's timeout. Gated by the unsupported.use_gocb_fast_fail_retry config.
88+
func gocbRetryStrategy(useFailFast bool) gocb.RetryStrategy {
89+
if useFailFast {
90+
return &goCBv2FailFastRetryStrategy{}
91+
}
92+
return gocb.NewBestEffortRetryStrategy(nil)
93+
}
94+
8695
// GOCBCORE Utilities
8796

8897
// CertificateAuthenticator allows for certificate auth in gocbcore

base/gocb_utils_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package base
1111
import (
1212
"testing"
1313

14+
"github.com/couchbase/gocb/v2"
1415
"github.com/stretchr/testify/require"
1516

1617
"github.com/stretchr/testify/assert"
@@ -95,3 +96,8 @@ func TestGoCBCoreAuthConfigInvalidPaths(t *testing.T) {
9596
_, err := GoCBCoreAuthConfig("", "", "/non/existent/cert", "/non/existent/key")
9697
assert.Error(t, err)
9798
}
99+
100+
func TestGoCBRetryStrategy(t *testing.T) {
101+
assert.IsType(t, &goCBv2FailFastRetryStrategy{}, gocbRetryStrategy(true))
102+
assert.IsType(t, gocb.NewBestEffortRetryStrategy(nil), gocbRetryStrategy(false))
103+
}

docs/api/components/schemas.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,6 +2493,10 @@ Startup-config:
24932493
description: Store database configurations in system xattrs
24942494
type: boolean
24952495
default: false
2496+
use_gocb_fast_fail_retry:
2497+
description: When true, errors on initial connection to Couchbase Server will fail instantaneously. Enabling this will surface authentication errors quickly, but can cause some Sync Gateway operations to shut down databases with intermittent Couchbase Server connection errors.
2498+
type: boolean
2499+
default: false
24962500
readOnly: true
24972501
couchbase_keepalive_interval:
24982502
description: TCP keep-alive interval between SG and Couchbase server. This is unused.

0 commit comments

Comments
 (0)