Ledger

Limits

Every cap Ledger enforces and where it comes from.

Some of these are Roblox's and some are Ledger's. The Roblox ones you can't move. The Ledger ones are set below the real limit so you get a clean refusal instead of a failed save.

Names and keys

Cap
Store name47 charactersLedger, so <Name>_Tx fits in the datastore's 50
Entity key50 charactersRoblox
Transfer id64 charactersLedger
Transaction id50 charactersLedger

Player keys are the UserId, so there's nothing to think about there. Entity keys have to be valid UTF-8.

Size

Cap
Stored value4 MBRoblox
Folded state2 MBLedger
One op1 MBLedger
Unsaved ops1.5 MBLedger
Queued ops4096Ledger

State can hold numbers, strings, booleans, tables and buffers. A buffer costs about a third more than its length. That is what the datastore charges to store one, and what these caps count.

State caps at 2 MB rather than 4, which leaves room for the ops sitting on top of the snapshot.

An op that would push state over the cap is refused with Full. Ops that shrink it are still allowed, so a cleanup works when you're already over.

Hitting the unsaved caps gives you Backlog, and in practice that only happens when the datastore is down.

Ledger keeps three tables of its own in the state, and they clear on different rules:

HoldsClears after
_Receivednames already applied30 days
_Heldmoney set aside for a transfer7 days to deliver, 8 to give back
_Bookedunits set aside on the keythe reservation's hold, 15 minutes by default

None of them grows with uptime. Each is bounded by how much happened inside its own window.

Applied names

The set of applied ids lives in the state, so it counts against the state cap. It does not grow without bound. Every time a name is written into it, anything already in there older than 30 days is dropped, whichever kind of name it is.

That sweep only rebuilds the set when something in it is actually old enough to go, so the usual write costs nothing.

One entry is about 40 bytes when Ledger picked the id, more if you named the transfer yourself, so a rolling 30 day window is nowhere near the state cap for any real player. An entry also carries a short fingerprint of what the name meant, which is what lets a name reused for a different amount answer Spent instead of true.

ClearDelivered runs that sweep early. It cannot drop a delivery id sooner than 30 days, because a sender can ask for a refund up to that point and needs the evidence to know the delivery already happened.

Naming your transfers tip:8412 rather than after a random string keeps this set small.

Transactions

Between 2 and 4 legs, and a key can only appear once.

One key doesn't need a transaction, which is why two is the minimum. Edit already applies to a single key or refuses it.

The maximum is four because each leg holds up a key while it waits. A leg is written to its key first and sits there unresolved, and anything else writing to that key meanwhile answers Busy. If the server running the transaction dies, another one has to abort it, and it may not do that until 30 seconds per leg have passed. Four legs can hold four keys for two minutes.

A transaction is considered dead after 60 seconds, so another server will abort it on the original's behalf. Committed markers get tidied up in the background about an hour later.

A transaction id ages out after 30 days, the same as every other name. A transaction lives at most an hour, so 30 days covers the whole protocol many times over. What it does not cover is you reusing the same id a month later, which applies it again. Derive ids from the thing being settled and they are never reused.

One key at a time

A key takes one transaction at a time. While a leg is parked on it, every other transaction touching that key answers Busy and stops. Tx does not retry a Busy for you, because every server retrying at once would multiply the load on a key that is already contended.

Retry it yourself with a short backoff. Measured against the mock, 8 servers depositing into one bank key all get through in about 3 attempts each. 32 servers take about 8 attempts each, and the cost per successful transaction goes from 10 requests to over 100.

Plan around this for anything every player writes to, so a guild bank, a global shop, or an event pot. Spread the writes across keys where you can, for example one key per guild rather than one for all of them. Correctness never suffers from contention, only throughput.

What an operation costs

Measured on the mock, for the path where nothing fails.

operationdatastore requests
Peek, Edit, Bump1
Reserve, Confirm, Release1
Grant4
Transfer3
Tx, 2 legs8
Tx, 4 legs12
Total16

A transaction costs 4 requests on its marker whatever the leg count, which is why the two leg case is the one worth avoiding. A limit that lives on one key is a reservation, not a transaction. See Reservations and totals.

Roblox gives an experience UpdateAsync budget of 300 + 20 per CCU a minute, which works out at about 20 writes per player per minute at any real player count. So a player can average roughly 6 transfers or 2 two leg transactions a minute across the whole experience, before autosave takes its share. Autosave itself is cheap and does not scale with how busy a player is, see Timing.

Transfers

A stranded transfer is redriven for 7 days. After that it expires and refunds. Delivered ids stay in the receiver's applied set for 30 days so a late retry can't pay twice.

Recovery handles up to 32 holds per pass, so a key with a lot stranded takes a few passes.

Reservations and totals

A reservation lasts 15 minutes by default, not the 30 days an applied name lasts. Hold sets the time per reservation, so a slow checkout can ask for longer without making every reservation wait.

Two things clear the ones that ran out. The next Reserve on that key clears every one of them at once. The recovery sweep clears 32 a pass, so a key nobody reserves again takes a few minutes to drain.

That means only the reservations open right now take up room. One is about 55 bytes, so the state cap is about 37,000 open at the same time on one key. A 15 minute window makes that hard to reach.

The hold lives on the reservation, not in the store options. Every server then folds the same log the same way, whatever it is configured with.

The recovery sweep settles up to 32 overdue reservations a pass on one key. A key with a lot of them takes a few passes. Each one that names a To costs a read of that key, because one already handed over has to be settled rather than given back.

A total is spread over 16 keys. Total reads all 16, so it costs 16 requests. Bump costs one.

See Reservations and totals.

Timing

Autosave runs every 30 seconds per session, and compacts as well when the log has gotten long.

A session with nothing queued and no transaction parked on it reads every fourth turn. An idle player then costs one request every two minutes rather than one every 30 seconds. It writes as soon as there is anything to write. Only the check for what other servers did waits.

An autosave is skipped when the server is under 4 requests of write budget, and it warns.

Compaction

The log is folded into the snapshot once it is worth rewriting the record. The snapshot size decides when. A small profile compacts every 128 ops. A large one waits until the log is a fifth of the snapshot. Nothing waits past 512 ops.

A big profile is expensive to rewrite, so rewriting it every 128 ops costs more than carrying them. A small one is cheap to rewrite, and folding fewer ops keeps loads fast.

Versions

Roblox keeps 30 days of history per key. History pages up to 100 at a time and defaults to 25.

What isn't limited

There's no cap on how many servers write the same key at once. The fold is why. It's the one number you don't have to plan around.

On this page