Introducing MemoryStore - High Throughput, Low Latency Data Service!

Yay, now no need to worry about datastore cooldown.

Lovely dubbley, nice work! :heart_eyes:

New entries are usually missing full documentation on the Developer hub. I’m sure if you check back at some point the article and any other missing documentation will be there

1 Like

This is perfect for what I’m making right now!

I currently am making a system in where players input (filtered) text into a TextBox and this text would appear on a sign (across every server). I only wanted this text to be able to appear for a minute max and the MessangingService limits are just to small for me, so I was using datastores every 5 seconds but was quickly hitting my limits. So this service will really help me!

‘An error’ repeats in the tutorial, got scared I was reading it wrong for a bit!

Screenshot_20210921_172813

1 Like

Can an API be added to see what the rate limit is similar to how datastore has an API to view the rate limits on its API

3 Likes
  1. Will characters 0 and 128-255 (non-utf8) be supported properly without using JSON internally? string.pack is extremely useful, but DataStoreService and MessagingService currently fail when sending binary data. It’s important that we can use compact fix-sized data strings because of performance, storage quotas, and other limitations.

  2. Is MemoryStoreService:UpdateAsync preferable to DataStoreService:UpdateAsync if I’m implementing a save locking system for player data? The goal is to prevent a new server from overwriting a player’s data by waiting for their previous server to remove a “lock” appended to it (or by waiting for the previous lock to expire if their previous server crashed.) It would be possible to implement this lock using MemoryStoreService, and use DataStoreService:GetAsync/SetAsync afterwards. It seems like MemoryStoreService:UpdateAsync would significantly reduce bandwidth because the player’s entire save doesn’t need to be sent back with the lock appended to it, but it seems more complex to use multiple APIs to achieve this.

10 Likes

Sure thing!

My overall use case is that I have a bunch of AWS-based services that integrate with various external services we use to improve user experience - for example, we use AWS Lambda to handle log in/log out and fetch data from PlayFab, or other stores.

We use Amazon SQS to queue up messages that we want to send to servers; for example, sometimes we might want to tell a user they’ve been awarded a prize, received a communication from our support team, or something else that requires bridging from outside of Roblox to inside via some kind of Http request.

For point one, having a script connection that can listen for changes would mean we don’t have to poll for changes - we can do a system with MessagingService to indicate there is traffic that needs to be processed by the other servers (since generally, I’m not a fan of polling on repeat for changes) but this adds boilerplate to our setup which otherwise isn’t needed. So, I’m not too attached to this point since it is technically possible already but it would be nice :slight_smile:

For point two, having external API access (i.e. through a WebSocket gateway or a REST API) would allow us to introduce a backbone for communication and data storage between our Roblox servers backend APIs in AWS. We currently have a HA-oriented Redis setup which we use in conjunction with Simple Queue Service to figure out when there is information for a user that we don’t need/want to save in a database.

As an example use case, we have an in-game & discord based reporting system. People can use the report command via our Discord, or whilst they’re in game, to report behaviour that may be acceptable to Roblox (i.e. trading in game items or glitching across the map) but isn’t acceptable to us so we moderate it on top of that. Being able to unify the memory store between our in game and in-discord solutions would mean that we could respond instantly to users, or even set up a live chat setup for our support team to chat to users.

The current solution that most people use for this kind of communication is long polling, for example:
Reselim/rbx-engine.io: Engine.IO client for Roblox (github.com) but this is very heavy on requests / bandwidth and is prone to frequent issues. So, being able to send data over a queue via a REST API would be super useful.

6 Likes

I’ve noticed small misinformation on DevHub while looking on this page there are stated that the count parameter has maximum of 200 but when I’ve tried it in studio it gave me following error:

4 Likes

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