Skip to content

Commit

Permalink
Implement reaction multi count
Browse files Browse the repository at this point in the history
Since the introduction of reactions one of the biggest impacts on
response times for feed/list requests has been the constant overhead of
countless network requests in order to populate counters for posts. This
is due the nature how we constructed the service interfaces. In this
change-set we introduce a specialised method to get all counts for a
list of posts at once. After we verify that this does impact the
response times significantly we can mirror the same idea in the cache
layer.
  • Loading branch information
Alexander Simmerl committed Aug 21, 2017
1 parent 876272f commit 951e6de
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 103 deletions.
2 changes: 1 addition & 1 deletion core/feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ func sourceReactions(
Type: fmt.Sprintf(
reactionEventFmt,
event.TypeReaction,
reaction.TypeToIdenitifier[r.Type],
reaction.TypeToIdentifier[r.Type],
),
UserID: r.OwnerID,
Visibility: event.VisibilityPrivate,
Expand Down
102 changes: 20 additions & 82 deletions core/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Post struct {
type PostCounts struct {
Comments int
Likes int
ReactionCounts ReactionCounts
ReactionCounts reaction.Counts
}

// PostFeed is the composite answer for post list methods.
Expand Down Expand Up @@ -96,6 +96,16 @@ func (ps PostList) OwnerIDs() []uint64 {
return ids
}

func (ps PostList) objectIDs() []uint64 {
ids := []uint64{}

for _, p := range ps {
ids = append(ids, p.ObjectID)
}

return ids
}

func postsFromObjects(os object.List) PostList {
ps := PostList{}

Expand Down Expand Up @@ -545,6 +555,14 @@ func enrichCounts(
currentApp *app.App,
ps PostList,
) error {
countsMap, err := reactions.CountMulti(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: ps.objectIDs(),
})
if err != nil {
return err
}

for _, p := range ps {
comments, err := objects.Count(currentApp.Namespace(), object.QueryOptions{
ObjectIDs: []uint64{
Expand All @@ -558,89 +576,9 @@ func enrichCounts(
return err
}

reactionCounts := ReactionCounts{}

reactionCounts.Angry, err = reactions.Count(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: []uint64{
p.ID,
},
Types: []reaction.Type{
reaction.TypeAngry,
},
})
if err != nil {
return err
}

reactionCounts.Haha, err = reactions.Count(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: []uint64{
p.ID,
},
Types: []reaction.Type{
reaction.TypeHaha,
},
})
if err != nil {
return err
}

reactionCounts.Like, err = reactions.Count(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: []uint64{
p.ID,
},
Types: []reaction.Type{
reaction.TypeLike,
},
})
if err != nil {
return err
}

reactionCounts.Love, err = reactions.Count(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: []uint64{
p.ID,
},
Types: []reaction.Type{
reaction.TypeLove,
},
})
if err != nil {
return err
}

reactionCounts.Sad, err = reactions.Count(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: []uint64{
p.ID,
},
Types: []reaction.Type{
reaction.TypeSad,
},
})
if err != nil {
return err
}

reactionCounts.Wow, err = reactions.Count(currentApp.Namespace(), reaction.QueryOptions{
Deleted: &defaultDeleted,
ObjectIDs: []uint64{
p.ID,
},
Types: []reaction.Type{
reaction.TypeWow,
},
})
if err != nil {
return err
}

p.Counts = PostCounts{
Comments: comments,
ReactionCounts: reactionCounts,
ReactionCounts: countsMap[p.ObjectID],
}
}

Expand Down
10 changes: 0 additions & 10 deletions core/reaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,3 @@ type ReactionFeed struct {
PostMap PostMap
UserMap user.Map
}

// ReactionCounts bundles all Reaction counts by type.
type ReactionCounts struct {
Angry uint
Haha uint
Like uint
Love uint
Sad uint
Wow uint
}
12 changes: 6 additions & 6 deletions handler/http/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,12 @@ type postCounts struct {
}

type reactionCounts struct {
Angry uint `json:"angry"`
Haha uint `json:"haha"`
Like uint `json:"like"`
Love uint `json:"love"`
Sad uint `json:"sad"`
Wow uint `json:"wow"`
Angry uint64 `json:"angry"`
Haha uint64 `json:"haha"`
Like uint64 `json:"like"`
Love uint64 `json:"love"`
Sad uint64 `json:"sad"`
Wow uint64 `json:"wow"`
}

type postFields struct {
Expand Down
6 changes: 5 additions & 1 deletion service/reaction/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func (s *cacheService) Count(ns string, opts QueryOptions) (uint, error) {
return aCount, err
}

func (s *cacheService) CountMulti(ns string, opts QueryOptions) (CountsMap, error) {
return nil, fmt.Errorf("cacheService.CountMulti not implemented")
}

func (s *cacheService) Put(ns string, input *Reaction) (*Reaction, error) {
key := cacheCountKey(QueryOptions{
ObjectIDs: []uint64{
Expand Down Expand Up @@ -104,7 +108,7 @@ func cacheCountKey(opts QueryOptions) string {
}

if len(opts.Types) == 1 {
ps = append(ps, TypeToIdenitifier[opts.Types[0]])
ps = append(ps, TypeToIdentifier[opts.Types[0]])
}

if len(opts.ObjectIDs) == 1 {
Expand Down
48 changes: 48 additions & 0 deletions service/reaction/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,54 @@ func testServiceCount(p prepareFunc, t *testing.T) {
}
}

func testServiceCountMulti(p prepareFunc, t *testing.T) {
var (
objectIDs = []uint64{
uint64(rand.Int63()),
uint64(rand.Int63()),
uint64(rand.Int63()),
}
ownerID = uint64(rand.Int63())
namespace = "service_count_multi"
service = p(t, namespace)
)

for _, oid := range objectIDs {
for _, r := range testList(oid, ownerID) {
r.ObjectID = oid

_, err := service.Put(namespace, r)
if err != nil {
t.Fatal(err)
}
}
}

want := CountsMap{}

for _, oid := range objectIDs {
want[oid] = Counts{
Angry: 5,
Haha: 3,
Like: 21,
Love: 9,
Sad: 1,
Wow: 7,
}
}

have, err := service.CountMulti(namespace, QueryOptions{
ObjectIDs: objectIDs,
})
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(have, want) {
t.Errorf("\nhave %v\nwant %v", have, want)
}
}

func testServicePut(p prepareFunc, t *testing.T) {
var (
deleted = true
Expand Down
10 changes: 10 additions & 0 deletions service/reaction/instrumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ func (s *instrumentService) Count(
return s.next.Count(ns, opts)
}

func (s *instrumentService) CountMulti(ns string, opts QueryOptions) (m CountsMap, err error) {
defer func(begin time.Time) {
s.track("CountMulti", ns, begin, err)
}(time.Now())

return s.next.CountMulti(ns, opts)
}

func (s *instrumentService) Put(
ns string,
input *Reaction,
Expand Down Expand Up @@ -132,6 +140,8 @@ type instrumentSource struct {
store string
}

// InstrumentSourceMiddleware observes key apsects of Source operations and exposes
// Prometheus metrics.
func InstrumentSourceMiddleware(
component, store string,
errCount kitmetrics.Counter,
Expand Down
20 changes: 20 additions & 0 deletions service/reaction/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,26 @@ func (s *logService) Count(ns string, opts QueryOptions) (count uint, err error)
return s.next.Count(ns, opts)
}

func (s *logService) CountMulti(ns string, opts QueryOptions) (m CountsMap, err error) {
defer func(begin time.Time) {
ps := []interface{}{
"duration_ns", time.Since(begin).Nanoseconds(),
"keys_count", len(m),
"method", "CountMulti",
"namespace", ns,
"opts", opts,
}

if err != nil {
ps = append(ps, "err", err)
}

_ = s.logger.Log(ps...)
}(time.Now())

return s.next.CountMulti(ns, opts)
}

func (s *logService) Put(ns string, input *Reaction) (output *Reaction, err error) {
defer func(begin time.Time) {
ps := []interface{}{
Expand Down
39 changes: 39 additions & 0 deletions service/reaction/mem.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,45 @@ func (s *memService) Count(ns string, opts QueryOptions) (uint, error) {
return uint(len(filterList(s.reactions[ns].ToList(), opts))), nil
}

func (s *memService) CountMulti(ns string, opts QueryOptions) (CountsMap, error) {
if err := s.Setup(ns); err != nil {
return nil, err
}

countsMap := CountsMap{}

for _, oid := range opts.ObjectIDs {
counts := Counts{}

for _, r := range s.reactions[ns] {
if r.Deleted {
continue
}

if r.ObjectID == oid {
switch r.Type {
case TypeAngry:
counts.Angry++
case TypeHaha:
counts.Haha++
case TypeLike:
counts.Like++
case TypeLove:
counts.Love++
case TypeSad:
counts.Sad++
case TypeWow:
counts.Wow++
}
}
}

countsMap[oid] = counts
}

return countsMap, nil
}

func (s *memService) Put(ns string, input *Reaction) (*Reaction, error) {
if err := s.Setup(ns); err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions service/reaction/mem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ func TestMemCount(t *testing.T) {
testServiceCount(prepareMem, t)
}

func TestMemCountMulti(t *testing.T) {
testServiceCountMulti(prepareMem, t)
}

func TestMemPut(t *testing.T) {
testServicePut(prepareMem, t)
}
Expand Down
Loading

0 comments on commit 951e6de

Please sign in to comment.