Using Momento Leaderboards
Momento Leaderboards is a first-class service with purpose-built APIs that supports leaderboards with tens of millions of items and rapid ingestion/querying/updates.
Leaderboard Client Methods
Create Leaderboard Client
To create and interact with leaderboards, you must first create a LeaderboardClient.
Parameters
- configuration - LeaderboardConfiguration: Options for configuring your leaderboard client. For more information, see SDK Configuration Objects.
- credentialProvider - CredentialProvider: See Instantiating CredentialProviders for more information on using your Momento API key.
Returns
- PreviewLeaderboardClient object capable of creating new leaderboards and interacting with existing ones.
- JavaScript
- Go
new PreviewLeaderboardClient({
configuration: LeaderboardConfigurations.Laptop.v1(),
credentialProvider: CredentialProvider.fromEnvironmentVariable('MOMENTO_API_KEY'),
});
credentialProvider, err := auth.NewEnvMomentoTokenProvider("MOMENTO_API_KEY")
if err != nil {
panic(err)
}
leaderboardClient, err = momento.NewPreviewLeaderboardClient(
config.LeaderboardDefault(),
credentialProvider,
)
if err != nil {
panic(err)
}
Create a Leaderboard
Use a LeaderboardClient to create a leaderboard by specifying a cache and leaderboard name.
Parameters
- cacheName - string: Which cache to create the leaderboard in.
- leaderboardName - string: What to name the leaderboard.
Returns
- Leaderboard object or Error
- JavaScript
- Go
// You can create multiple leaderboards using the same leaderboard client
// but with different cache and leaderboard names
leaderboardClient.leaderboard(cacheName, 'momento-leaderboard');
leaderboardClient.leaderboard(cacheName, 'acorns-leaderboard');
// Leaderboard and cache names must be non-empty strings
try {
leaderboardClient.leaderboard(cacheName, ' ');
} catch (error) {
console.log('Expected error creating a leaderboard with invalid leaderboard name:', error);
}
leaderboard, err = leaderboardClient.Leaderboard(ctx, &momento.LeaderboardRequest{
CacheName: cacheName,
LeaderboardName: "leaderboard",
})
if err != nil {
panic(err)
}
return leaderboard
Leaderboard Methods
Upsert elements
Inserts elements if they do not already exist in the leaderboard. Updates elements if they do already exist in the leaderboard. There are no partial failures; an upsert call will either succeed or fail.
Parameters
-
elements - Dictionary: Dictionary of (id, score) pairs to upsert.
- id: integer
- score: double
-
The id can be a player identifier, session identifier, browser identifier or any other kind of identifier you want to use for this scoreboard. The full unsigned 64 bit range is allowed here, between 0 and 2^63-1 inclusive.
-
An id can only appear in a leaderboard one time, meaning you can't have two scores for one player unless that player has two ids!
Returns
One of the following:
- JavaScript
- Go
// Upsert a set of elements as a Map
const elements1: Map<number, number> = new Map([
[123, 100.0],
[234, 200.0],
[345, 300.0],
[456, 400.0],
]);
const result1 = await leaderboard.upsert(elements1);
switch (result1.type) {
case LeaderboardUpsertResponse.Success:
console.log('Successfully upserted elements to leaderboard');
break;
case LeaderboardUpsertResponse.Error:
console.log('Upsert error:', result1.message());
throw new Error(
`An error occurred while attempting to call upsert on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result1.errorCode()}: ${result1.message()}`
);
}
// Or upsert a set of elements as a Record
const elements2: Record<number, number> = {
567: 500,
678: 600,
789: 700,
890: 800,
};
const result2 = await leaderboard.upsert(elements2);
switch (result2.type) {
case LeaderboardUpsertResponse.Success:
console.log('Successfully upserted elements to leaderboard');
break;
case LeaderboardUpsertResponse.Error:
console.log('Upsert error:', result2.message());
throw new Error(
`An error occurred while attempting to call upsert on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result2.errorCode()}: ${result2.message()}`
);
}
upsertElements := []momento.LeaderboardUpsertElement{
{Id: 123, Score: 10.33},
{Id: 456, Score: 3333},
{Id: 789, Score: 5678.9},
}
_, err := leaderboard.Upsert(ctx, momento.LeaderboardUpsertRequest{Elements: upsertElements})
if err != nil {
panic(err)
}
Upsert is implemented as a batched operation, so for large leaderboards, you can upsert in batches of up to 8192 elements.
- JavaScript
// To upsert a large number of elements, you must upsert
// in batches of up to 8192 elements at a time.
// This example shows how to paginate for a large value of `totalNumElements`, such as `20000`.
const elements = [...Array(totalNumElements).keys()].map(i => {
return {id: i + 1, score: i * Math.random()};
});
for (let i = 0; i < totalNumElements; i += 8192) {
// Create a Map containing 8192 elements at a time
const batch = new Map(elements.slice(i, i + 8192).map(obj => [obj['id'], obj['score']]));
// Then upsert one batch at a time until all elements have been ingested
const result = await leaderboard.upsert(batch);
switch (result.type) {
case LeaderboardUpsertResponse.Success:
break;
case LeaderboardUpsertResponse.Error:
console.log(`Error upserting batch [${i}, ${i + 8192})`);
break;
}
}
Fetch elements by score
Fetches elements that fall within the specified min and max scores.
Elements with the same score will be returned in alphanumerical order based on their ID (e.g. IDs of elements with the same score would be returned in the order [1, 10, 123, 2, 234, ...] rather than [1, 2, 10, 123, 234, ...]).
Parameters
Optional Parameters
- minScore - double: Inclusive lower bound for the score range. Defaults to
-inf
. - maxScore - double: Exclusive upper bound for the score range. Defaults to
+inf
. - order - LeaderboardOrder enum: The order to fetch the elements in. Defaults to ascending, meaning 0 is the lowest-scoring rank.
- offset - integer: The number of elements to skip before returning the first element. Defaults to 0.
- count - integer: The maximum number of elements to return. Defaults to 8192.
Returns
One of the following:
- JavaScript
- Go
// By default, FetchByScore will fetch the elements from the entire score range
// with zero offset in ascending order. It can return 8192 elements at a time.
const result1 = await leaderboard.fetchByScore();
switch (result1.type) {
case LeaderboardFetchResponse.Success:
console.log('Successfully fetched elements using open score range:');
result1.values().forEach(element => {
console.log(`\tId: ${element.id} | Rank: ${element.rank} | Score: ${element.score}`);
});
break;
case LeaderboardFetchResponse.Error:
throw new Error(
`An error occurred while attempting to call fetchByScore with no options on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result1.errorCode()}: ${result1.message()}`
);
}
// Example specifying all FetchByScore options. You can provide any subset of these options
// to modify your FetchByScore request.
const result2 = await leaderboard.fetchByScore({
minScore: 10,
maxScore: 600,
order: LeaderboardOrder.Descending,
offset: 2,
count: 10,
});
switch (result2.type) {
case LeaderboardFetchResponse.Success:
console.log('Successfully fetched elements by score using all options:');
result2.values().forEach(element => {
console.log(`\tId: ${element.id} | Rank: ${element.rank} | Score: ${element.score}`);
});
break;
case LeaderboardFetchResponse.Error:
throw new Error(
`An error occurred while attempting to call fetchByScore with all options on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result2.errorCode()}: ${result2.message()}`
);
}
minScore := 150.0
maxScore := 3000.0
offset := uint32(1)
count := uint32(2)
fetchOrder := momento.ASCENDING
fetchByScoreResponse, err := leaderboard.FetchByScore(ctx, momento.LeaderboardFetchByScoreRequest{
MinScore: &minScore,
MaxScore: &maxScore,
Offset: &offset,
Count: &count,
Order: &fetchOrder,
})
if err != nil {
panic(err)
}
switch r := fetchByScoreResponse.(type) {
case *responses.LeaderboardFetchSuccess:
fmt.Printf("Successfully fetched elements by score:\n")
for _, element := range r.Values() {
fmt.Printf("ID: %d, Score: %f, Rank: %d\n", element.Id, element.Score, element.Rank)
}
}
FetchByScore is implemented as a batch operation, so for large leaderboards, you can fetch in batches of up to 8192 elements.
You can page through multiple elements that fall within the requested score range using the offset
parameter until you receive an empty list, which indicates the end of the requested elements.
- JavaScript
// Use the offset option to paginate through your results if your leaderboard
// has more than 8192 elements.
// This example shows how to paginate for a large value of `totalNumElements`, such as `20000`.
for (let offset = 0; offset < totalNumElements; offset += 8192) {
const result = await leaderboard.fetchByScore({offset});
switch (result.type) {
case LeaderboardFetchResponse.Success:
processBatch(result.values());
break;
case LeaderboardFetchResponse.Error:
console.log(
`Error fetching batch by score [${offset}, ${offset + 8192}) (${result.errorCode()}: ${result.message()})`
);
}
}
Fetch elements by rank
Fetches elements that fall within the specified min and max ranks.
Parameters
- startRank - integer: Inclusive lower bound for the rank range. Defaults to 0.
- endRank - integer: Exclusive upper bound for the rank range. Defaults to
startRank
+ 8192.
Optional Parameters
- order - LeaderboardOrder enum: The order to fetch the elements in. Defaults to ascending, meaning 0 is the lowest-scoring rank.
Returns
One of the following:
- JavaScript
- Go
// By default, FetchByRank will fetch the elements in the range [startRank, endRank)
// in ascending order, meaning rank 0 is for the lowest score.
const result1 = await leaderboard.fetchByRank(0, 10);
switch (result1.type) {
case LeaderboardFetchResponse.Success:
console.log('Successfully fetched elements in rank range [0,10)');
result1.values().forEach(element => {
console.log(`\tId: ${element.id} | Rank: ${element.rank} | Score: ${element.score}`);
});
break;
case LeaderboardFetchResponse.Error:
throw new Error(
`An error occurred while attempting to call fetchByRank with no options on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result1.errorCode()}: ${result1.message()}`
);
}
fetchOrder := momento.ASCENDING
fetchByRankResponse, err := leaderboard.FetchByRank(ctx, momento.LeaderboardFetchByRankRequest{
StartRank: 0,
EndRank: 100,
Order: &fetchOrder,
})
if err != nil {
panic(err)
}
switch r := fetchByRankResponse.(type) {
case *responses.LeaderboardFetchSuccess:
fmt.Printf("Successfully fetched elements by rank:\n")
for _, element := range r.Values() {
fmt.Printf("ID: %d, Score: %f, Rank: %d\n", element.Id, element.Score, element.Rank)
}
}
For large leaderboards, you will need to fetch in batches of up to 8192 elements.
- JavaScript
// Use the startRank and endRank options to paginate through your leaderboard
// if your leaderboard has more than 8192 elements
// This example shows how to paginate for a large value of `totalNumElements`, such as `20000`.
for (let rank = 0; rank < totalNumElements; rank += 8192) {
const result = await leaderboard.fetchByRank(rank, rank + 8192, {order: LeaderboardOrder.Descending});
switch (result.type) {
case LeaderboardFetchResponse.Success:
processBatch(result.values());
break;
case LeaderboardFetchResponse.Error:
console.log(
`Error fetching batch by rank [${rank}, ${rank + 8192}) (${result.errorCode()}: ${result.message()})`
);
}
}
Fetch elements by ID
Fetches elements given a list of element IDs.
Parameters
- ids - List: List of integers representing the ids of the elements to fetch.
Optional Parameters
- order - LeaderboardOrder enum: The order to fetch the elements in. Defaults to ascending, meaning 0 is the lowest-scoring rank.
Returns
One of the following:
- JavaScript
- Go
// Provide a list of element IDs to get their ranks in ascending or descending order
const result = await leaderboard.getRank([123, 456, 789], {
order: LeaderboardOrder.Descending,
});
switch (result.type) {
case LeaderboardFetchResponse.Success:
console.log('Successfully fetched the rank of 3 elements:');
result.values().forEach(element => {
console.log(`\tId: ${element.id} | Rank: ${element.rank} | Score: ${element.score}`);
});
break;
case LeaderboardFetchResponse.Error:
throw new Error(
`An error occurred while attempting to call getRank on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result.errorCode()}: ${result.message()}`
);
}
getRankResponse, err := leaderboard.GetRank(ctx, momento.LeaderboardGetRankRequest{
Ids: []uint32{123, 456},
})
if err != nil {
panic(err)
}
switch r := getRankResponse.(type) {
case *responses.LeaderboardFetchSuccess:
fmt.Printf("Successfully fetched elements by ID:\n")
for _, element := range r.Values() {
fmt.Printf("ID: %d, Score: %f, Rank: %d\n", element.Id, element.Score, element.Rank)
}
}
Get leaderboard length
Gets the number of entries in the leaderboard. The leaderboard name is inferred from the Leaderboard
object.
Parameters
Returns
One of the following:
- JavaScript
- Go
const result = await leaderboard.length();
switch (result.type) {
case LeaderboardLengthResponse.Success:
console.log('Successfully retrieved leaderboard length:', result.length());
break;
case LeaderboardLengthResponse.Error:
throw new Error(
`An error occurred while attempting to call length on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result.errorCode()}: ${result.message()}`
);
}
lengthResponse, err := leaderboard.Length(ctx)
if err != nil {
panic(err)
}
switch r := lengthResponse.(type) {
case *responses.LeaderboardLengthSuccess:
fmt.Printf("Leaderboard length: %d\n", r.Length())
}
Remove elements
Removes elements with the specified IDs.
Parameters
- ids - List: A list of ids for the elements you want to remove.
Returns
One of the following:
- JavaScript
- Go
// Provide a list of element IDs to delete those elements
const result = await leaderboard.removeElements([123, 456, 789]);
switch (result.type) {
case LeaderboardRemoveElementsResponse.Success:
console.log('Successfully removed elements');
break;
case LeaderboardRemoveElementsResponse.Error:
throw new Error(
`An error occurred while attempting to call removeElements on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result.errorCode()}: ${result.message()}`
);
}
_, err := leaderboard.RemoveElements(ctx, momento.LeaderboardRemoveElementsRequest{Ids: []uint32{123, 456}})
if err != nil {
panic(err)
}
For large leaderboards, you will need to remove in batches of up to 8192 elements.
- JavaScript
// You can remove batches of 8192 elements at a time.
// This example shows how to paginate for a large value of `totalNumElements`, such as `20000`.
const ids = [...Array(totalNumElements).keys()];
for (let i = 0; i < totalNumElements; i += 8192) {
const result = await leaderboard.removeElements(ids.slice(i, i + 8192));
switch (result.type) {
case LeaderboardRemoveElementsResponse.Success:
break;
case LeaderboardRemoveElementsResponse.Error:
console.log(`Error removing batch [${i}, ${i + 8192}) (${result.errorCode()}: ${result.message()})`);
break;
}
}
Delete leaderboard
Deletes the leaderboard. The leaderboard name is inferred from the Leaderboard object.
Parameters
Returns
One of the following:
- JavaScript
- Go
const result = await leaderboard.delete();
switch (result.type) {
case LeaderboardDeleteResponse.Success:
console.log('Successfully deleted the leaderboard');
break;
case LeaderboardDeleteResponse.Error:
throw new Error(
`An error occurred while attempting to call delete on leaderboard 'momento-leaderboard' in cache '${cacheName}': ${result.errorCode()}: ${result.message()}`
);
}
_, err := leaderboard.Delete(ctx)
if err != nil {
panic(err)
}