eliminate multiple calls to counter in paginated user comments

This commit is contained in:
Umputun
2018-06-01 22:51:19 -05:00
parent 837b2ef719
commit afbddaa5ec
6 changed files with 70 additions and 38 deletions
+6 -1
View File
@@ -444,11 +444,16 @@ func (s *Rest) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) {
log.Printf("[DEBUG] get comments for userID %s, %s", userID, siteID)
data, err := s.Cache.Get(cache.Key(cache.URLKey(r), userID, siteID), func() ([]byte, error) {
comments, count, e := s.DataService.User(siteID, userID, limit, 0)
comments, e := s.DataService.User(siteID, userID, limit, 0)
if e != nil {
return nil, e
}
comments = s.adminService.alterComments(comments, r)
count, e := s.DataService.UserCount(siteID, userID)
if e != nil {
return nil, e
}
resp.Comments, resp.Count = comments, count
return encodeJSONWithHTML(resp)
})
+29 -10
View File
@@ -294,14 +294,14 @@ func (b *BoltDB) Info(locator store.Locator, readOnlyAge int) (store.PostInfo, e
// User extracts all comments for given site and given userID
// "users" bucket has sub-bucket for each userID, and keeps it as ts:ref
func (b *BoltDB) User(siteID, userID string, limit, skip int) (comments []store.Comment, totalComments int, err error) {
func (b *BoltDB) User(siteID, userID string, limit, skip int) (comments []store.Comment, err error) {
comments = []store.Comment{}
commentRefs := []string{}
bdb, err := b.db(siteID)
if err != nil {
return nil, 0, err
return nil, err
}
if limit == 0 || limit > userLimit {
@@ -317,37 +317,56 @@ func (b *BoltDB) User(siteID, userID string, limit, skip int) (comments []store.
}
c := userIDBkt.Cursor()
totalComments = 0
skipComments := 0
for k, v := c.Last(); k != nil; k, v = c.Prev() {
totalComments++
if len(commentRefs) >= limit {
break
}
if skip > 0 && skipComments < skip {
skipComments++
continue
}
if len(commentRefs) < limit {
commentRefs = append(commentRefs, string(v))
}
commentRefs = append(commentRefs, string(v))
}
return nil
})
if err != nil {
return comments, totalComments, err
return comments, err
}
// retrieve comments for refs
for _, v := range commentRefs {
url, commentID, e := b.parseRef([]byte(v))
if e != nil {
return comments, totalComments, errors.Wrapf(e, "can't parse reference %s", v)
return comments, errors.Wrapf(e, "can't parse reference %s", v)
}
if c, e := b.Get(store.Locator{SiteID: siteID, URL: url}, commentID); e == nil {
comments = append(comments, c)
}
}
return comments, totalComments, err
return comments, err
}
// UserCount returns number of comments for user TODO: this can be slow, but userIDBkt just refs
func (b *BoltDB) UserCount(siteID, userID string) (int, error) {
bdb, err := b.db(siteID)
if err != nil {
return 0, err
}
count := 0
err = bdb.View(func(tx *bolt.Tx) error {
usersBkt := tx.Bucket([]byte(userBucketName))
userIDBkt := usersBkt.Bucket([]byte(userID))
if userIDBkt == nil {
return errors.Errorf("no comments for user %s in store", userID)
}
stats := userIDBkt.Stats()
count = stats.KeyN
return nil
})
return count, err
}
// Get returns comment for locator.URL and commentID string
+23 -16
View File
@@ -225,28 +225,25 @@ func TestBoltDB_GetForUser(t *testing.T) {
defer os.Remove(testDb)
b := prep(t)
res, count, err := b.User("radio-t", "user1", 5, 0)
res, err := b.User("radio-t", "user1", 5, 0)
assert.Nil(t, err)
assert.Equal(t, 2, len(res))
assert.Equal(t, 2, count)
assert.Equal(t, "some text2", res[0].Text, "sorted by -time")
res, count, err = b.User("radio-t", "user1", 1, 0)
res, err = b.User("radio-t", "user1", 1, 0)
assert.Nil(t, err)
assert.Equal(t, 1, len(res), "allow 1 comment")
assert.Equal(t, 2, count)
assert.Equal(t, "some text2", res[0].Text, "sorted by -time")
res, count, err = b.User("radio-t", "user1", 1, 1)
res, err = b.User("radio-t", "user1", 1, 1)
assert.Nil(t, err)
assert.Equal(t, 1, len(res), "allow 1 comment")
assert.Equal(t, 2, count)
assert.Equal(t, `some text, <a href="http://radio-t.com">link</a>`, res[0].Text, "second comment")
_, _, err = b.User("bad", "user1", 1, 0)
_, err = b.User("bad", "user1", 1, 0)
assert.EqualError(t, err, `site "bad" not found`)
_, _, err = b.User("radio-t", "userZ", 1, 0)
_, err = b.User("radio-t", "userZ", 1, 0)
assert.EqualError(t, err, `no comments for user userZ in store`)
}
@@ -271,36 +268,46 @@ func TestBoltDB_GetForUserPagination(t *testing.T) {
}
// seek 0, 5 comments
res, count, err := b.User("radio-t", "user1", 5, 0)
res, err := b.User("radio-t", "user1", 5, 0)
assert.Nil(t, err)
assert.Equal(t, 50, count)
assert.Equal(t, 5, len(res))
assert.Equal(t, "id-49", res[0].ID)
assert.Equal(t, "id-45", res[4].ID)
// seek 10, 3 comments
res, count, err = b.User("radio-t", "user1", 3, 10)
res, err = b.User("radio-t", "user1", 3, 10)
assert.Nil(t, err)
assert.Equal(t, 50, count)
assert.Equal(t, 3, len(res))
assert.Equal(t, "id-39", res[0].ID)
assert.Equal(t, "id-37", res[2].ID)
// seek 45, ask 10 comments
res, count, err = b.User("radio-t", "user1", 10, 45)
res, err = b.User("radio-t", "user1", 10, 45)
assert.Nil(t, err)
assert.Equal(t, 50, count)
assert.Equal(t, 5, len(res))
assert.Equal(t, "id-4", res[0].ID)
assert.Equal(t, "id-0", res[4].ID)
// seek 55, ask 10 comments
res, count, err = b.User("radio-t", "user1", 10, 55)
res, err = b.User("radio-t", "user1", 10, 55)
assert.Nil(t, err)
assert.Equal(t, 50, count)
assert.Equal(t, 0, len(res))
}
func TestBoltDB_GetForUserCounter(t *testing.T) {
defer os.Remove(testDb)
b := prep(t)
count, err := b.UserCount("radio-t", "user1")
assert.Nil(t, err)
assert.Equal(t, 2, count)
_, err = b.UserCount("bad", "user1")
assert.EqualError(t, err, `site "bad" not found`)
_, err = b.UserCount("radio-t", "userZ")
assert.EqualError(t, err, `no comments for user userZ in store`)
}
func TestBoltDB_Ref(t *testing.T) {
b := BoltDB{}
comment := store.Comment{
+1 -1
View File
@@ -206,7 +206,7 @@ func (b *BoltDB) Blocked(siteID string) (users []store.BlockedUser, err error) {
// get user name from comment user section
userName := ""
userComments, _, e := b.User(siteID, string(k), 1, 0)
userComments, e := b.User(siteID, string(k), 1, 0)
if e == nil && len(userComments) > 0 {
userName = userComments[0].User.Name
}
+1 -1
View File
@@ -109,7 +109,7 @@ func TestBoltAdmin_DeleteUser(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, 0, c, "0 count")
_, _, err = b.User("radio-t", "user1", 5, 0)
_, err = b.User("radio-t", "user1", 5, 0)
assert.EqualError(t, err, "no comments for user user1 in store")
comments, err := b.Last("radio-t", 10)
+10 -9
View File
@@ -27,15 +27,16 @@ type UserRequest struct {
// Accessor defines all usual access ops avail for regular user
type Accessor interface {
Create(comment store.Comment) (commentID string, err error) // create new comment, avoid dups by id
Get(locator store.Locator, commentID string) (store.Comment, error) // get comment by id
Put(locator store.Locator, comment store.Comment) error // update comment, mutable parts only
Find(locator store.Locator, sort string) ([]store.Comment, error) // find comments for locator
Last(siteID string, limit int) ([]store.Comment, error) // last comments for given site, sorted by time
User(siteID, userID string, limit, skip int) ([]store.Comment, int, error) // comments by user, sorted by time
Count(locator store.Locator) (int, error) // number of comments for the post
List(siteID string, limit int, skip int) ([]store.PostInfo, error) // list of commented posts
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error) // get post info
Create(comment store.Comment) (commentID string, err error) // create new comment, avoid dups by id
Get(locator store.Locator, commentID string) (store.Comment, error) // get comment by id
Put(locator store.Locator, comment store.Comment) error // update comment, mutable parts only
Find(locator store.Locator, sort string) ([]store.Comment, error) // find comments for locator
Last(siteID string, limit int) ([]store.Comment, error) // last comments for given site, sorted by time
User(siteID, userID string, limit, skip int) ([]store.Comment, error) // comments by user, sorted by time
UserCount(siteID, userID string) (int, error) // comments count by user
Count(locator store.Locator) (int, error) // number of comments for the post
List(siteID string, limit int, skip int) ([]store.PostInfo, error) // list of commented posts
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error) // get post info
}
// Admin defines all store ops avail for admin only