Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Nov 8, 2023

Conversation

agnivade
Copy link
Member

@agnivade agnivade commented Nov 3, 2023

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

golang/go#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

Improve websocket event marshalling performance

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

golang/go#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
```
@agnivade agnivade added the 2: Dev Review Requires review by a developer label Nov 3, 2023
@mm-cloud-bot mm-cloud-bot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Nov 3, 2023
Copy link
Member

@agarciamontoro agarciamontoro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only we had encoding/json/v2 already!

Comment on lines +250 to 256
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++
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this here now? And why don't we use int64(i) directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just makes things a bit more realistic as that's what happens here:

evt = evt.SetSequence(wc.Sequence)
err = evt.Encode(enc)
wc.Sequence++

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch then :) Thanks for the clarification

@agnivade agnivade added the Do Not Merge/Awaiting Loadtest Must be loadtested before it can be merged label Nov 6, 2023
Copy link
Contributor

@streamer45 streamer45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Looking forward to the load-tests 🚀

@streamer45 streamer45 removed the 2: Dev Review Requires review by a developer label Nov 6, 2023
@agnivade
Copy link
Member Author

agnivade commented Nov 8, 2023

Results look good when comparing flame graphs. But overall, there is no difference in the user count because the extra CPU headroom generated is just filled up by the cache serialization/deserialization and the CPU thresholds limit any further improvement.

Profile from a ceiling test: (I didn't get one from the master test, but it should be representative because it works in percentages)
flame_graph

Optimized:
opt_ws_flame_profile

You can see how the cache serialization starts to become more prominent in the new profile.

@agnivade agnivade added 4: Reviews Complete All reviewers have approved the pull request and removed Do Not Merge/Awaiting Loadtest Must be loadtested before it can be merged labels Nov 8, 2023
@agnivade
Copy link
Member Author

agnivade commented Nov 8, 2023

/e2e-test

@mattermost-build
Copy link
Contributor

Successfully triggered E2E testing!
GitLab pipeline | Test dashboard

@agnivade
Copy link
Member Author

agnivade commented Nov 8, 2023

e2e tests passed. Merging.

@agnivade agnivade merged commit 3563e56 into master Nov 8, 2023
38 checks passed
@agnivade agnivade deleted the optWSMarshal2 branch November 8, 2023 06:45
@amyblais amyblais added Changelog/Done Required changelog entry has been written Docs/Not Needed Does not require documentation labels Nov 8, 2023
@jwilander jwilander added the kind/refactor Categorizes issue or PR as related to refactor of production code. label Nov 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4: Reviews Complete All reviewers have approved the pull request Changelog/Done Required changelog entry has been written Docs/Not Needed Does not require documentation kind/refactor Categorizes issue or PR as related to refactor of production code. release-note Denotes a PR that will be considered when it comes time to generate release notes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants