MM-54998: Optimize JSON marshalling in websocket broadcast (#25286)

Marshalling a json.RawMessage is not zero overhead. Instead,
it compacts the raw message which starts to have an overhead
at scale.

https://github.com/golang/go/issues/33422

Since we have full control over the message constructed, we
can simply write the byte slice into the network stream.
This gives considerable performance boost.

```
goos: linux
goarch: amd64
pkg: github.com/mattermost/mattermost/server/public/model
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
             │   old.txt    │              new_2.txt              │
             │    sec/op    │   sec/op     vs base                │
EncodeJSON-8   1640.5n ± 2%   289.6n ± 1%  -82.35% (p=0.000 n=10)

             │  old.txt   │             new_2.txt             │
             │    B/op    │    B/op     vs base               │
EncodeJSON-8   528.0 ± 0%   503.0 ± 0%  -4.73% (p=0.000 n=10)

             │  old.txt   │             new_2.txt              │
             │ allocs/op  │ allocs/op   vs base                │
EncodeJSON-8   5.000 ± 0%   4.000 ± 0%  -20.00% (p=0.000 n=10)
```

P.S. No concerns over changing the model API because we are
still using 0.x

https://mattermost.atlassian.net/browse/MM-54998

```release-note
Improve websocket event marshalling performance
```
This commit is contained in:
Agniva De Sarker 2023-11-08 12:15:24 +05:30 committed by GitHub
parent f29fbddef1
commit 3563e56a77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 9 additions and 5 deletions

View File

@ -463,7 +463,7 @@ func (wc *WebConn) writePump() {
var err error
if evtOk {
evt = evt.SetSequence(wc.Sequence)
err = evt.Encode(enc)
err = evt.Encode(enc, &buf)
wc.Sequence++
} else {
err = enc.Encode(msg)
@ -530,7 +530,7 @@ func (wc *WebConn) writeMessage(msg *model.WebSocketEvent) error {
// We don't use the encoder from the write pump because it's unwieldy to pass encoders
// around, and this is only called during initialization of the webConn.
var buf bytes.Buffer
err := msg.Encode(json.NewEncoder(&buf))
err := msg.Encode(json.NewEncoder(&buf), &buf)
if err != nil {
mlog.Warn("Error in encoding websocket message", mlog.Err(err))
return nil

View File

@ -294,9 +294,10 @@ func (ev *WebSocketEvent) ToJSON() ([]byte, error) {
}
// Encode encodes the event to the given encoder.
func (ev *WebSocketEvent) Encode(enc *json.Encoder) error {
func (ev *WebSocketEvent) Encode(enc *json.Encoder, buf io.Writer) error {
if ev.precomputedJSON != nil {
return enc.Encode(json.RawMessage(ev.precomputedJSONBuf()))
_, err := buf.Write(ev.precomputedJSONBuf())
return err
}
return enc.Encode(webSocketEventJSON{

View File

@ -247,8 +247,11 @@ func BenchmarkEncodeJSON(b *testing.B) {
ev := message.PrecomputeJSON()
var seq int64
enc := json.NewEncoder(io.Discard)
for i := 0; i < b.N; i++ {
err = ev.Encode(enc)
ev = ev.SetSequence(seq)
err = ev.Encode(enc, io.Discard)
seq++
}
}