Skip to content

Commit 9986b95

Browse files
Improve metadata unmarshal performance
While profiling a application that uses goengine. The amount of memory used for Unmarshalling metadata was extreme. By using easyjson we can remove the `map[string]interface{}` unmarshal and enjoy the speed and memory reduction is library offers. Before this PR the add benchmark was: ``` BenchmarkJSONMetadata_UnmarshalJSON-4 300000 4313 ns/op 1208 B/op 29 allocs/op ``` With the new and improved version it has become: ``` BenchmarkJSONMetadata_UnmarshalJSON-4 1000000 1773 ns/op 440 B/op 12 allocs/op ```
1 parent c993d8e commit 9986b95

File tree

5 files changed

+65
-10
lines changed

5 files changed

+65
-10
lines changed

Gopkg.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

metadata/metadata.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package metadata
22

3-
import "encoding/json"
3+
import (
4+
"encoding/json"
5+
6+
"github.com/mailru/easyjson/jlexer"
7+
)
48

59
// Metadata is an immutable map[string]interface{} implementation
610
type Metadata interface {
@@ -117,12 +121,34 @@ func (j JSONMetadata) MarshalJSON() ([]byte, error) {
117121

118122
// UnmarshalJSON unmarshal the json into Metdadata
119123
func (j *JSONMetadata) UnmarshalJSON(data []byte) error {
120-
var valueMap map[string]interface{}
121-
err := json.Unmarshal(data, &valueMap)
122-
if err != nil {
123-
return err
124+
r := jlexer.Lexer{Data: data}
125+
j.UnmarshalEasyJSON(&r)
126+
return r.Error()
127+
}
128+
129+
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
130+
func (j *JSONMetadata) UnmarshalEasyJSON(in *jlexer.Lexer) {
131+
metadata := New()
132+
133+
isTopLevel := in.IsStart()
134+
if in.IsNull() {
135+
if isTopLevel {
136+
in.Consumed()
137+
}
138+
in.Skip()
139+
return
140+
}
141+
in.Delim('{')
142+
for !in.IsDelim('}') {
143+
key := in.UnsafeString()
144+
in.WantColon()
145+
metadata = WithValue(metadata, key, in.Interface())
146+
in.WantComma()
147+
}
148+
in.Delim('}')
149+
if isTopLevel {
150+
in.Consumed()
124151
}
125152

126-
j.Metadata = FromMap(valueMap)
127-
return nil
153+
j.Metadata = metadata
128154
}

metadata/metadata_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/hellofresh/goengine/metadata"
11+
"github.com/mailru/easyjson"
1112
"github.com/stretchr/testify/assert"
1213
)
1314

@@ -252,3 +253,16 @@ func BenchmarkJSONMetadata_UnmarshalJSON(b *testing.B) {
252253
}
253254
}
254255
}
256+
257+
func BenchmarkJSONMetadata_UnmarshalEasyJSON(b *testing.B) {
258+
payload := []byte(`{"_aggregate_id": "b9ebca7a-c1eb-40dd-94a4-fac7c5e84fb5", "_aggregate_type": "bank_account", "_aggregate_version": 1}`)
259+
260+
b.ResetTimer()
261+
for i := 0; i < b.N; i++ {
262+
var m metadata.JSONMetadata
263+
err := easyjson.Unmarshal(payload, &m)
264+
if err != nil {
265+
b.Fail()
266+
}
267+
}
268+
}

strategy/json/sql/message_factory_aggregate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package sql
22

33
import (
44
"database/sql"
5-
"encoding/json"
65
"fmt"
76
"time"
87

98
"github.com/hellofresh/goengine"
109
"github.com/hellofresh/goengine/aggregate"
1110
driverSQL "github.com/hellofresh/goengine/driver/sql"
1211
"github.com/hellofresh/goengine/metadata"
12+
"github.com/mailru/easyjson"
1313
)
1414

1515
// Ensure that AggregateChangedFactory satisfies the MessageFactory interface
@@ -79,7 +79,7 @@ func (a *aggregateChangedEventStream) Message() (goengine.Message, int64, error)
7979
}
8080

8181
metadataWrapper := metadata.JSONMetadata{Metadata: metadata.New()}
82-
if err := json.Unmarshal(jsonMetadata, &metadataWrapper); err != nil {
82+
if err := easyjson.Unmarshal(jsonMetadata, &metadataWrapper); err != nil {
8383
return nil, 0, err
8484
}
8585
meta := metadataWrapper.Metadata

strategy/json/sql/message_factory_aggregate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestAggregateChangedFactory_CreateFromRows(t *testing.T) {
164164

165165
return mockRows, mocks.NewMessagePayloadFactory(ctrl)
166166
},
167-
"unexpected end of JSON input",
167+
"parse error: expected { near offset 1 of ''",
168168
},
169169
{
170170
"bad payload",

0 commit comments

Comments
 (0)