MM-63130: Move to webHub iteration to be alloc-free (#30792)
We switch to using iterators introduced in Go 1.23 to make iteration alloc-free and fast. And since element removal is allowed while iterating a map, this also means we don't need to even copy the slice any more. While here, we also address the comment https://github.com/mattermost/mattermost/pull/30178#discussion_r1954862151. I have simply gone back to using []string as the map entry rather than a type alias or a redirection with a struct. https://mattermost.atlassian.net/browse/MM-63130 ```release-note NONE ``` * Changed back nil to len ```release-note NONE ``` * fixing unused assignment ```release-note NONE ``` * add benchmark ``` goos: linux goarch: amd64 pkg: github.com/mattermost/mattermost/server/v8/channels/app/platform cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ HubConnIndexIterator/2_users-8 93.53n ± 1% 38.09n ± 1% -59.27% (p=0.000 n=10) HubConnIndexIterator/3_users-8 106.30n ± 0% 38.41n ± 1% -63.86% (p=0.000 n=10) HubConnIndexIterator/4_users-8 111.30n ± 1% 38.66n ± 1% -65.27% (p=0.000 n=10) geomean 103.4n 38.39n -62.89% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ HubConnIndexIterator/2_users-8 16.00 ± 0% 24.00 ± 0% +50.00% (p=0.000 n=10) HubConnIndexIterator/3_users-8 24.00 ± 0% 24.00 ± 0% ~ (p=1.000 n=10) ¹ HubConnIndexIterator/4_users-8 32.00 ± 0% 24.00 ± 0% -25.00% (p=0.000 n=10) geomean 23.08 24.00 +4.00% ¹ all samples are equal │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ HubConnIndexIterator/2_users-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ HubConnIndexIterator/3_users-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ HubConnIndexIterator/4_users-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ geomean 1.000 1.000 +0.00% ¹ all samples are equal ``` ```release-note NONE ``` * ForChannel test as well ```release-note NONE ``` * review comments ```release-note NONE ``` * fix lint errors ```release-note NONE ``` --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
67ab69606a
Коммит
509b8e9af7
@@ -7,10 +7,12 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -254,8 +256,8 @@ func TestHubConnIndex(t *testing.T) {
|
||||
assert.True(t, connIndex.Has(wc1))
|
||||
assert.True(t, connIndex.Has(wc2))
|
||||
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc2.UserId), []*WebConn{wc2, wc3, wc4})
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc1.UserId), []*WebConn{wc1})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc2.UserId)), []*WebConn{wc2, wc3, wc4})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc1.UserId)), []*WebConn{wc1})
|
||||
assert.True(t, connIndex.Has(wc2))
|
||||
assert.True(t, connIndex.Has(wc1))
|
||||
assert.Len(t, connIndex.All(), 4)
|
||||
@@ -264,8 +266,8 @@ func TestHubConnIndex(t *testing.T) {
|
||||
t.Run("RemoveMiddleUser2", func(t *testing.T) {
|
||||
connIndex.Remove(wc3) // Remove from middle from user2
|
||||
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc2.UserId), []*WebConn{wc2, wc4})
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc1.UserId), []*WebConn{wc1})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc2.UserId)), []*WebConn{wc2, wc4})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc1.UserId)), []*WebConn{wc1})
|
||||
assert.True(t, connIndex.Has(wc2))
|
||||
assert.False(t, connIndex.Has(wc3))
|
||||
assert.True(t, connIndex.Has(wc4))
|
||||
@@ -275,9 +277,9 @@ func TestHubConnIndex(t *testing.T) {
|
||||
t.Run("RemoveUser1", func(t *testing.T) {
|
||||
connIndex.Remove(wc1) // Remove sole connection from user1
|
||||
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc2.UserId), []*WebConn{wc2, wc4})
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc1.UserId), []*WebConn{})
|
||||
assert.Len(t, connIndex.ForUser(wc1.UserId), 0)
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc2.UserId)), []*WebConn{wc2, wc4})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc1.UserId)), []*WebConn{})
|
||||
assert.Len(t, slices.Collect(connIndex.ForUser(wc1.UserId)), 0)
|
||||
assert.Len(t, connIndex.All(), 2)
|
||||
assert.False(t, connIndex.Has(wc1))
|
||||
assert.True(t, connIndex.Has(wc2))
|
||||
@@ -286,8 +288,8 @@ func TestHubConnIndex(t *testing.T) {
|
||||
t.Run("RemoveEndUser2", func(t *testing.T) {
|
||||
connIndex.Remove(wc4) // Remove from end from user2
|
||||
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc2.UserId), []*WebConn{wc2})
|
||||
assert.ElementsMatch(t, connIndex.ForUser(wc1.UserId), []*WebConn{})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc2.UserId)), []*WebConn{wc2})
|
||||
assert.ElementsMatch(t, slices.Collect(connIndex.ForUser(wc1.UserId)), []*WebConn{})
|
||||
assert.True(t, connIndex.Has(wc2))
|
||||
assert.False(t, connIndex.Has(wc3))
|
||||
assert.False(t, connIndex.Has(wc4))
|
||||
@@ -400,11 +402,11 @@ func TestHubConnIndex(t *testing.T) {
|
||||
t.Run("ForChannel", func(t *testing.T) {
|
||||
require.Len(t, connIndex.byChannelID, 1)
|
||||
ids := make([]string, 0)
|
||||
for _, c := range connIndex.ForChannel(th.BasicChannel.Id) {
|
||||
for c := range connIndex.ForChannel(th.BasicChannel.Id) {
|
||||
ids = append(ids, c.GetConnectionID())
|
||||
}
|
||||
require.ElementsMatch(t, []string{wc1ID, wc2ID, wc3ID}, ids)
|
||||
require.Len(t, connIndex.ForChannel("notexist"), 0)
|
||||
require.Len(t, slices.Collect(connIndex.ForChannel("notexist")), 0)
|
||||
})
|
||||
|
||||
ch := th.CreateChannel(th.BasicTeam)
|
||||
@@ -420,14 +422,14 @@ func TestHubConnIndex(t *testing.T) {
|
||||
t.Run("InvalidateCMCacheForUser", func(t *testing.T) {
|
||||
require.NoError(t, connIndex.InvalidateCMCacheForUser(th.BasicUser2.Id))
|
||||
require.Len(t, connIndex.byChannelID, 2)
|
||||
require.Len(t, connIndex.ForChannel(th.BasicChannel.Id), 3)
|
||||
require.Len(t, connIndex.ForChannel(ch.Id), 2)
|
||||
require.Len(t, slices.Collect(connIndex.ForChannel(th.BasicChannel.Id)), 3)
|
||||
require.Len(t, slices.Collect(connIndex.ForChannel(ch.Id)), 2)
|
||||
})
|
||||
|
||||
t.Run("Remove", func(t *testing.T) {
|
||||
connIndex.Remove(wc3)
|
||||
require.Len(t, connIndex.byChannelID, 2)
|
||||
require.Len(t, connIndex.ForChannel(th.BasicChannel.Id), 2)
|
||||
require.Len(t, slices.Collect(connIndex.ForChannel(th.BasicChannel.Id)), 2)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -470,7 +472,7 @@ func TestHubConnIndexIncorrectRemoval(t *testing.T) {
|
||||
err = connIndex.Add(wc4)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, wc := range connIndex.ForUser(wc2.UserId) {
|
||||
for wc := range connIndex.ForUser(wc2.UserId) {
|
||||
if !connIndex.Has(wc) {
|
||||
require.Failf(t, "Failed to find connection", "connection: %v", wc)
|
||||
continue
|
||||
@@ -527,21 +529,21 @@ func TestHubConnIndexInactive(t *testing.T) {
|
||||
assert.Equal(t, connIndex.ForUserActiveCount(wc2.UserId), 1)
|
||||
assert.Nil(t, connIndex.RemoveInactiveByConnectionID(wc1.UserId, "conn3"))
|
||||
assert.False(t, connIndex.Has(wc3))
|
||||
assert.Len(t, connIndex.ForUser(wc2.UserId), 1)
|
||||
assert.Len(t, slices.Collect(connIndex.ForUser(wc2.UserId)), 1)
|
||||
|
||||
wc3.lastUserActivityAt = model.GetMillis()
|
||||
err = connIndex.Add(wc3)
|
||||
require.NoError(t, err)
|
||||
connIndex.RemoveInactiveConnections()
|
||||
assert.True(t, connIndex.Has(wc3))
|
||||
assert.Len(t, connIndex.ForUser(wc2.UserId), 2)
|
||||
assert.Len(t, slices.Collect(connIndex.ForUser(wc2.UserId)), 2)
|
||||
assert.Equal(t, connIndex.ForUserActiveCount(wc2.UserId), 1)
|
||||
assert.Len(t, connIndex.All(), 3)
|
||||
|
||||
wc3.lastUserActivityAt = model.GetMillis() - (time.Minute).Milliseconds()
|
||||
connIndex.RemoveInactiveConnections()
|
||||
assert.False(t, connIndex.Has(wc3))
|
||||
assert.Len(t, connIndex.ForUser(wc2.UserId), 1)
|
||||
assert.Len(t, slices.Collect(connIndex.ForUser(wc2.UserId)), 1)
|
||||
assert.Equal(t, connIndex.ForUserActiveCount(wc2.UserId), 1)
|
||||
assert.Len(t, connIndex.All(), 2)
|
||||
}
|
||||
@@ -647,6 +649,146 @@ func TestHubWebConnCount(t *testing.T) {
|
||||
assert.Equal(t, 0, th.Service.WebConnCountForUser("none"))
|
||||
}
|
||||
|
||||
var globalIter iter.Seq[*WebConn]
|
||||
|
||||
func BenchmarkHubConnIndexIteratorForUser(b *testing.B) {
|
||||
th := Setup(b)
|
||||
defer th.TearDown()
|
||||
|
||||
connIndex := newHubConnectionIndex(2*time.Second, th.Service.Store, th.Service.logger, false)
|
||||
|
||||
// User1
|
||||
wc1 := &WebConn{
|
||||
Platform: th.Service,
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
wc1.Active.Store(true)
|
||||
wc1.SetConnectionID("conn1")
|
||||
wc1.SetSession(&model.Session{})
|
||||
|
||||
// User2
|
||||
wc2 := &WebConn{
|
||||
Platform: th.Service,
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
wc2.Active.Store(true)
|
||||
wc2.SetConnectionID("conn2")
|
||||
wc2.SetSession(&model.Session{})
|
||||
|
||||
wc3 := &WebConn{
|
||||
Platform: th.Service,
|
||||
UserId: wc2.UserId,
|
||||
}
|
||||
wc3.Active.Store(false)
|
||||
wc3.SetConnectionID("conn3")
|
||||
wc3.SetSession(&model.Session{})
|
||||
|
||||
require.NoError(b, connIndex.Add(wc1))
|
||||
require.NoError(b, connIndex.Add(wc2))
|
||||
require.NoError(b, connIndex.Add(wc3))
|
||||
|
||||
b.ResetTimer()
|
||||
b.Run("2 users", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
globalIter = connIndex.ForUser(wc2.UserId)
|
||||
}
|
||||
})
|
||||
|
||||
wc4 := &WebConn{
|
||||
Platform: th.Service,
|
||||
UserId: wc2.UserId,
|
||||
}
|
||||
wc4.Active.Store(false)
|
||||
wc4.SetConnectionID("conn4")
|
||||
wc4.SetSession(&model.Session{})
|
||||
|
||||
require.NoError(b, connIndex.Add(wc4))
|
||||
b.ResetTimer()
|
||||
b.Run("3 users", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
globalIter = connIndex.ForUser(wc2.UserId)
|
||||
}
|
||||
})
|
||||
|
||||
wc5 := &WebConn{
|
||||
Platform: th.Service,
|
||||
UserId: wc2.UserId,
|
||||
}
|
||||
wc5.Active.Store(false)
|
||||
wc5.SetConnectionID("conn5")
|
||||
wc5.SetSession(&model.Session{})
|
||||
|
||||
require.NoError(b, connIndex.Add(wc5))
|
||||
b.ResetTimer()
|
||||
b.Run("4 users", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
globalIter = connIndex.ForUser(wc2.UserId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHubConnIndexIteratorForChannel(b *testing.B) {
|
||||
th := Setup(b).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
_, err := th.Service.Store.Channel().SaveMember(th.Context, &model.ChannelMember{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
UserId: th.BasicUser.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
SchemeGuest: th.BasicUser.IsGuest(),
|
||||
SchemeUser: !th.BasicUser.IsGuest(),
|
||||
})
|
||||
require.NoError(b, err)
|
||||
_, err = th.Service.Store.Channel().SaveMember(th.Context, &model.ChannelMember{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
UserId: th.BasicUser2.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
SchemeGuest: th.BasicUser2.IsGuest(),
|
||||
SchemeUser: !th.BasicUser2.IsGuest(),
|
||||
})
|
||||
require.NoError(b, err)
|
||||
|
||||
connIndex := newHubConnectionIndex(1*time.Second, th.Service.Store, th.Service.logger, true)
|
||||
|
||||
// User1
|
||||
wc1ID := model.NewId()
|
||||
wc1 := &WebConn{
|
||||
Platform: th.Service,
|
||||
Suite: th.Suite,
|
||||
UserId: th.BasicUser.Id,
|
||||
}
|
||||
wc1.SetConnectionID(wc1ID)
|
||||
wc1.SetSession(&model.Session{})
|
||||
|
||||
// User2
|
||||
wc2ID := model.NewId()
|
||||
wc2 := &WebConn{
|
||||
Platform: th.Service,
|
||||
Suite: th.Suite,
|
||||
UserId: th.BasicUser2.Id,
|
||||
}
|
||||
wc2.SetConnectionID(wc2ID)
|
||||
wc2.SetSession(&model.Session{})
|
||||
|
||||
wc3ID := model.NewId()
|
||||
wc3 := &WebConn{
|
||||
Platform: th.Service,
|
||||
Suite: th.Suite,
|
||||
UserId: wc2.UserId,
|
||||
}
|
||||
wc3.SetConnectionID(wc3ID)
|
||||
wc3.SetSession(&model.Session{})
|
||||
|
||||
require.NoError(b, connIndex.Add(wc1))
|
||||
require.NoError(b, connIndex.Add(wc2))
|
||||
require.NoError(b, connIndex.Add(wc3))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
globalIter = connIndex.ForChannel(th.BasicChannel.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// Always run this with -benchtime=0.1s
|
||||
// See: https://github.com/golang/go/issues/27217.
|
||||
func BenchmarkHubConnIndex(b *testing.B) {
|
||||
|
||||
Ссылка в новой задаче
Block a user