Introducing MemoryStore - High Throughput, Low Latency Data Service!

Also, why should this be used over messaging service?

I agree, an example of how to do a Global Leaderboard would be very helpful, I’m a little confused as well.

The way I am thinking you could do it would be to make a leaderboard SortedMap and have each of the player’s IDs as the keys with the “score” as the value, then call GetRangeAsync() with no args to get the entire SortedMap and then sort it by score after the fact - but that seems really expensive to do especially if you want to have it display updated values often and had a lot of players. I guess if you only needed to update the leaderboard when a server starts and every ~10 minutes after the fact it could work, but I feel like there is probably a more efficient way of going about it.

If anyone has some insight on the best way to do this sort of thing I’d love to hear it.

7 Likes

There should be ways of reading how many items are currently in a given queue to communicate information to players about how long a particular thing will take. For example, if a player has queued up to join a different game server, I need to be able to communicate to that player how many other players are ahead of them in the queue. If there are 100 other players ahead of them, they might not want to sit in queue for that long.

Thank you so much for all of these great updates roblox! I’m so happy to be in the developer community at this time!!

This temporarily stores data while messaging service doesn’t.

WARNING: In a Sorted Map, values are limited to 1KB - they are not of unlimited size! (Is this a bug? Is it meant to be unlimited?)

When it comes to limits, the documentation is wrong (and missing) in many other places too. Testing in Studio, I have discovered these limits and behaviours:

SortedMap memory cost:

  • Keys are free
  • Values are not arbitrary but must be convertible to JSON (and therefore arbitrary strings suffer from the same inefficiency as data stores for numerous characters – for instance, “\0” is encoded as “\u0000” – @Tomarty I think you were asking about this)
  • Values have a maximum size of 1kb - the docs are outright wrong when they claim (in bold no less!) While keys have a size limit of 128 characters, there isn’t a size limit for values as long as they don’t go over the memory size limit. (It is correct about the key size limit.)
    • For a table value…
      • If t[1] exists, non-integer keys are ignored
      • If t[1] does not exist, numeric keys are all truncated and then converted to strings!
    • The cost of a value is usually equal to ​#game:GetService("HttpService"):JSONEncode(value) However, inaccurate decimals that are used as values (such as the number you get if you perform x = 0; for i = 1, 31 do x += 0.1 end – see how it’s not quite 3.1 if you do print(x-3.1)) are truncated sooner (using 12+ characters less compared to what JSONEncode tells you)

When the docs say 64KB, each KB is 1024 bytes (or 1024 characters after JSON encoding your values).

GetRangeAsync:

  • Has a maximum count of 100, not 200
  • Only counts as 1 request, not count requests (the documentation didn’t specify)

