Skip to content

Commit 1c4cff8

Browse files
committed
gcs: add error classification
1 parent fa9c3c0 commit 1c4cff8

4 files changed

Lines changed: 58 additions & 6 deletions

File tree

flow/alerting/classifier.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const (
8888
ErrorSourceMySQL ErrorSource = "mysql"
8989
ErrorSourceMongoDB ErrorSource = "mongodb"
9090
ErrorSourceBigQuery ErrorSource = "bigquery"
91+
ErrorSourceGCS ErrorSource = "gcs"
9192
ErrorSourcePostgresCatalog ErrorSource = "postgres_catalog"
9293
ErrorSourceSSH ErrorSource = "ssh_tunnel"
9394
ErrorSourceNet ErrorSource = "net"
@@ -943,6 +944,23 @@ func GetErrorClass(ctx context.Context, err error) (ErrorClass, ErrorInfo) {
943944
return ErrorOther, bqErrorInfo
944945
}
945946

947+
if _, ok := errors.AsType[*exceptions.GCSError](err); ok {
948+
gcsErrorInfo := ErrorInfo{
949+
Source: ErrorSourceGCS,
950+
Code: "UNKNOWN",
951+
}
952+
if apiErr, ok := errors.AsType[*googleapi.Error](err); ok {
953+
gcsErrorInfo.Code = strconv.Itoa(apiErr.Code)
954+
switch apiErr.Code {
955+
case 503: // Service Unavailable
956+
return ErrorRetryRecoverable, gcsErrorInfo
957+
default:
958+
return ErrorOther, gcsErrorInfo
959+
}
960+
}
961+
return ErrorOther, gcsErrorInfo
962+
}
963+
946964
if chException, ok := errors.AsType[*clickhouse.Exception](err); ok {
947965
chErrorInfo := ErrorInfo{
948966
Source: ErrorSourceClickHouse,

flow/alerting/classifier_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,22 @@ func TestBigQueryGCSBucketNotExistShouldBeConnectivity(t *testing.T) {
11881188
}, errInfo)
11891189
}
11901190

1191+
func TestGCSTransientErrorsShouldBeRecoverable(t *testing.T) {
1192+
apiErr := &googleapi.Error{
1193+
Code: 503,
1194+
Message: "We encountered an internal error. Please try again.",
1195+
Errors: []googleapi.ErrorItem{{Reason: "backendError"}},
1196+
}
1197+
err := exceptions.NewGCSError(fmt.Errorf("failed to finalize GCS upload for a.avro: %w", apiErr))
1198+
errorClass, errInfo := GetErrorClass(t.Context(), fmt.Errorf(
1199+
"failed to push records: failed to upload to staging: %w", err))
1200+
assert.Equal(t, ErrorRetryRecoverable, errorClass)
1201+
assert.Equal(t, ErrorInfo{
1202+
Source: ErrorSourceGCS,
1203+
Code: strconv.Itoa(503),
1204+
}, errInfo)
1205+
}
1206+
11911207
func TestBigQueryUnclassifiedCodeShouldBeOther(t *testing.T) {
11921208
t.Parallel()
11931209

flow/connectors/clickhouse/staging_gcs.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
peerdb_clickhouse "github.com/PeerDB-io/peerdb/flow/pkg/clickhouse"
1818
"github.com/PeerDB-io/peerdb/flow/pkg/objectstore"
1919
"github.com/PeerDB-io/peerdb/flow/shared"
20+
"github.com/PeerDB-io/peerdb/flow/shared/exceptions"
2021
)
2122

2223
const gcsSignedURLExpiry = 1 * time.Hour
@@ -50,7 +51,7 @@ func newGCSStagingStore(ctx context.Context, bucketName string) (StagingStore, e
5051
// Uses Application Default Credentials (Workload Identity on GKE)
5152
client, err := storage.NewClient(ctx)
5253
if err != nil {
53-
return nil, fmt.Errorf("failed to create GCS client: %w", err)
54+
return nil, exceptions.NewGCSError(fmt.Errorf("failed to create GCS client: %w", err))
5455
}
5556

5657
return &gcsStagingStore{
@@ -69,10 +70,10 @@ func (g *gcsStagingStore) Upload(ctx context.Context, env map[string]string, key
6970

7071
if _, err := io.Copy(w, body); err != nil {
7172
w.Close()
72-
return fmt.Errorf("failed to write to GCS object %s: %w", key, err)
73+
return exceptions.NewGCSError(fmt.Errorf("failed to write to GCS object %s: %w", key, err))
7374
}
7475
if err := w.Close(); err != nil {
75-
return fmt.Errorf("failed to finalize GCS upload for %s: %w", key, err)
76+
return exceptions.NewGCSError(fmt.Errorf("failed to finalize GCS upload for %s: %w", key, err))
7677
}
7778

7879
logger.Info("finished GCS upload", slog.String("key", key))
@@ -85,7 +86,7 @@ func (g *gcsStagingStore) TableFunctionExpr(ctx context.Context, key string, for
8586
Expires: time.Now().Add(gcsSignedURLExpiry),
8687
})
8788
if err != nil {
88-
return "", fmt.Errorf("failed to generate signed URL for gs://%s/%s: %w", g.bucket, key, err)
89+
return "", exceptions.NewGCSError(fmt.Errorf("failed to generate signed URL for gs://%s/%s: %w", g.bucket, key, err))
8990
}
9091

9192
var expr strings.Builder
@@ -109,10 +110,10 @@ func (g *gcsStagingStore) DeletePrefix(ctx context.Context, prefix string) error
109110
break
110111
}
111112
if err != nil {
112-
return fmt.Errorf("failed to list GCS objects: %w", err)
113+
return exceptions.NewGCSError(fmt.Errorf("failed to list GCS objects: %w", err))
113114
}
114115
if err := g.client.Bucket(g.bucket).Object(attrs.Name).Delete(ctx); err != nil {
115-
return fmt.Errorf("failed to delete GCS object %s: %w", attrs.Name, err)
116+
return exceptions.NewGCSError(fmt.Errorf("failed to delete GCS object %s: %w", attrs.Name, err))
116117
}
117118
}
118119

flow/shared/exceptions/gcs.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package exceptions
2+
3+
type GCSError struct {
4+
error
5+
}
6+
7+
func NewGCSError(err error) *GCSError {
8+
return &GCSError{err}
9+
}
10+
11+
func (e *GCSError) Error() string {
12+
return "GCS Error: " + e.error.Error()
13+
}
14+
15+
func (e *GCSError) Unwrap() error {
16+
return e.error
17+
}

0 commit comments

Comments
 (0)