fix(couchbase): improve readiness timeout

This commit is contained in:
2026-06-28 19:02:31 +07:00
parent 6b0c2f5a4c
commit 0ad7fbebfc
5 changed files with 50 additions and 3 deletions
+2
View File
@@ -96,6 +96,8 @@ On startup each adapter initializes the minimum resource it owns, then every tic
Each configured service starts independently. If one service cannot connect, it logs the error and retries without stopping other services in the same deployment.
For hosted Couchbase/Capella clusters, `ready_timeout` defaults to `2m`. If Couchbase still reports `CONNECTION_ERROR`, check the connection string, bucket name, database user permissions, and Capella allowed IP/network access.
## Adding a new adapter
1. Create `adapter/<name>.go`.
+1 -1
View File
@@ -93,7 +93,7 @@ func (a *couchbaseAdapter) Connect(ctx context.Context) error {
}
b := cluster.Bucket(a.bucket)
if err := b.WaitUntilReady(a.readyTimeout, &gocb.WaitUntilReadyOptions{Context: ctx}); err != nil {
return err
return a.bucketReadyError(err)
}
if err := a.ensureScopeAndCollection(ctx, b); err != nil {
return err
+10 -1
View File
@@ -9,7 +9,16 @@ import (
"github.com/couchbase/gocb/v2"
)
const defaultCouchbaseReadyTimeout = 30 * time.Second
const defaultCouchbaseReadyTimeout = 2 * time.Minute
func (a *couchbaseAdapter) bucketReadyError(err error) error {
return fmt.Errorf(
"bucket %q was not ready after %s: %w; check bucket_name, connection_string, database user permissions, and Capella allowed IP/network access; increase ready_timeout if the cluster or bucket was just created",
a.bucket,
a.readyTimeout,
err,
)
}
func (a *couchbaseAdapter) ensureBucket(ctx context.Context, cluster *gocb.Cluster) error {
if a.bucketRAMQuotaMB == 0 {
+36
View File
@@ -1,6 +1,8 @@
package adapter
import (
"errors"
"strings"
"testing"
"time"
)
@@ -29,6 +31,25 @@ func TestCouchbaseFactoryParsesInitializationOptions(t *testing.T) {
}
}
func TestCouchbaseFactoryUsesHostedReadyTimeoutDefault(t *testing.T) {
a, err := New("couchbase", Config{
"connection_string": "couchbases://cb.example.com",
"username": "user",
"password": "pass",
"bucket_name": "keepalive",
"scope_name": "scope",
"collection_name": "collection",
})
if err != nil {
t.Fatalf("New returned error: %v", err)
}
got := a.(*couchbaseAdapter)
if got.readyTimeout != 2*time.Minute {
t.Fatalf("readyTimeout = %s, want 2m", got.readyTimeout)
}
}
func TestCouchbaseFactoryRejectsInvalidReadyTimeout(t *testing.T) {
_, err := New("couchbase", Config{
"connection_string": "couchbases://cb.example.com",
@@ -43,3 +64,18 @@ func TestCouchbaseFactoryRejectsInvalidReadyTimeout(t *testing.T) {
t.Fatal("New returned nil error")
}
}
func TestCouchbaseBucketReadyErrorAddsActionableContext(t *testing.T) {
cause := errors.New("unambiguous timeout")
a := couchbaseAdapter{bucket: "keepalive", readyTimeout: 2 * time.Minute}
err := a.bucketReadyError(cause)
if !errors.Is(err, cause) {
t.Fatalf("bucketReadyError does not wrap cause: %v", err)
}
for _, want := range []string{"keepalive", "2m0s", "Capella allowed IP", "ready_timeout"} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("bucketReadyError() = %q, want it to contain %q", err, want)
}
}
}
+1 -1
View File
@@ -25,6 +25,6 @@ services:
bucket_name: keepalive
scope_name: _default
collection_name: _default
ready_timeout: 30s
ready_timeout: 2m
# Optional. Set only when this user can create a missing bucket.
# bucket_ram_quota_mb: 128