When you provide an expiration time, the documentation claims that it’s an int64, but note:

  • Passing in math.huge or 0/0 are both interpreted as 0 (and the key is deleted immediately)
  • Though the limit is apparently 30 days (2592000 seconds), it won’t error until you pass in 922337203686 or higher (though it errors with Code: 0, Error: An unexpected error has occurred.
  • All negative values from -1 through -922337203685 do not error and do not expire immediately (-1 does not expire after 1 second)
  • Fractional values are rounded to the nearest integer

Request count limits:

  • The docs say The quota you have for Studio will be very limited, similar to the minimum quota of an experience., but it should just say that they’re exactly the same (and you don’t count as a player, even if you enter Play Solo mode).
  • The request limit & regeneration works like this:
    1. If you have more than the request limit in the last 60 seconds, no more can be sent
      • If you exceed this limit, you get the error Code: 6, The rate of requests exceeds the allowed limit. {id string here} [all “Code:” errors have that id string; it changes for each request]
    2. While your spentRequests + availableRequests is lower than the limit for the last 60 seconds, it regenerates gradually (in studio, every second you get approximately 17 more, which might be because it rounds 1000/60 (16.67) up)
      • If you run out of requests, you get the error Request Failed.

Those rules mean that if you spend all your requests in 5 seconds, you won’t get any more requests for another 55 seconds, after which it’ll start regenerating (and it’ll take nearly a minute to regenerate them all).

16 Likes

Some questions, and answers:

  • Are items in a queue read and removed per server or per game? Items exist in a queue per game. When an item is made invisible, it will not be seen by all servers in the game. When any server removes an item, the item is removed for all servers.
  • What happens when the invisibility timeout is 0? A read item is immediately made visible to all servers.
  • What happens when two instances of the same queue have different invisibility timeouts? The invisibility timeout is sent with each read request. The items returned from a read request are affected by the timeout sent with that request.

Observations:

  • Queue/SortedMap names and SortedMap keys:
    • Do not contribute towards the memory quota.
    • Have a maximum length of 128 bytes.
    • Have a UTF-8 encoding.
    • Empty names cause an error.
    • Names containing only spaces (0x0A, 0x0B, 0x0C, 0x0D, 0x20) cause an error.
    • Control characters (0x00-0x32) are disallowed.
    • Invalid bytes are converted to %XX, where XX is the hexadecimal digits of the byte.
  • A request may resolve before data is committed. That is, a request that would cause the memory quota to be exceeded may not return an error. Instead, the error can occur on a subsequent request.

Problems:

  • Queue items made invisible seem to often become visible well before the timeout.
  • Queue and SortedMap names are not validated until an actual request is made.
  • Long Queue and SortedMap names can cause a request to fail with an ambiguous Request Failed error.
11 Likes

To any hero with a big heart: make an open-sourced matchmaking system!

This would be extremely helpful to so many people developing on Roblox!

3 Likes

Just a note: This is the way that all variant serialization (such as the serialization for values passed through remotes) works on Roblox, it’s not unique to the MemoryStore API.

5 Likes

Huh - this must have changed within the last couple years. I have a note to myself from Dec 2019 that events would error when given a table with non-string keys when t[1] == nil. Thanks for letting me know.

You do not remember incorrectly, this API has exactly the behavior you remember:

local MEM = game:GetService("MemoryStoreService"):GetSortedMap("Test")
MEM:SetAsync("Test", {[true] = true}, 0) --> Error: Invalid table key type used
2 Likes

On the behalf of everyone: THANK YOU! I’ve been waiting for this since 2018…

1 Like

Is memory store service synchronous or asynchronous? I currently already have a global matchmaking system with http service that is based around my external server being able to handle requests synchronously, so if it is synchronous I can make the change from http service to memory store service quite quick.

Edit: Looks like it’s asynchronous :confused:

We have been trialling storing the keys like this, noting the use of 0 for padding:

0000127_Player123456

Which represents a score of 127 for Player 123456.

When we store the current score, we cache the key value we used. Then, we have a loop that runs every N seconds and checks if the score for player 123456 has changed. If so, we delete the old key and replace with a new key, like: 0001024_Player123456

So when you sort the keys Descending, you get the highest values first and each leaderboard should have one value per user.

About to release this to prod today, so hopefully it all works :slight_smile:

5 Likes

Can we please have a way to toggle this to production in Studio? DataStores allow this, which is super helpful for debugging issues. Being able to communicate with production data will allow us to create tools that help developers see into the data easier than trying to write code in a finicky UI within a game server.

13 Likes

Thanks for the detailed response. The use case makes lots of sense. I’m wondering why this can’t be done with just HttpService plus local game server’s memory. What’s the value of access data in MemoryStore?

I’m almost finished :wink:

3 Likes

It absolutely can be done this way (although there isn’t any way to directly communicate to RCC instances from the outside world - all network traffic must be initiated by the Roblox server, and it only supports RESTful methods. This means we’re limited to long polling).

My view though is that if it isn’t practical or feasible to facilitate communications to specific game instances, it may be a better approach to have externally facing APIs that allow us to interact with the same global data & memory stores that our servers can talk to, which would still facilitate useful applications!


Example: Let’s say a popular frontpage Game A is introducing a live concert in collaboration with an artist; however, this live performance will be selling the best tickets via an in game bidding system. This is powered by memory stores. However, the advertisers for the band would like to embed on their website live ticket bidding information.

Without direct external access to the memory stores, the game server(s) would have to either send their latest bid information via http request (which would be expensive if we had many servers!), or they would have to elect amongst themselves which server to send this information (which is hard).

With direct external access to the memory stores, the game servers can handle their bidding and purchase flows whilst the advertisers site can query the memory store at its own pace without having to proxy through active game servers.

4 Likes

That is just incredible! Wow. You are really doing God’s work here - you will help so many people.

Thank you so much, and really looking forward to that! :slight_smile:

And, no way, you are the same guy from here. We talked 2 weeks ago about something similar lol

1 Like

Thx! By expensive, do you mean it takes too much quota of HttpService?