MM-41260: Revamp ES/Bleve batching logic (#19841)

The older method used to reply completely on timestamps
to take batches of items in a timestamp range and then
just incrementing the timestamp. This led to handling
edge-cases such as more items than the batch count, all
having the same timestamp.

Additionally, relying on timestamp as the page cursor
meant that indexing was not very efficient if you had
several items spread out across large spans of time.

To get away from all of that we use a proper cursor-based
approach consisting of createAt+Id. With this, we move
completely to a constant page size where we can fetch
a given number of objects irrespective of when they
were created. This makes indexing much more faster and
efficient.

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

```release-note
Elasticsearch and Bleve indexing have been revamped to be much
more efficient and faster. The config parameter BulkIndexingTimeWindowSeconds
for both elasticsearch and bleve have been removed.
A new config parameter called BatchSize has been introduced instead.
This parameter controls the number of objects that
can be indexed in a single batch. This makes things
more efficient and maintains a constant workload.
```
Этот коммит содержится в:
Agniva De Sarker
2022-03-31 10:46:01 +05:30
коммит произвёл GitHub
родитель 9adf06e122
Коммит f8a3119426
27 изменённых файлов: 384 добавлений и 376 удалений

Просмотреть файл

@@ -7601,7 +7601,6 @@ func testChannelStoreGetChannelsBatchForIndexing(t *testing.T, ss store.Store) {
require.NoError(t, nErr)
time.Sleep(10 * time.Millisecond)
startTime := c2.CreateAt
c3 := &model.Channel{}
c3.DisplayName = "Channel3"
@@ -7633,23 +7632,20 @@ func testChannelStoreGetChannelsBatchForIndexing(t *testing.T, ss store.Store) {
_, nErr = ss.Channel().Save(c6, -1)
require.NoError(t, nErr)
endTime := c6.CreateAt
// First and last channel should be outside the range
channels, err := ss.Channel().GetChannelsBatchForIndexing(startTime, endTime, 1000)
channels, err := ss.Channel().GetChannelsBatchForIndexing(c1.CreateAt, "", 4)
assert.NoError(t, err)
assert.ElementsMatch(t, []*model.Channel{c2, c3, c4, c5}, channels)
assert.Len(t, channels, 4)
// Update the endTime, last channel should be in
endTime = model.GetMillis()
channels, err = ss.Channel().GetChannelsBatchForIndexing(startTime, endTime, 1000)
// From 4th createat+id
channels, err = ss.Channel().GetChannelsBatchForIndexing(channels[3].CreateAt, channels[3].Id, 5)
assert.NoError(t, err)
assert.ElementsMatch(t, []*model.Channel{c2, c3, c4, c5, c6}, channels)
assert.Len(t, channels, 2)
// Testing the limit
channels, err = ss.Channel().GetChannelsBatchForIndexing(startTime, endTime, 2)
channels, err = ss.Channel().GetChannelsBatchForIndexing(channels[1].CreateAt, channels[1].Id, 1)
assert.NoError(t, err)
assert.ElementsMatch(t, []*model.Channel{c2, c3}, channels)
assert.Len(t, channels, 0)
}
func testGroupSyncedChannelCount(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -673,42 +673,23 @@ func testFileInfoStoreGetFilesBatchForIndexing(t *testing.T, ss store.Store) {
ss.FileInfo().PermanentDelete(f3.Id)
}()
t.Run("get all files", func(t *testing.T) {
r, err := ss.FileInfo().GetFilesBatchForIndexing(f1.CreateAt, model.GetMillis()+100000, 100)
require.NoError(t, err)
require.Len(t, r, 3, "Expected 3 posts in results. Got %v", len(r))
for _, f := range r {
if f.Id == f1.Id {
require.Equal(t, f.ChannelId, o1.ChannelId, "Unexpected channel ID")
require.Equal(t, f.Path, "file1.txt", "Unexpected filename")
} else if f.Id == f2.Id {
require.Equal(t, f.ChannelId, o2.ChannelId, "Unexpected channel ID")
require.Equal(t, f.Path, "file2.txt", "Unexpected filename")
} else if f.Id == f3.Id {
require.Equal(t, f.ChannelId, o3.ChannelId, "Unexpected channel ID")
require.Equal(t, f.Path, "file3.txt", "Unexpected filename")
} else {
require.Fail(t, "unexpected file returned")
}
}
})
// Getting all
r, err := ss.FileInfo().GetFilesBatchForIndexing(f1.CreateAt-1, "", 100)
require.NoError(t, err)
require.Len(t, r, 3, "Expected 3 posts in results. Got %v", len(r))
t.Run("get files after certain date", func(t *testing.T) {
r, err := ss.FileInfo().GetFilesBatchForIndexing(f1.CreateAt+1, model.GetMillis()+100000, 100)
require.NoError(t, err)
require.Len(t, r, 2, "Expected 2 posts in results. Got %v", len(r))
for _, f := range r {
if f.Id == f2.Id {
require.Equal(t, f.ChannelId, o2.ChannelId, "Unexpected channel ID")
require.Equal(t, f.Path, "file2.txt", "Unexpected filename")
} else if f.Id == f3.Id {
require.Equal(t, f.ChannelId, o3.ChannelId, "Unexpected channel ID")
require.Equal(t, f.Path, "file3.txt", "Unexpected filename")
} else {
require.Fail(t, "unexpected file returned")
}
}
})
// Testing pagination
r, err = ss.FileInfo().GetFilesBatchForIndexing(f1.CreateAt-1, "", 2)
require.NoError(t, err)
require.Len(t, r, 2, "Expected 2 posts in results. Got %v", len(r))
r, err = ss.FileInfo().GetFilesBatchForIndexing(r[1].CreateAt, r[1].Id, 2)
require.NoError(t, err)
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
r, err = ss.FileInfo().GetFilesBatchForIndexing(r[0].CreateAt, r[0].Id, 2)
require.NoError(t, err)
require.Len(t, r, 0, "Expected 0 posts in results. Got %v", len(r))
}
func testFileInfoStoreCountAll(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -697,13 +697,13 @@ func (_m *ChannelStore) GetChannels(teamID string, userID string, opts *model.Ch
return r0, r1
}
// GetChannelsBatchForIndexing provides a mock function with given fields: startTime, endTime, limit
func (_m *ChannelStore) GetChannelsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.Channel, error) {
ret := _m.Called(startTime, endTime, limit)
// GetChannelsBatchForIndexing provides a mock function with given fields: startTime, startChannelID, limit
func (_m *ChannelStore) GetChannelsBatchForIndexing(startTime int64, startChannelID string, limit int) ([]*model.Channel, error) {
ret := _m.Called(startTime, startChannelID, limit)
var r0 []*model.Channel
if rf, ok := ret.Get(0).(func(int64, int64, int) []*model.Channel); ok {
r0 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(0).(func(int64, string, int) []*model.Channel); ok {
r0 = rf(startTime, startChannelID, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Channel)
@@ -711,8 +711,8 @@ func (_m *ChannelStore) GetChannelsBatchForIndexing(startTime int64, endTime int
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, int64, int) error); ok {
r1 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(1).(func(int64, string, int) error); ok {
r1 = rf(startTime, startChannelID, limit)
} else {
r1 = ret.Error(1)
}

Просмотреть файл

@@ -144,13 +144,13 @@ func (_m *FileInfoStore) GetByPath(path string) (*model.FileInfo, error) {
return r0, r1
}
// GetFilesBatchForIndexing provides a mock function with given fields: startTime, endTime, limit
func (_m *FileInfoStore) GetFilesBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.FileForIndexing, error) {
ret := _m.Called(startTime, endTime, limit)
// GetFilesBatchForIndexing provides a mock function with given fields: startTime, startFileID, limit
func (_m *FileInfoStore) GetFilesBatchForIndexing(startTime int64, startFileID string, limit int) ([]*model.FileForIndexing, error) {
ret := _m.Called(startTime, startFileID, limit)
var r0 []*model.FileForIndexing
if rf, ok := ret.Get(0).(func(int64, int64, int) []*model.FileForIndexing); ok {
r0 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(0).(func(int64, string, int) []*model.FileForIndexing); ok {
r0 = rf(startTime, startFileID, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.FileForIndexing)
@@ -158,8 +158,8 @@ func (_m *FileInfoStore) GetFilesBatchForIndexing(startTime int64, endTime int64
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, int64, int) error); ok {
r1 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(1).(func(int64, string, int) error); ok {
r1 = rf(startTime, startFileID, limit)
} else {
r1 = ret.Error(1)
}

Просмотреть файл

@@ -465,13 +465,13 @@ func (_m *PostStore) GetPostsAfter(options model.GetPostsOptions) (*model.PostLi
return r0, r1
}
// GetPostsBatchForIndexing provides a mock function with given fields: startTime, endTime, limit
func (_m *PostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, error) {
ret := _m.Called(startTime, endTime, limit)
// GetPostsBatchForIndexing provides a mock function with given fields: startTime, startPostID, limit
func (_m *PostStore) GetPostsBatchForIndexing(startTime int64, startPostID string, limit int) ([]*model.PostForIndexing, error) {
ret := _m.Called(startTime, startPostID, limit)
var r0 []*model.PostForIndexing
if rf, ok := ret.Get(0).(func(int64, int64, int) []*model.PostForIndexing); ok {
r0 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(0).(func(int64, string, int) []*model.PostForIndexing); ok {
r0 = rf(startTime, startPostID, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PostForIndexing)
@@ -479,8 +479,8 @@ func (_m *PostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, li
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, int64, int) error); ok {
r1 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(1).(func(int64, string, int) error); ok {
r1 = rf(startTime, startPostID, limit)
} else {
r1 = ret.Error(1)
}

Просмотреть файл

@@ -979,13 +979,13 @@ func (_m *UserStore) GetUnreadCountForChannel(userID string, channelID string) (
return r0, r1
}
// GetUsersBatchForIndexing provides a mock function with given fields: startTime, endTime, limit
func (_m *UserStore) GetUsersBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.UserForIndexing, error) {
ret := _m.Called(startTime, endTime, limit)
// GetUsersBatchForIndexing provides a mock function with given fields: startTime, startFileID, limit
func (_m *UserStore) GetUsersBatchForIndexing(startTime int64, startFileID string, limit int) ([]*model.UserForIndexing, error) {
ret := _m.Called(startTime, startFileID, limit)
var r0 []*model.UserForIndexing
if rf, ok := ret.Get(0).(func(int64, int64, int) []*model.UserForIndexing); ok {
r0 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(0).(func(int64, string, int) []*model.UserForIndexing); ok {
r0 = rf(startTime, startFileID, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.UserForIndexing)
@@ -993,8 +993,8 @@ func (_m *UserStore) GetUsersBatchForIndexing(startTime int64, endTime int64, li
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, int64, int) error); ok {
r1 = rf(startTime, endTime, limit)
if rf, ok := ret.Get(1).(func(int64, string, int) error); ok {
r1 = rf(startTime, startFileID, limit)
} else {
r1 = ret.Error(1)
}

Просмотреть файл

@@ -2953,7 +2953,7 @@ func testPostStoreGetPostsBatchForIndexing(t *testing.T, ss store.Store) {
o2.ChannelId = c2.Id
o2.UserId = model.NewId()
o2.Message = NewTestId()
o2, err = ss.Post().Save(o2)
_, err = ss.Post().Save(o2)
require.NoError(t, err)
o3 := &model.Post{}
@@ -2961,26 +2961,30 @@ func testPostStoreGetPostsBatchForIndexing(t *testing.T, ss store.Store) {
o3.UserId = model.NewId()
o3.RootId = o1.Id
o3.Message = NewTestId()
o3, err = ss.Post().Save(o3)
_, err = ss.Post().Save(o3)
require.NoError(t, err)
r, err := ss.Post().GetPostsBatchForIndexing(o1.CreateAt, model.GetMillis()+100000, 100)
// Getting all
r, err := ss.Post().GetPostsBatchForIndexing(o1.CreateAt-1, "", 100)
require.NoError(t, err)
require.Len(t, r, 3, "Expected 3 posts in results. Got %v", len(r))
for _, p := range r {
if p.Id == o1.Id {
require.Equal(t, p.TeamId, c1.TeamId, "Unexpected team ID")
require.Nil(t, p.ParentCreateAt, "Unexpected parent create at")
} else if p.Id == o2.Id {
require.Equal(t, p.TeamId, c2.TeamId, "Unexpected team ID")
require.Nil(t, p.ParentCreateAt, "Unexpected parent create at")
} else if p.Id == o3.Id {
require.Equal(t, p.TeamId, c1.TeamId, "Unexpected team ID")
require.Equal(t, *p.ParentCreateAt, o1.CreateAt, "Unexpected parent create at")
} else {
require.Fail(t, "unexpected post returned")
}
}
// Testing pagination
r, err = ss.Post().GetPostsBatchForIndexing(o1.CreateAt-1, "", 1)
require.NoError(t, err)
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
r, err = ss.Post().GetPostsBatchForIndexing(r[0].CreateAt, r[0].Id, 1)
require.NoError(t, err)
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
r, err = ss.Post().GetPostsBatchForIndexing(r[0].CreateAt, r[0].Id, 1)
require.NoError(t, err)
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
r, err = ss.Post().GetPostsBatchForIndexing(r[0].CreateAt, r[0].Id, 1)
require.NoError(t, err)
require.Len(t, r, 0, "Expected 0 post in results. Got %v", len(r))
}
func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -4716,7 +4716,6 @@ func testUserStoreGetUsersBatchForIndexing(t *testing.T, ss store.Store) {
})
require.NoError(t, err)
startTime := u2.CreateAt
time.Sleep(time.Millisecond)
u3, err := ss.User().Save(&model.User{
@@ -4744,47 +4743,23 @@ func testUserStoreGetUsersBatchForIndexing(t *testing.T, ss store.Store) {
})
require.NoError(t, err)
endTime := u3.CreateAt
// First and last user should be outside the range
res1List, err := ss.User().GetUsersBatchForIndexing(startTime, endTime, 100)
// Getting all users
res1List, err := ss.User().GetUsersBatchForIndexing(u1.CreateAt-1, "", 100)
require.NoError(t, err)
assert.Len(t, res1List, 3)
assert.Len(t, res1List, 1)
assert.Equal(t, res1List[0].Username, u2.Username)
assert.ElementsMatch(t, res1List[0].TeamsIds, []string{t1.Id})
assert.ElementsMatch(t, res1List[0].ChannelsIds, []string{cPub1.Id, cPub2.Id})
// Update startTime to include first user
startTime = u1.CreateAt
res2List, err := ss.User().GetUsersBatchForIndexing(startTime, endTime, 100)
// Testing pagination
res2List, err := ss.User().GetUsersBatchForIndexing(u1.CreateAt-1, "", 1)
require.NoError(t, err)
assert.Len(t, res2List, 1)
res2List, err = ss.User().GetUsersBatchForIndexing(res2List[0].CreateAt, res2List[0].Id, 2)
require.NoError(t, err)
assert.Len(t, res2List, 2)
assert.Equal(t, res2List[0].Username, u1.Username)
assert.Equal(t, res2List[0].ChannelsIds, []string{})
assert.Equal(t, res2List[0].TeamsIds, []string{})
assert.Equal(t, res2List[1].Username, u2.Username)
// Update endTime to include last user
endTime = model.GetMillis()
res3List, err := ss.User().GetUsersBatchForIndexing(startTime, endTime, 100)
res2List, err = ss.User().GetUsersBatchForIndexing(res2List[1].CreateAt, res2List[1].Id, 2)
require.NoError(t, err)
assert.Len(t, res3List, 3)
assert.Equal(t, res3List[0].Username, u1.Username)
assert.Equal(t, res3List[1].Username, u2.Username)
assert.Equal(t, res3List[2].Username, u3.Username)
assert.ElementsMatch(t, res3List[2].TeamsIds, []string{})
assert.ElementsMatch(t, res3List[2].ChannelsIds, []string{cPub2.Id})
// Testing the limit
res4List, err := ss.User().GetUsersBatchForIndexing(startTime, endTime, 2)
require.NoError(t, err)
assert.Len(t, res4List, 2)
assert.Equal(t, res4List[0].Username, u1.Username)
assert.Equal(t, res4List[1].Username, u2.Username)
assert.Len(t, res2List, 0)
}
func testUserStoreGetTeamGroupUsers(t *testing.T, ss store.Store) {