mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-29980: Optimize profilesInChannels cache to fast path (#16116)
* MM-29980: Optimize profilesInChannels cache to fast path We add one more message type to the fast path- profiles in channels. There are 2 primary reasons for this: - This is not really a new model type, but just a map of users. And users already use the fast path. So we can get some more gains without really investing much more code. - A more important reason is that with the upcoming striped mutex changes, we will get a higher throughput at the cost of a bit more CPU utilization. The reason being that since less amount of time will be spent in lock-contention, the CPU is free to do more stuff. So this change is to counter that increase. As usual, this gives much better performance than the original decoder. Micro-benchmark results ``` name old time/op new time/op delta LRU/UserMap=new-8 16.6µs ± 3% 3.9µs ± 4% -76.15% (p=0.000 n=10+10) name old alloc/op new alloc/op delta LRU/UserMap=new-8 4.78kB ± 0% 2.74kB ± 0% -42.65% (p=0.000 n=10+10) name old allocs/op new allocs/op delta LRU/UserMap=new-8 38.0 ± 0% 30.0 ± 0% -21.05% (p=0.000 n=10+10) ``` https://mattermost.atlassian.net/browse/MM-29980 Here are some results from a load test. The comparison is done with a 2 node cluster; one running master and one running with this patch so that it's easier to compare. The total users are 2000. <See PR> ```release-note NONE ``` * Fix gofmt * Trigger CI
This commit is contained in:
@@ -1620,3 +1620,160 @@ func (z *User) Msgsize() (s int) {
|
||||
s += msgp.BoolSize + msgp.StringPrefixSize + len(z.MfaSecret) + msgp.Int64Size + msgp.BoolSize + msgp.StringPrefixSize + len(z.BotDescription) + msgp.Int64Size + msgp.StringPrefixSize + len(z.TermsOfServiceId) + msgp.Int64Size
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *UserMap) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var zb0003 uint32
|
||||
zb0003, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
if (*z) == nil {
|
||||
(*z) = make(UserMap, zb0003)
|
||||
} else if len((*z)) > 0 {
|
||||
for key := range *z {
|
||||
delete((*z), key)
|
||||
}
|
||||
}
|
||||
for zb0003 > 0 {
|
||||
zb0003--
|
||||
var zb0001 string
|
||||
var zb0002 *User
|
||||
zb0001, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
if dc.IsNil() {
|
||||
err = dc.ReadNil()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0001)
|
||||
return
|
||||
}
|
||||
zb0002 = nil
|
||||
} else {
|
||||
if zb0002 == nil {
|
||||
zb0002 = new(User)
|
||||
}
|
||||
err = zb0002.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
(*z)[zb0001] = zb0002
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z UserMap) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = en.WriteMapHeader(uint32(len(z)))
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0004, zb0005 := range z {
|
||||
err = en.WriteString(zb0004)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
if zb0005 == nil {
|
||||
err = en.WriteNil()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = zb0005.EncodeMsg(en)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0004)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z UserMap) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
o = msgp.AppendMapHeader(o, uint32(len(z)))
|
||||
for zb0004, zb0005 := range z {
|
||||
o = msgp.AppendString(o, zb0004)
|
||||
if zb0005 == nil {
|
||||
o = msgp.AppendNil(o)
|
||||
} else {
|
||||
o, err = zb0005.MarshalMsg(o)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0004)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *UserMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var zb0003 uint32
|
||||
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
if (*z) == nil {
|
||||
(*z) = make(UserMap, zb0003)
|
||||
} else if len((*z)) > 0 {
|
||||
for key := range *z {
|
||||
delete((*z), key)
|
||||
}
|
||||
}
|
||||
for zb0003 > 0 {
|
||||
var zb0001 string
|
||||
var zb0002 *User
|
||||
zb0003--
|
||||
zb0001, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
if msgp.IsNil(bts) {
|
||||
bts, err = msgp.ReadNilBytes(bts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
zb0002 = nil
|
||||
} else {
|
||||
if zb0002 == nil {
|
||||
zb0002 = new(User)
|
||||
}
|
||||
bts, err = zb0002.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
(*z)[zb0001] = zb0002
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z UserMap) Msgsize() (s int) {
|
||||
s = msgp.MapHeaderSize
|
||||
if z != nil {
|
||||
for zb0004, zb0005 := range z {
|
||||
_ = zb0005
|
||||
s += msgp.StringPrefixSize + len(zb0004)
|
||||
if zb0005 == nil {
|
||||
s += msgp.NilSize
|
||||
} else {
|
||||
s += zb0005.Msgsize()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,6 +98,12 @@ type User struct {
|
||||
TermsOfServiceCreateAt int64 `db:"-" json:"terms_of_service_create_at,omitempty"`
|
||||
}
|
||||
|
||||
//msgp UserMap
|
||||
|
||||
// UserMap is a map from a userId to a user object.
|
||||
// It is used to generate methods which can be used for fast serialization/de-serialization.
|
||||
type UserMap map[string]*User
|
||||
|
||||
type UserUpdate struct {
|
||||
Old *User
|
||||
New *User
|
||||
|
||||
5
services/cache/lru.go
vendored
5
services/cache/lru.go
vendored
@@ -221,6 +221,11 @@ func (l *LRU) get(key string, value interface{}) error {
|
||||
_, err := s.UnmarshalMsg(e.value)
|
||||
*v = &s
|
||||
return err
|
||||
case *map[string]*model.User:
|
||||
var u model.UserMap
|
||||
_, err := u.UnmarshalMsg(e.value)
|
||||
*v = u
|
||||
return err
|
||||
}
|
||||
|
||||
// Slow path for other structs.
|
||||
|
||||
44
services/cache/lru_test.go
vendored
44
services/cache/lru_test.go
vendored
@@ -279,6 +279,17 @@ func TestLRUMarshalUnMarshal(t *testing.T) {
|
||||
// This does not make an actual difference in terms of functionality.
|
||||
u.Timezone = nil
|
||||
require.Equal(t, user, u)
|
||||
|
||||
tt := make(map[string]*model.User)
|
||||
tt["1"] = u
|
||||
err = l.Set("mm", model.UserMap(tt))
|
||||
require.Nil(t, err)
|
||||
|
||||
var out map[string]*model.User
|
||||
err = l.Get("mm", &out)
|
||||
require.Nil(t, err)
|
||||
out["1"].Timezone = nil
|
||||
require.Equal(t, tt, out)
|
||||
}
|
||||
|
||||
func BenchmarkLRU(b *testing.B) {
|
||||
@@ -434,6 +445,39 @@ func BenchmarkLRU(b *testing.B) {
|
||||
}
|
||||
})
|
||||
|
||||
uMap := map[string]*model.User{
|
||||
"id1": {
|
||||
Id: "id1",
|
||||
CreateAt: 1111,
|
||||
UpdateAt: 1112,
|
||||
Username: "user1",
|
||||
Password: "pass",
|
||||
},
|
||||
"id2": {
|
||||
Id: "id2",
|
||||
CreateAt: 1113,
|
||||
UpdateAt: 1114,
|
||||
Username: "user2",
|
||||
Password: "pass2",
|
||||
},
|
||||
}
|
||||
|
||||
b.Run("UserMap=new", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l2 := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
err := l2.Set("test", model.UserMap(uMap))
|
||||
require.Nil(b, err)
|
||||
|
||||
var val map[string]*model.User
|
||||
err = l2.Get("test", &val)
|
||||
require.Nil(b, err)
|
||||
}
|
||||
})
|
||||
|
||||
post := &model.Post{
|
||||
Id: "id",
|
||||
CreateAt: 11111,
|
||||
|
||||
@@ -85,7 +85,7 @@ func (s LocalCacheUserStore) GetAllProfilesInChannel(channelId string, allowFrom
|
||||
}
|
||||
|
||||
if allowFromCache {
|
||||
s.rootStore.doStandardAddToCache(s.rootStore.profilesInChannelCache, channelId, userMap)
|
||||
s.rootStore.doStandardAddToCache(s.rootStore.profilesInChannelCache, channelId, model.UserMap(userMap))
|
||||
}
|
||||
|
||||
return userMap, nil
|
||||
|
||||
Reference in New Issue
Block a user