MM-45956: Optimize FileInfo stats query (#22603)

* MM-45956: Optimize FileInfo stats query

We Denormalize Post.ChannelId on FileInfo.ChannelId

```release-note
The file info stats query is now optimized by denormalizing the channelID column into the table itself. This will speed up the query to get the file count for a channel on clicking the RHS.

Migration times:
On a MySQL 8.0.31 DB with
1405 rows in FileInfo and 11M posts, it took around 0.3s

On a Postgres 12.14 DB with
1731 rows in FileInfo and 11M posts, it took around 0.27s
```

https://mattermost.atlassian.net/browse/MM-45956
Этот коммит содержится в:
Agniva De Sarker
2023-03-23 22:14:04 +05:30
коммит произвёл GitHub
родитель 1edbde8aa3
Коммит 56b18ca7bf
25 изменённых файлов: 266 добавлений и 138 удалений

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

@@ -106,26 +106,31 @@ func testFileInfoSaveGetByPath(t *testing.T, ss store.Store) {
func testFileInfoGetForPost(t *testing.T, ss store.Store) {
userId := model.NewId()
postId := model.NewId()
channelId := model.NewId()
infos := []*model.FileInfo{
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
DeleteAt: 123,
},
{
PostId: model.NewId(),
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
@@ -225,25 +230,30 @@ func testFileInfoGetForUser(t *testing.T, ss store.Store) {
userId := model.NewId()
userId2 := model.NewId()
postId := model.NewId()
channelId := model.NewId()
infos := []*model.FileInfo{
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: model.NewId(),
ChannelId: channelId,
CreatorId: userId2,
Path: "file.txt",
},
@@ -289,6 +299,9 @@ func testFileInfoGetWithOptions(t *testing.T, ss store.Store) {
if post.Id != "" {
fileInfo.PostId = post.Id
}
if post.ChannelId != "" {
fileInfo.ChannelId = post.ChannelId
}
_, err := ss.FileInfo().Save(&fileInfo)
require.NoError(t, err)
return fileInfo
@@ -414,6 +427,7 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
t.Run("should attach files", func(t *testing.T) {
userId := model.NewId()
postId := model.NewId()
channelId := model.NewId()
info1, err := ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
@@ -429,13 +443,15 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
require.Equal(t, "", info1.PostId)
require.Equal(t, "", info2.PostId)
err = ss.FileInfo().AttachToPost(info1.Id, postId, userId)
err = ss.FileInfo().AttachToPost(info1.Id, postId, channelId, userId)
assert.NoError(t, err)
info1.PostId = postId
info1.ChannelId = channelId
err = ss.FileInfo().AttachToPost(info2.Id, postId, userId)
err = ss.FileInfo().AttachToPost(info2.Id, postId, channelId, userId)
assert.NoError(t, err)
info2.PostId = postId
info2.ChannelId = channelId
data, err := ss.FileInfo().GetForPost(postId, true, false, false)
require.NoError(t, err)
@@ -449,6 +465,7 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
t.Run("should not attach files to multiple posts", func(t *testing.T) {
userId := model.NewId()
postId := model.NewId()
channelId := model.NewId()
info, err := ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
@@ -458,16 +475,17 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
require.Equal(t, "", info.PostId)
err = ss.FileInfo().AttachToPost(info.Id, model.NewId(), userId)
err = ss.FileInfo().AttachToPost(info.Id, model.NewId(), channelId, userId)
require.NoError(t, err)
err = ss.FileInfo().AttachToPost(info.Id, postId, userId)
err = ss.FileInfo().AttachToPost(info.Id, postId, channelId, userId)
require.Error(t, err)
})
t.Run("should not attach files owned from a different user", func(t *testing.T) {
userId := model.NewId()
postId := model.NewId()
channelId := model.NewId()
info, err := ss.FileInfo().Save(&model.FileInfo{
CreatorId: model.NewId(),
@@ -477,12 +495,13 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
require.Equal(t, "", info.PostId)
err = ss.FileInfo().AttachToPost(info.Id, postId, userId)
err = ss.FileInfo().AttachToPost(info.Id, postId, channelId, userId)
assert.Error(t, err)
})
t.Run("should attach files uploaded by nouser", func(t *testing.T) {
postId := model.NewId()
channelId := model.NewId()
info, err := ss.FileInfo().Save(&model.FileInfo{
CreatorId: "nouser",
@@ -491,12 +510,13 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
require.NoError(t, err)
assert.Equal(t, "", info.PostId)
err = ss.FileInfo().AttachToPost(info.Id, postId, model.NewId())
err = ss.FileInfo().AttachToPost(info.Id, postId, channelId, model.NewId())
require.NoError(t, err)
data, err := ss.FileInfo().GetForPost(postId, true, false, false)
require.NoError(t, err)
info.PostId = postId
info.ChannelId = channelId
assert.EqualValues(t, []*model.FileInfo{info}, data)
})
}
@@ -504,26 +524,31 @@ func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
func testFileInfoDeleteForPost(t *testing.T, ss store.Store) {
userId := model.NewId()
postId := model.NewId()
channelId := model.NewId()
infos := []*model.FileInfo{
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
DeleteAt: 123,
},
{
PostId: model.NewId(),
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
},
@@ -549,6 +574,7 @@ func testFileInfoDeleteForPost(t *testing.T, ss store.Store) {
func testFileInfoPermanentDelete(t *testing.T, ss store.Store) {
info, err := ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
ChannelId: model.NewId(),
CreatorId: model.NewId(),
Path: "file.txt",
})
@@ -560,9 +586,11 @@ func testFileInfoPermanentDelete(t *testing.T, ss store.Store) {
func testFileInfoPermanentDeleteBatch(t *testing.T, ss store.Store) {
postId := model.NewId()
channelId := model.NewId()
_, err := ss.FileInfo().Save(&model.FileInfo{
PostId: postId,
ChannelId: channelId,
CreatorId: model.NewId(),
Path: "file.txt",
CreateAt: 1000,
@@ -571,6 +599,7 @@ func testFileInfoPermanentDeleteBatch(t *testing.T, ss store.Store) {
_, err = ss.FileInfo().Save(&model.FileInfo{
PostId: postId,
ChannelId: channelId,
CreatorId: model.NewId(),
Path: "file.txt",
CreateAt: 1200,
@@ -579,6 +608,7 @@ func testFileInfoPermanentDeleteBatch(t *testing.T, ss store.Store) {
_, err = ss.FileInfo().Save(&model.FileInfo{
PostId: postId,
ChannelId: channelId,
CreatorId: model.NewId(),
Path: "file.txt",
CreateAt: 2000,
@@ -600,9 +630,11 @@ func testFileInfoPermanentDeleteBatch(t *testing.T, ss store.Store) {
func testFileInfoPermanentDeleteByUser(t *testing.T, ss store.Store) {
userId := model.NewId()
postId := model.NewId()
channelId := model.NewId()
_, err := ss.FileInfo().Save(&model.FileInfo{
PostId: postId,
ChannelId: channelId,
CreatorId: userId,
Path: "file.txt",
})
@@ -668,6 +700,7 @@ func testFileInfoStoreGetFilesBatchForIndexing(t *testing.T, ss store.Store) {
require.NoError(t, err)
f1, err := ss.FileInfo().Save(&model.FileInfo{
PostId: o1.Id,
ChannelId: o1.ChannelId,
CreatorId: model.NewId(),
Path: "file1.txt",
})
@@ -686,6 +719,7 @@ func testFileInfoStoreGetFilesBatchForIndexing(t *testing.T, ss store.Store) {
f2, err := ss.FileInfo().Save(&model.FileInfo{
PostId: o2.Id,
ChannelId: o2.ChannelId,
CreatorId: model.NewId(),
Path: "file2.txt",
})
@@ -705,6 +739,7 @@ func testFileInfoStoreGetFilesBatchForIndexing(t *testing.T, ss store.Store) {
f3, err := ss.FileInfo().Save(&model.FileInfo{
PostId: o3.Id,
ChannelId: o3.ChannelId,
CreatorId: model.NewId(),
Path: "file3.txt",
})
@@ -737,6 +772,7 @@ func testFileInfoStoreCountAll(t *testing.T, ss store.Store) {
require.NoError(t, err)
f1, err := ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
ChannelId: model.NewId(),
CreatorId: model.NewId(),
Path: "file1.txt",
})
@@ -744,12 +780,14 @@ func testFileInfoStoreCountAll(t *testing.T, ss store.Store) {
_, err = ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
ChannelId: model.NewId(),
CreatorId: model.NewId(),
Path: "file2.txt",
})
require.NoError(t, err)
_, err = ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
ChannelId: model.NewId(),
CreatorId: model.NewId(),
Path: "file3.txt",
})

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

@@ -14,13 +14,13 @@ type FileInfoStore struct {
mock.Mock
}
// AttachToPost provides a mock function with given fields: fileID, postID, creatorID
func (_m *FileInfoStore) AttachToPost(fileID string, postID string, creatorID string) error {
ret := _m.Called(fileID, postID, creatorID)
// AttachToPost provides a mock function with given fields: fileID, postID, channelID, creatorID
func (_m *FileInfoStore) AttachToPost(fileID string, postID string, channelID string, creatorID string) error {
ret := _m.Called(fileID, postID, channelID, creatorID)
var r0 error
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
r0 = rf(fileID, postID, creatorID)
if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok {
r0 = rf(fileID, postID, channelID, creatorID)
} else {
r0 = ret.Error(0)
}