Collected on 2026-09-29 from the GitHub issues (open and closed) and the DevForum threads of
ProfileStore, ProfileService, DataStore2, Suphi’s DataStore Module, Lapis, DataKeep and DocumentService.
Every row names its source and the spec that proves KeepBlox’s answer. A row without a spec is not
closed.
Sources: PS MadStudioRoblox/ProfileService, PStore MadStudioRoblox/ProfileStore, DS2
Kampfkarren/Roblox, Lapis nezuo/lapis, DataKeep noahrepublic/DataKeep, DocS
anthony0br/DocumentService (#n is a GitHub issue). DevForum threads: ProfileService
t/667805, ProfileStore t/3190543,
Suphi t/2425597 (pN #m is page N, post m).
| Complaint |
Source |
KeepBlox |
Spec |
| 60-90 s wait for the lock after a server hop or auto-reconnect |
PS#41, PS p60 #1274 |
a live owner hands over on request in about a second (through MemoryStore when MessagingService is down); a dead server is known by its MemoryStore beat, and its keys are taken within about 12 s |
tests/unit/Lease.luau, bench/Benchmarks.md |
| Kicked after a manual save |
PStore p8 #153 |
a save never ends or re-claims the session |
Complaints: a manual save does not end the session |
| Lock kept after a failed load or close; player locked out |
Lapis#30, Lapis#46 |
a failed or cancelled load gives the key back; a failed migration too |
Complaints: a load in flight when shutdown begins gives the key back, Store: a cancelled load gives the key back |
| Every load became a force-load |
DataKeep#6 |
the claim transform decides from the record alone |
tests/unit/Lock.luau |
| An autosave in flight re-locks the key after close |
DocS#119, DocS#125 |
one write lane per profile; a released session never writes again |
Complaints: an autosave in flight never re-locks a released key |
| Quick rejoin into the same server: “Document not open” / stale closed document |
DocS#126, DataKeep#21 |
the load waits for this server’s own release, then claims afresh |
Complaints: a quick rejoin into the same server waits for the release |
| Complaint |
Source |
KeepBlox |
Spec |
| A failed read loads the template over saved data |
PStore p10 #195/#203 |
a load is one UpdateAsync transform; a failure retries and never writes |
Complaints: a failed read never loads the template over saved data |
Set without Get wipes data; no lock, a second device loses changes |
DS2#152, DS2#144 |
session lock on every key; a value that is not a profile is quarantined |
Store: a value that is not a profile is quarantined |
| Reading an old version overwrote live data |
DataKeep#22 |
readVersion returns a copy and never writes |
Complaints: reading an old version never changes the live profile |
| Autosave after a failed migration |
DocS#90 |
a failed migration gives the key back and writes nothing |
tests/unit/Migrate.luau |
| Number keys silently became strings |
Lapis#68 |
a table that is neither an array nor a string-keyed dictionary is refused, with its path |
Validate: every refusal names the path of the bad value |
| One bad value (bad UTF-8, a cycle, a function) fails every save until the game removes it |
PStore findings; the poison benchmark: 204-258 steps lost, or the player locked out |
the value is repaired in what is stored and reported with its path; only a table’s shape (mixed, holes, number keys, which Roblox would silently cut or rename) or the size limit still refuse the write |
Store: a bad value is repaired in what is stored, reported once with its path, bench/Benchmarks.md (poison: 0 lost) |
| Data lost past 4 MB |
PS p40 #848, PS#24 |
refused before the write, naming the field that grew; the last save stays |
Complaints: a profile over the size limit is refused with its path |
| Complaint |
Source |
KeepBlox |
Spec |
| Saves lost when the game’s own BindToClose competes |
PStore p5 #104 |
releases run in parallel and retry until the deadline |
Complaints: a game BindToClose that spends the budget does not cost saves |
| Loads accepted during shutdown |
DS2#126, Lapis#37 |
refused with “closing”, nothing written |
Complaints: a load after shutdown began is refused |
| BindToClose yields forever or always waits 30 s |
DocS#51, DocS#48 |
it waits only for pending releases, at most shutdownDeadline |
Store: shutdown releases every profile in parallel |
| Complaint |
Source |
KeepBlox |
Spec |
| The library’s own save queue grows without bound |
Lapis#41 |
manual saves join the one waiting; renewals skip a busy lane and wait for budget |
Complaints: a save loop becomes a few writes |
| Memory leak per player; loads slow after an hour |
DS2#124, PS p40 #851 |
nothing is kept after a release: no profile, subscription or thread |
Complaints: 300 joins and leaves leave nothing behind |
| Loads delayed when many players join at once |
PStore p10 #207 |
loads do not wait on each other |
Complaints: 50 players joining at once all load quickly |
| Viewing profiles blocks loads |
PS p60 #1270 |
support reads do not share the load path |
Complaints: reading versions does not hold up loads |
| Complaint |
Source |
KeepBlox |
Spec |
| Reconcile is manual and shallow |
Suphi p11 #213, PStore#9 |
a template store fills missing keys deeply on every load; a schema store fills its defaults |
Complaints: new template keys reach old profiles, deeply |
| Complaint |
Source |
KeepBlox |
Spec |
| No way to edit offline players (bans, gifts, bulk fixes) |
PS#48, PS p40 #834, DocS#58 |
store:edit(key, fn): edits in place when open here, claims quietly when nobody holds the key, “inUse” (and a store:message) when a live server does; never kicks |
tests/unit/Edit.luau |
| An edit that fails half-way leaves broken data |
DocS#92 |
an edit that throws or cannot be stored is undone and nothing is written |
Edit: an edit that throws writes nothing |
| Want Studio play tests that never save |
PStore p6 #113, DocS#65 |
studio = "memory" |
Mock: studio = memory never touches live data |
| Want to test against real profiles without touching them |
PStore p6 #113 |
studio = "copy": live data read once, unlocked, kept in memory |
Mock: studio = copy starts from the live data |
| Studio with API access writes a probe to a live key |
PStore findings |
the access check only reads |
Mock: Studio without API access falls back to memory |
| Complaint |
Source |
KeepBlox |
Spec |
| Moving from ProfileService is blocked (scope removed) |
PStore p5 #99, p8 #156 |
ProfileStore and ProfileService records load as they are; importers.* take scope |
tests/scenario/Compat.luau, tests/unit/Import.luau |
| No way off DataStore2, DocumentService, Lapis, DataKeep or Suphi’s module |
DS2#147 |
KeepBlox.importers: each key moves on its first load, the old value is only read, the old lock is honoured |
tests/unit/Import.luau |
| A failed read during a move starts the player from scratch |
PStore p10 #195 |
a failed read of the old store is retried until the load times out; nothing is written meanwhile |
Import: a failed read of the old store is retried |
| Migrations must be safe to write and test |
DocS#90, DocS#91 |
numbered migrations, KeepBlox.migrate for fixtures; a new player’s template is never migrated; imported data runs every migration |
tests/unit/Migrate.luau |
| Rolling back a release locks out every player who played on it |
Lapis 0.3.3 and DocumentService 1.2.2: their backwardsCompatible lets old servers read newer data only as it is, so it fits added fields, not renames |
{ up, down } migrations and writeVersion: the release stores data at the old version, turned back by down (saves, trades, new players, crash snapshots), so the release before reads everything; a down that does not undo its up is refused when the store is made |
tests/unit/Releases.luau |
| A trade right after a migration migrates the data twice |
found in KeepBlox itself (fixed before any release had it) |
every write stores the schema version with its data |
Releases: a trade right after a migrating load stores the schema version with its data |
| Request |
Source |
KeepBlox |
Spec |
| Leaderboards beside the profiles |
PS#33, PStore p5 #87/#101 (declined there) |
leaderboards option: chosen numbers mirrored to ordered data stores within the OrderedWrite budget; store:leaderboard reads the top, cached |
tests/unit/Boards.luau |
| Atomic trades between players |
DocS#127, ProfileStore findings #9 |
store:trade(a, b, fn): both or neither, whatever dies when, via a trade record and journals |
tests/unit/Trade.luau (a crash after every write, a lost answer, 40 seeds of failures) |
| Shared, unlocked documents (guilds, clans, global keys) |
Lapis#58, DocS#89, PStore p3 #55-59 |
KeepBlox.shared: update in one UpdateAsync from any server, watch for changes |
tests/unit/Shared.luau |
| Compression for large profiles |
PStore#7, DocS#112, PS#24 |
compress = { above = n }, Zstandard through EncodingService; opt-in because ProfileStore cannot read it |
tests/unit/Codec.luau |
| Rollback to an old version |
PStore p6 #108, DataKeep#4 |
versions, readVersion, restore (refused while a server holds the key); restore names the purchases it took back (purchasesSince), which stay granted, so support can make them good |
tests/unit/Versions.luau, Receipts: a restore names the purchases granted since the version it brings back |
| Saved / ended / error signals |
PS#5, PS#11, DocS#107, Lapis#38 |
onSaving, onSaved, onEnded(reason), store.onError |
tests/unit/Store.luau |
| UserIds for GDPR |
DS2#143, Lapis#21 |
addUserId / removeUserId, kept through imports |
tests/unit/Import.luau |
| Read-only views that cannot corrupt live data |
Lapis#44, DataKeep#22 |
readVersion returns a copy; load(key, { quiet = true }) never kicks |
tests/unit/Edit.luau |
| Complaint |
Source |
KeepBlox |
Spec |
| Wrong or missing Luau types |
PS#23, PStore p8 #162, DataKeep#31, Suphi p7 #124 |
--!strict everywhere, checked with the new solver on every commit |
scripts/type-check.sh |
| roblox-ts declarations missing or stale |
PStore#12 |
types/index.d.ts, checked against the Luau API |
tests/unit/Typings.luau |
| Mock mode breaks or hangs |
PS#34, PS#7, DataKeep#24 |
mock = true / a scratch name: the same code over in-memory stores |
tests/unit/Mock.luau |
| No way to test shutdown |
DocS#80 |
store:close() does what BindToClose does |
Mock: a test can close a store as a shutdown would |
| No official Wally / pesde package |
PS#25, PStore#21, PStore#10 |
xopoiii/keepblox on both, from 0.1.0 |
published: Installation |
| Complaint |
Source |
Why it does not apply |
| Breaks under Deferred events |
DS2#142 |
KeepBlox connects to no engine signal except BindToClose; its own signals call listeners directly. |
| MemoryStore throttling closes sessions |
Suphi p7 #134, p11 #209 |
KeepBlox’s lock lives in the DataStore record. Its MemoryStore heartbeat only speeds up hand-overs and takeovers: when MemoryStore is throttled or down, no session closes, and the lease rule takes over. |
| SubscribeAsync hangs in Team Test |
PStore p5 #93 |
the hand-over subscription never holds up a load (Upkeep.listen). |
| Lock scope differs between Studio and live |
Suphi p7 #128 |
the lock is part of the record; Studio without API access never touches a live key. |
- Cost against crash loss: for a library that only has the data store, writes an hour times the seconds
a crash can lose is about constant. KeepBlox steps out of it with the snapshots its server heartbeat
carries in MemoryStore (
src/Lease.luau): it writes the data store every 300 s, as cheaply as
ProfileStore, and a crash loses about one 4 s beat. A profile over snapshotBytes (1000 by default:
MemoryStore holds about 1.2 KB per player) is snapshotted as its edits since it was stored
(src/Snapshots.luau); one whose edits do not fit either is written every 30 s instead, which matches
ProfileService on both loss and cost.