Skip to content

Commit 2a4add5

Browse files
authored
perf(spanner): improve mutationProto allocations and performance (#12740)
* perf(spanner): improve mutationProto allocations and performance * fix test
1 parent ce9d29b commit 2a4add5

File tree

2 files changed

+76
-29
lines changed

2 files changed

+76
-29
lines changed

spanner/mutation.go

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"fmt"
2121
"math/rand"
2222
"reflect"
23-
"time"
2423

2524
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
2625
"google.golang.org/grpc/codes"
@@ -470,41 +469,39 @@ func (m Mutation) proto() (*sppb.Mutation, error) {
470469
// mutationsProto turns a spanner.Mutation array into a sppb.Mutation array,
471470
// it is convenient for sending batch mutations to Cloud Spanner.
472471
func mutationsProto(ms []*Mutation) ([]*sppb.Mutation, *sppb.Mutation, error) {
473-
var selectedMutation *Mutation
474-
var nonInsertMutations []*Mutation
475-
476-
l := make([]*sppb.Mutation, 0, len(ms))
477-
for _, m := range ms {
478-
if m.op != opInsert {
479-
nonInsertMutations = append(nonInsertMutations, m)
480-
}
481-
if selectedMutation == nil {
482-
selectedMutation = m
483-
}
484-
// Track the INSERT mutation with the highest number of values if only INSERT mutation were found
485-
if selectedMutation.op == opInsert && m.op == opInsert && len(m.values) > len(selectedMutation.values) {
486-
selectedMutation = m
487-
}
488-
489-
// Convert the mutation to sppb.Mutation and add to the list
472+
n := len(ms)
473+
out := make([]*sppb.Mutation, 0, n)
474+
if n == 0 {
475+
return out, nil, nil
476+
}
477+
maxInsertIdx := -1
478+
maxInsertVals := -1
479+
nonInsertCount := 0
480+
selectedNonInsertIdx := -1
481+
for i, m := range ms {
490482
pb, err := m.proto()
491483
if err != nil {
492484
return nil, nil, err
493485
}
494-
l = append(l, pb)
486+
out = append(out, pb)
487+
if m.op == opInsert {
488+
if v := len(m.values); v >= maxInsertVals {
489+
maxInsertVals, maxInsertIdx = v, i
490+
}
491+
continue
492+
}
493+
nonInsertCount++
494+
if rand.Intn(nonInsertCount) == 0 {
495+
selectedNonInsertIdx = i
496+
}
495497
}
496-
if len(nonInsertMutations) > 0 {
497-
selectedMutation = nonInsertMutations[rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(nonInsertMutations))]
498+
if nonInsertCount > 0 {
499+
return out, out[selectedNonInsertIdx], nil
498500
}
499-
if selectedMutation != nil {
500-
m, err := selectedMutation.proto()
501-
if err != nil {
502-
return nil, nil, err
503-
}
504-
return l, m, nil
501+
if maxInsertIdx >= 0 {
502+
return out, out[maxInsertIdx], nil
505503
}
506-
507-
return l, nil, nil
504+
return out, nil, nil
508505
}
509506

510507
// mutationGroupsProto turns a spanner.MutationGroup array into a

spanner/mutation_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,3 +885,53 @@ func TestEncodeMutationGroupArray(t *testing.T) {
885885
}
886886
}
887887
}
888+
889+
func BenchmarkMutationsProto(b *testing.B) {
890+
type benchmarkCase struct {
891+
name string
892+
mutations []*Mutation
893+
}
894+
benchmarkCases := []benchmarkCase{
895+
{
896+
name: "small number of mutations",
897+
mutations: []*Mutation{
898+
Insert("t_foo", []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}),
899+
Update("t_foo", []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}),
900+
InsertOrUpdate("t_foo", []string{"col1", "col2"}, []interface{}{1.0, 2.0}),
901+
Replace("t_foo", []string{"col1", "col2"}, []interface{}{"one", 2.0}),
902+
Delete("t_foo", Key{"foo"}),
903+
},
904+
},
905+
{
906+
name: "large number of mutations",
907+
mutations: func() []*Mutation {
908+
var mutations []*Mutation
909+
for i := 0; i < 20; i++ {
910+
mutations = append(mutations, Insert("t_foo", []string{"col1", "col2"}, []interface{}{int64(i), int64(i + 1)}))
911+
mutations = append(mutations, Update("t_foo", []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}))
912+
mutations = append(mutations, InsertOrUpdate("t_foo", []string{"col1", "col2"}, []interface{}{1.0, 2.0}))
913+
mutations = append(mutations, Replace("t_foo", []string{"col1", "col2"}, []interface{}{"one", 2.0}))
914+
mutations = append(mutations, Delete("t_foo", Key{i}))
915+
}
916+
return mutations
917+
}(),
918+
},
919+
{
920+
name: "mixed type of mutations",
921+
mutations: []*Mutation{
922+
Insert("t_foo", []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}),
923+
Update("t_foo", []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}),
924+
Delete("t_foo", Key{"foo"}),
925+
Insert("t_bar", []string{"col1"}, []interface{}{"bar"}),
926+
},
927+
},
928+
}
929+
930+
for _, bc := range benchmarkCases {
931+
b.Run(bc.name, func(b *testing.B) {
932+
for i := 0; i < b.N; i++ {
933+
_, _, _ = mutationsProto(bc.mutations)
934+
}
935+
})
936+
}
937+
}

0 commit comments

Comments
 (0)