Skip to content

Commit 31b4830

Browse files
committed
Avoid BSON unmarshal in extractTimestampFromResumeToken.
Unmarshaling is slow due to reflection. This function gets called frequently enough to justify the optimization.
1 parent a544bf1 commit 31b4830

File tree

2 files changed

+60
-7
lines changed

2 files changed

+60
-7
lines changed

internal/verifier/change_stream.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/10gen/migration-verifier/internal/retry"
1111
"github.com/10gen/migration-verifier/internal/types"
1212
"github.com/10gen/migration-verifier/internal/util"
13+
"github.com/10gen/migration-verifier/mbson"
1314
"github.com/10gen/migration-verifier/msync"
1415
"github.com/10gen/migration-verifier/option"
1516
mapset "github.com/deckarep/golang-set/v2"
@@ -857,17 +858,19 @@ func (csr *ChangeStreamReader) persistChangeStreamResumeToken(ctx context.Contex
857858
}
858859

859860
func extractTimestampFromResumeToken(resumeToken bson.Raw) (primitive.Timestamp, error) {
860-
tokenStruct := struct {
861-
Data string `bson:"_data"`
862-
}{}
863-
864861
// Change stream token is always a V1 keystring in the _data field
865-
err := bson.Unmarshal(resumeToken, &tokenStruct)
862+
tokenDataRV, err := resumeToken.LookupErr("_data")
863+
864+
if err != nil {
865+
return primitive.Timestamp{}, errors.Wrapf(err, "extracting %#q from resume token (%v)", "_data", resumeToken)
866+
}
867+
868+
tokenData, err := mbson.CastRawValue[string](tokenDataRV)
866869
if err != nil {
867-
return primitive.Timestamp{}, errors.Wrapf(err, "failed to extract %#q from resume token (%v)", "_data", resumeToken)
870+
return primitive.Timestamp{}, errors.Wrapf(err, "parsing resume token (%v)", "_data", resumeToken)
868871
}
869872

870-
resumeTokenBson, err := keystring.KeystringToBson(keystring.V1, tokenStruct.Data)
873+
resumeTokenBson, err := keystring.KeystringToBson(keystring.V1, tokenData)
871874
if err != nil {
872875
return primitive.Timestamp{}, err
873876
}

mbson/raw_value.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package mbson
2+
3+
import (
4+
"fmt"
5+
6+
"go.mongodb.org/mongo-driver/bson"
7+
"go.mongodb.org/mongo-driver/bson/bsontype"
8+
"go.mongodb.org/mongo-driver/bson/primitive"
9+
)
10+
11+
type bsonType interface {
12+
bson.Raw | primitive.Timestamp | string
13+
}
14+
15+
type cannotCastErr struct {
16+
gotBSONType bsontype.Type
17+
toGoType any
18+
}
19+
20+
func (ce cannotCastErr) Error() string {
21+
return fmt.Sprintf("cannot cast BSON %s to %T", ce.gotBSONType, ce.toGoType)
22+
}
23+
24+
// CastRawValue is a “one-stop-shop” interface around bson.RawValue’s various
25+
// casting interfaces. Unlike those functions, though, this returns an error
26+
// if the target type doesn’t match the value.
27+
//
28+
// Augment bsonType if you find a type here that’s missing.
29+
func CastRawValue[T bsonType](in bson.RawValue) (T, error) {
30+
retPtr := new(T)
31+
32+
switch any(*retPtr).(type) {
33+
case bson.Raw:
34+
if doc, isDoc := in.DocumentOK(); isDoc {
35+
return any(doc).(T), nil
36+
}
37+
case primitive.Timestamp:
38+
if t, i, ok := in.TimestampOK(); ok {
39+
return any(primitive.Timestamp{t, i}).(T), nil
40+
}
41+
case string:
42+
if str, ok := in.StringValueOK(); ok {
43+
return any(str).(T), nil
44+
}
45+
default:
46+
panic(fmt.Sprintf("Unrecognized Go type: %T (maybe augment bsonType?)", *retPtr))
47+
}
48+
49+
return *retPtr, cannotCastErr{in.Type, *retPtr}
50+
}

0 commit comments

Comments
 (0)