Skip to content

Commit

Permalink
Feature - Multiple Members Upsert Route (#17)
Browse files Browse the repository at this point in the history
* Added Put route into project

* Added leaderboard controller and also added payload to the given route

* Changed the method names, changed the LUA script to REDIS and created the method to insert for many members score

* Added tests and changed function names for better undestanding

* Corrected Tests

* Corrected the format of the leaderboard.go file, by running go fmt leaderboard/leaderboard.go

* Added new route to the doc and also made more tests into the new interface

* Added benchmark test for new route and made it faster

* Refactoring and making better performance

* Added new route to the lib interface, so that other projects using this lib can access it

* Updated doc with new benchmarks
  • Loading branch information
rodrigolck authored and cscatolini committed Oct 29, 2018
1 parent 5066580 commit c7eea52
Show file tree
Hide file tree
Showing 15 changed files with 699 additions and 95 deletions.
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Features
* **No leaderboard configuration** - Just start notifying scores for members of a leaderboard. There's no need to create, configure or maintain leaderboards. Let Podium do that for you;
* **Top Members** - Get the top members of a leaderboard whether you need by absolute value (top 200 members) or percentage (top 3% members);
* **Members around me** - Podium easily returns members around a specific member in the leaderboard. It will even compensate if you ask for the top member or last member to make sure you get a consistent amount of members;
* **Batch score update** - Send a member score to many different leaderboards in a single operation. This allows easy tracking of member rankings in several leaderboards at once (global, regional, clan, etc.);
* **Batch score update** - In a single operation, send a member score to many different leaderboards or many members score to the same leaderboard. This allows easy tracking of member rankings in several leaderboards at once (global, regional, clan, etc.);
* **Easy to deploy** - Podium comes with containers already exported to docker hub for every single of our successful builds. Just pick your choice!
* **Leaderboards with expiration** - If a player last update is older than (timeNow - X seconds), delete it from the leaderboard

Expand Down Expand Up @@ -44,15 +44,18 @@ Benchmarks

Podium benchmarks prove it's blazing fast:

BenchmarkSetMemberScore-4 20000 285962 ns/op 0.32 MB/s 5219 B/op 71 allocs/op
BenchmarkRemoveMember-4 50000 220081 ns/op 0.07 MB/s 3823 B/op 53 allocs/op
BenchmarkGetMember-4 30000 266313 ns/op 0.27 MB/s 4143 B/op 56 allocs/op
BenchmarkGetMemberRank-4 30000 231241 ns/op 0.25 MB/s 4319 B/op 57 allocs/op
BenchmarkGetAroundMember-4 10000 519063 ns/op 2.38 MB/s 8314 B/op 58 allocs/op
BenchmarkGetTotalMembers-4 30000 196277 ns/op 0.15 MB/s 3936 B/op 52 allocs/op
BenchmarkGetTopMembers-4 20000 455470 ns/op 2.59 MB/s 7973 B/op 54 allocs/op
BenchmarkGetTopPercentage-4 500 14354336 ns/op 8.28 MB/s 509746 B/op 65 allocs/op
BenchmarkSetMemberScoreForSeveralLeaderboards-4 1000 70326444 ns/op 1.55 MB/s 534548 B/op 96 allocs/op
BenchmarkSetMemberScore-8 30000 284307 ns/op 0.32 MB/s 5635 B/op 81 allocs/op
BenchmarkSetMembersScore-8 5000 1288746 ns/op 3.01 MB/s 51452 B/op 583 allocs/op
BenchmarkIncrementMemberScore-8 30000 288306 ns/op 0.32 MB/s 5651 B/op 81 allocs/op
BenchmarkRemoveMember-8 50000 202398 ns/op 0.08 MB/s 4648 B/op 68 allocs/op
BenchmarkGetMember-8 30000 215802 ns/op 0.33 MB/s 4728 B/op 68 allocs/op
BenchmarkGetMemberRank-8 50000 201367 ns/op 0.28 MB/s 4712 B/op 68 allocs/op
BenchmarkGetAroundMember-8 20000 397849 ns/op 3.14 MB/s 8703 B/op 69 allocs/op
BenchmarkGetTotalMembers-8 50000 192860 ns/op 0.16 MB/s 4536 B/op 64 allocs/op
BenchmarkGetTopMembers-8 20000 306186 ns/op 3.85 MB/s 8585 B/op 66 allocs/op
BenchmarkGetTopPercentage-8 1000 10011287 ns/op 11.88 MB/s 510300 B/op 77 allocs/op
BenchmarkSetMemberScoreForSeveralLeaderboards-8 1000 106129629 ns/op 1.03 MB/s 516103 B/op 98 allocs/op
BenchmarkGetMembers-8 2000 3931289 ns/op 9.13 MB/s 243755 B/op 76 allocs/op

To run the benchmarks: `make bench-redis bench-podium-app bench-run`.

Expand Down
1 change: 1 addition & 0 deletions api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func (app *App) configureApplication() error {
a.Get("/healthcheck", HealthCheckHandler(app))
a.Get("/status", StatusHandler(app))
a.Delete("/l/:leaderboardID", RemoveLeaderboardHandler(app))
a.Put("/l/:leaderboardID/scores", BulkUpsertMembersScoreHandler(app))
a.Put("/l/:leaderboardID/members/:memberPublicID/score", UpsertMemberScoreHandler(app))
a.Patch("/l/:leaderboardID/members/:memberPublicID/score", IncrementMemberScoreHandler(app))
a.Get("/l/:leaderboardID/members/:memberPublicID", GetMemberHandler(app))
Expand Down
58 changes: 52 additions & 6 deletions api/leaderboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func serializeMember(member *leaderboard.Member, position int, includeTTL bool)
return memberData
}

func serializeMembers(members []*leaderboard.Member, includePosition bool, includeTTL bool) []map[string]interface{} {
func serializeMembers(members leaderboard.Members, includePosition bool, includeTTL bool) []map[string]interface{} {
serializedMembers := make([]map[string]interface{}, len(members))
for i, member := range members {
if includePosition {
Expand All @@ -54,6 +54,52 @@ func serializeMembers(members []*leaderboard.Member, includePosition bool, inclu
return serializedMembers
}

// BulkUpsertMembersScoreHandler is the handler responsible for creating or updating members score
func BulkUpsertMembersScoreHandler(app *App) func(c echo.Context) error {
return func(c echo.Context) error {
lg := app.Logger.With(
zap.String("handler", "BulkUpsertMembersScoreHandler"),
)
leaderboardID := c.Param("leaderboardID")

var payload setMembersScorePayload
prevRank := c.QueryParam("prevRank") == "true"
scoreTTL := c.QueryParam("scoreTTL")

err := WithSegment("Payload", c, func() error {
if err := LoadJSONPayload(&payload, c, lg); err != nil {
app.AddError()
return err
}
return nil
})
if err != nil {
return FailWith(400, err.Error(), c)
}

members := make(leaderboard.Members, len(payload.MembersScore))
err = WithSegment("Model", c, func() error {
l := leaderboard.NewLeaderboard(app.RedisClient.Trace(c.StdContext()), leaderboardID, 0, lg)
for i, ms := range payload.MembersScore {
members[i] = &leaderboard.Member{Score: ms.Score, PublicID: ms.PublicID}
}
err = l.SetMembersScore(members, prevRank, scoreTTL)

if err != nil {
app.AddError()
return err
}
return nil
})
if err != nil {
return FailWithError(err, c)
}
return SucceedWith(map[string]interface{}{
"members": serializeMembers(members, false, scoreTTL != ""),
}, c)
}
}

// UpsertMemberScoreHandler is the handler responsible for creating or updating the member score
func UpsertMemberScoreHandler(app *App) func(c echo.Context) error {
return func(c echo.Context) error {
Expand Down Expand Up @@ -385,7 +431,7 @@ func GetAroundMemberHandler(app *App) func(c echo.Context) error {
return FailWith(400, err.Error(), c)
}

var members []*leaderboard.Member
var members leaderboard.Members
status := 404
err = WithSegment("Model", c, func() error {
l := leaderboard.NewLeaderboard(app.RedisClient.Trace(c.StdContext()), leaderboardID, pageSize, lg)
Expand Down Expand Up @@ -434,7 +480,7 @@ func GetAroundScoreHandler(app *App) func(c echo.Context) error {
return FailWith(400, err.Error(), c)
}

var members []*leaderboard.Member
var members leaderboard.Members
status := 404
err = WithSegment("Model", c, func() error {
l := leaderboard.NewLeaderboard(app.RedisClient.Trace(c.StdContext()), leaderboardID, pageSize, lg)
Expand Down Expand Up @@ -515,7 +561,7 @@ func GetTopMembersHandler(app *App) func(c echo.Context) error {
return FailWith(400, err.Error(), c)
}

var members []*leaderboard.Member
var members leaderboard.Members
err = WithSegment("Model", c, func() error {
l := leaderboard.NewLeaderboard(app.RedisClient.Trace(c.StdContext()), leaderboardID, pageSize, lg)
members, err = l.GetLeaders(pageNumber, order)
Expand Down Expand Up @@ -560,7 +606,7 @@ func GetTopPercentageHandler(app *App) func(c echo.Context) error {
return FailWith(400, "Percentage must be a valid integer between 1 and 100.", c)
}

var members []*leaderboard.Member
var members leaderboard.Members
status := 400
err = WithSegment("Model", c, func() error {
l := leaderboard.NewLeaderboard(app.RedisClient.Trace(c.StdContext()), leaderboardID, defaultPageSize, lg)
Expand Down Expand Up @@ -611,7 +657,7 @@ func GetMembersHandler(app *App) func(c echo.Context) error {

memberIDs := strings.Split(ids, ",")

var members []*leaderboard.Member
var members leaderboard.Members
err := WithSegment("Model", c, func() error {
var err error
l := leaderboard.NewLeaderboard(app.RedisClient.Trace(c.StdContext()), leaderboardID, defaultPageSize, lg)
Expand Down

0 comments on commit c7eea52

Please sign in to comment.