[MM-44488] Cloud limits: enforcing messages (#20362)
* Add new Job to keep updating the last_accessible_post time * Filter out posts for funcs returning PostList model * Separate methods to get and compute cache * filter pinned posts * For posts with sorted CreateAt order, support a faster form of filtering. * Add inaccessible header for getPost and getPostsByIDs APIs * replace manual binary search with the std. library * in-place filter posts Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Nathaniel Allred <neallred@protonmail.com>
Этот коммит содержится в:
@@ -5683,6 +5683,24 @@ func (s *OpenTracingLayerPostStore) GetMaxPostSize() int {
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) GetNthRecentPostTime(n int64) (int64, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetNthRecentPostTime")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PostStore.GetNthRecentPostTime(n)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) GetOldest() (*model.Post, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetOldest")
|
||||
|
||||
@@ -6428,6 +6428,27 @@ func (s *RetryLayerPostStore) GetMaxPostSize() int {
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) GetNthRecentPostTime(n int64) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PostStore.GetNthRecentPostTime(n)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) GetOldest() (*model.Post, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -193,6 +193,13 @@ func (th *SearchTestHelper) deleteUser(user *model.User) error {
|
||||
return th.Store.User().PermanentDelete(user.Id)
|
||||
}
|
||||
|
||||
func (th *SearchTestHelper) deleteBotUser(botID string) error {
|
||||
if err := th.deleteBot(botID); err != nil {
|
||||
return err
|
||||
}
|
||||
return th.Store.User().PermanentDelete(botID)
|
||||
}
|
||||
|
||||
func (th *SearchTestHelper) cleanAllUsers() error {
|
||||
users, err := th.Store.User().GetAll()
|
||||
if err != nil {
|
||||
|
||||
@@ -1649,7 +1649,7 @@ func testSearchTermsWithUnderscores(t *testing.T, th *SearchTestHelper) {
|
||||
func testSearchBotAccountsPosts(t *testing.T, th *SearchTestHelper) {
|
||||
bot, err := th.createBot("testbot", "Test Bot", th.User.Id)
|
||||
require.NoError(t, err)
|
||||
defer th.deleteBot(bot.UserId)
|
||||
defer th.deleteBotUser(bot.UserId)
|
||||
err = th.addUserToTeams(model.UserFromBot(bot), []string{th.Team.Id})
|
||||
require.NoError(t, err)
|
||||
p1, err := th.createPost(bot.UserId, th.ChannelBasic.Id, "bot test message", "", model.PostTypeDefault, 0, false)
|
||||
|
||||
@@ -1735,6 +1735,41 @@ var specialSearchChar = []string{
|
||||
":",
|
||||
}
|
||||
|
||||
// GetNthRecentPostTime returns the CreateAt time of the nth most recent post.
|
||||
func (s *SqlPostStore) GetNthRecentPostTime(n int64) (int64, error) {
|
||||
if n <= 0 {
|
||||
return 0, errors.New("n can't be less than 1")
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select("CreateAt").
|
||||
From("Posts p").
|
||||
// Consider users posts only for cloud limit
|
||||
Where(sq.And{
|
||||
sq.Eq{"p.Type": ""},
|
||||
sq.Expr("p.UserId NOT IN (SELECT UserId FROM Bots)"),
|
||||
}).
|
||||
OrderBy("p.CreateAt DESC").
|
||||
Limit(1).
|
||||
Offset(uint64(n - 1))
|
||||
|
||||
query, queryArgs, err := builder.ToSql()
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "GetNthRecentPostTime_tosql")
|
||||
}
|
||||
|
||||
var createAt int64
|
||||
if err := s.GetMasterX().Get(&createAt, query, queryArgs...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, store.NewErrNotFound("Post", "none")
|
||||
}
|
||||
|
||||
return 0, errors.Wrapf(err, "failed to get the Nth Post=%d", n)
|
||||
}
|
||||
|
||||
return createAt, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) buildCreateDateFilterClause(params *model.SearchParams, builder sq.SelectBuilder) sq.SelectBuilder {
|
||||
// handle after: before: on: filters
|
||||
if params.OnDate != "" {
|
||||
|
||||
@@ -385,6 +385,8 @@ type PostStore interface {
|
||||
GetOldestEntityCreationTime() (int64, error)
|
||||
HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error)
|
||||
GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error)
|
||||
// GetNthRecentPostTime returns the CreateAt time of the nth most recent post.
|
||||
GetNthRecentPostTime(n int64) (int64, error)
|
||||
}
|
||||
|
||||
type UserStore interface {
|
||||
|
||||
@@ -287,6 +287,27 @@ func (_m *PostStore) GetMaxPostSize() int {
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetNthRecentPostTime provides a mock function with given fields: n
|
||||
func (_m *PostStore) GetNthRecentPostTime(n int64) (int64, error) {
|
||||
ret := _m.Called(n)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int64) int64); ok {
|
||||
r0 = rf(n)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int64) error); ok {
|
||||
r1 = rf(n)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetOldest provides a mock function with given fields:
|
||||
func (_m *PostStore) GetOldest() (*model.Post, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -57,6 +57,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("GetForThread", func(t *testing.T) { testPostStoreGetForThread(t, ss) })
|
||||
t.Run("HasAutoResponsePostByUserSince", func(t *testing.T) { testHasAutoResponsePostByUserSince(t, ss) })
|
||||
t.Run("GetPostsSinceForSync", func(t *testing.T) { testGetPostsSinceForSync(t, ss, s) })
|
||||
t.Run("GetNthRecentPostTime", func(t *testing.T) { testGetNthRecentPostTime(t, ss) })
|
||||
}
|
||||
|
||||
func testPostStoreSave(t *testing.T, ss store.Store) {
|
||||
@@ -3765,3 +3766,98 @@ func getPostIds(posts []*model.Post, morePosts ...*model.Post) []string {
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func testGetNthRecentPostTime(t *testing.T, ss store.Store) {
|
||||
_, err := ss.Post().GetNthRecentPostTime(0)
|
||||
assert.Error(t, err)
|
||||
_, err = ss.Post().GetNthRecentPostTime(-1)
|
||||
assert.Error(t, err)
|
||||
|
||||
diff := int64(10000)
|
||||
now := utils.MillisFromTime(time.Now()) + diff
|
||||
|
||||
p1 := &model.Post{}
|
||||
p1.ChannelId = model.NewId()
|
||||
p1.UserId = model.NewId()
|
||||
p1.Message = "test"
|
||||
p1.CreateAt = now
|
||||
p1, err = ss.Post().Save(p1)
|
||||
require.NoError(t, err)
|
||||
|
||||
p2 := &model.Post{}
|
||||
p2.ChannelId = p1.ChannelId
|
||||
p2.UserId = p1.UserId
|
||||
p2.Message = p1.Message
|
||||
now = now + diff
|
||||
p2.CreateAt = now
|
||||
p2, err = ss.Post().Save(p2)
|
||||
require.NoError(t, err)
|
||||
|
||||
bot1 := &model.Bot{
|
||||
Username: "username",
|
||||
Description: "a bot",
|
||||
OwnerId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
_, err = ss.Bot().Save(bot1)
|
||||
require.NoError(t, err)
|
||||
|
||||
b1 := &model.Post{}
|
||||
b1.Message = "bot test"
|
||||
b1.ChannelId = p1.ChannelId
|
||||
b1.UserId = bot1.UserId
|
||||
now = now + diff
|
||||
b1.CreateAt = now
|
||||
_, err = ss.Post().Save(b1)
|
||||
require.NoError(t, err)
|
||||
|
||||
p3 := &model.Post{}
|
||||
p3.ChannelId = p1.ChannelId
|
||||
p3.UserId = p1.UserId
|
||||
p3.Message = p1.Message
|
||||
now = now + diff
|
||||
p3.CreateAt = now
|
||||
p3, err = ss.Post().Save(p3)
|
||||
require.NoError(t, err)
|
||||
|
||||
s1 := &model.Post{}
|
||||
s1.Type = model.PostTypeJoinChannel
|
||||
s1.ChannelId = p1.ChannelId
|
||||
s1.UserId = model.NewId()
|
||||
s1.Message = "system_join_channel message"
|
||||
now = now + diff
|
||||
s1.CreateAt = now
|
||||
_, err = ss.Post().Save(s1)
|
||||
require.NoError(t, err)
|
||||
|
||||
p4 := &model.Post{}
|
||||
p4.ChannelId = p1.ChannelId
|
||||
p4.UserId = p1.UserId
|
||||
p4.Message = p1.Message
|
||||
now = now + diff
|
||||
p4.CreateAt = now
|
||||
p4, err = ss.Post().Save(p4)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := ss.Post().GetNthRecentPostTime(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p4.CreateAt, r)
|
||||
|
||||
// Skip system post
|
||||
r, err = ss.Post().GetNthRecentPostTime(2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p3.CreateAt, r)
|
||||
|
||||
// Skip system & bot post
|
||||
r, err = ss.Post().GetNthRecentPostTime(3)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p2.CreateAt, r)
|
||||
|
||||
r, err = ss.Post().GetNthRecentPostTime(4)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p1.CreateAt, r)
|
||||
|
||||
_, err = ss.Post().GetNthRecentPostTime(10000)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, &store.ErrNotFound{}, err)
|
||||
}
|
||||
|
||||
@@ -5146,6 +5146,22 @@ func (s *TimerLayerPostStore) GetMaxPostSize() int {
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) GetNthRecentPostTime(n int64) (int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PostStore.GetNthRecentPostTime(n)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetNthRecentPostTime", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) GetOldest() (*model.Post, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user