Database schema
All data lives in one DynamoDB table per stage (abstract-play-dev, abstract-play-prod). The partition key (pk) names a logical record family; the sort key (sk) scopes items within that family.
Most access patterns use Query on pk with optional begins_with on sk. See subsystem pages for how each family is read and written.
Games
-
Games — full game state
- pk:
GAME - sk:
<metaGame>#<completedbit>#<gameid>
- pk:
-
Game comments — chat for a game
- pk:
GAMECOMMENTS - sk:
<gameid>
- pk:
-
Notes — per-user notes on a game
- pk:
NOTE - sk:
<gameId>#<userid>
- pk:
Users
-
Users — profile, settings, challenge lists (dashboard via indexes below)
- pk:
USER - sk:
<userid> - fields include
publicRivalries(boolean, default false) — opt in to public rivalries table cleaned(boolean, optional) — set by dashboard cruft cleanup for inactive users; cleared onme_dashboard(or legacyme) logindashboardMaintAt(number, optional) — lease timestamp for per-user dashboard maintenance lock (me_dashboardprune/timeout sweep)- Retired (Phase 5):
games[],gamesUpdate— removed from all USER records in prod (Aug 2025)
- pk:
-
Push subscriptions — web push endpoints (one record per browser/device)
- pk:
PUSH - sk:
<userid>#<subscriptionKey>(subscriptionKey= first 16 hex chars of SHA-256 ofpayload.endpoint) - fields:
payload,endpoint,updatedAt - legacy:
sk: <userid>(migrated on nextsave_pushor removed on 404/410)
- pk:
-
User list — public directory (name, country, lastSeen, stars, publicRivalries)
- pk:
USERS - sk:
<userid>
- pk:
-
Tags — per-user game tags
- pk:
TAG - sk:
<userid>
- pk:
-
Palettes — per-user UI color palettes
- pk:
PALETTES - sk:
<userid>
- pk:
-
Customizations — per-user, per-game UI settings
- pk:
CUSTOMIZATION#<userid> - sk:
<metaGame>
- pk:
-
Playground saves — per-user saved playground positions (unlimited slots)
- pk:
PLAYGROUND#<userid> - sk:
<uuid> - fields:
id,name,metaGame,date(epoch ms),body(JSON string; gzip-compressed when large)
- pk:
Retired (no longer written; purged from prod Aug 2025):
-
pk:
PLAYGROUND, sk:<userid>— legacy single-slot sandbox -
Player relations — blocking (bidirectional)
- pk:
PLAYER#<blockingPlayerId>, sk:BLOCKED#<blockedPlayerId> - pk:
PLAYER#<blockedPlayerId>, sk:BLOCKEDBY#<blockingPlayerId> - pk:
PLAYER#<userid>, sk:REPRESENTATIVE#<metaGame>#<gameid>— per-user representative-game index (max 2 per metaGame)
- pk:
-
Watched games — spectator dashboard list (not a participant)
- pk:
WATCHED#<userid>, sk:<gameid> - summary fields mirror dashboard
Gameobjects plusaddedAt,seen,lastChat
- pk:
-
Game watchers — reverse index for fan-out on moves and chat
- pk:
GAMEWATCHERS#<gameid>, sk:<userid>
- pk:
-
Highlighted games — player page pins (participant only)
- pk:
HIGHLIGHT#<userid>, sk:<metaGame>#<gameid> - summary fields plus
addedAtfor display order
- pk:
-
Representative games — community recommendations per metaGame
- pk:
REPRESENTATIVE#<metaGame>, sk:<userid>#<gameid> - fields:
userId,userName,addedAt, game summary fields
- pk:
-
Recommendation impression events — logged-in show/click/challenge telemetry for offline tuning (no live reads)
- pk:
RECOMMENDS#<userid> - sk:
<epochMs>#<random> - fields:
event,batchId,surface,tier,expiresAt(TTL, ~90 days), plusmetaGame,position,reasonType,gameIds,reasonsas applicable - no GSI; rate limit 50 events/user/UTC day
- pk:
-
Game Move layout feedback events — beta layout experiment telemetry (no live reads)
- pk:
LAYOUTFB#<userid> - sk:
<epochMs>#<random> - fields:
event,layoutId, plusrating,comment,toLayoutId,gameId,durationMsas applicable - no GSI; no TTL (manual purge when experiment ends); no rate limit
- pk:
-
In-app dashboard notifications — per-user feed shown on
me_dashboard(distinct from email/push)- pk:
NOTIFICATION#<userid> - sk:
<epochMs>#<random> - fields:
body(typed JSON object),expiresAt(TTL seconds; 180 days on create, tightened to 7 days on first dashboard fetch) - no GSI
body.typevalues:gameStart,gameEnd,ratingChange,challengeIssued,challengeDeclined,challengeRevoked,eventInvitationeventInvitationis sent when an organizer saves the invite list on a moderatedORGEVENT(event_update_invites); not used for automated tournaments. Body includeseventId,eventName,organizerId,organizerName. Inspect rows withbin/dump-dashboard.mjs--include-notifications(read-only; does not refresh TTL)
- pk:
Game lists
-
Current games by player — per-player active game summaries (stream-maintained; Phase 3 reads from here)
- pk:
CURRENTGAMES#<userid> - sk:
<gameid> - summary fields mirror dashboard
Gameobjects (id,metaGame,players,toMove,lastMoveTime,numMoves, etc.)
- pk:
-
Recent completed games by player — transient completed-dashboard membership (Phase 3b; stream-maintained on game complete)
- pk:
RECENTCOMPLETED#<userid> - sk:
<gameid> - summary fields mirror completed dashboard
Gameobjects (toMoveempty,gameEnded,winner,numMoves, etc.) - evicted from dashboard after seen + 7 days (when
lastChat <= seen), manual dismiss, or absent after backfill cleanup; opponent chat can re-add
- pk:
-
Completed games by metaGame — summary rows for the completed-games page
- pk:
COMPLETEDGAMES#<metaGame> - sk:
<timestamp>#<gameid>
- pk:
-
Completed games by player — one item per player per game
- pk:
COMPLETEDGAMES#<userid> - sk:
<timestamp>#<gameid>
- pk:
Retired (no longer written; purged from prod — zero rows remain):
- pk:
COMPLETEDGAMES, sk:<timestamp>#<gameid>— legacy global list - pk:
COMPLETEDGAMES#<metaGame>#<userid>, sk:<timestamp>#<gameid>— legacy per-player-per-game index
Exploration
- Game exploration — move tree for a game position entered by a user
- pk:
GAMEEXPLORATION#<gameid> - sk:
<userid>#<movenumber>
- pk:
Ratings and meta games
-
Ratings — per metaGame leaderboard data
- pk:
RATINGS#<metaGame> - sk:
<userid>
- pk:
-
Meta game counts — aggregate stats (retired monolith; admin recount writes sharded items)
- pk:
METAGAMES/ sk:COUNTS— retired (Phase 5); delete after verification
- pk:
-
Meta game counts (sharded, authoritative) — per-metaGame live counters; stream + inline app writes;
meta_gamesreads and adminupdate_meta_game_countswrite here- pk:
METAGAMES#<metaGame> - sk:
COUNTS - fields:
currentgames,completedgames,standingchallenges,stars,ratingsCount
- pk:
-
Per-user game overlay (Phase 5) — per-user
seen/lastChaton dashboard games; sole overlay store- pk:
USERGAME#<userid> - sk:
<gameid>
- pk:
Challenges
-
Standing challenges — open challenges listed by game
- pk:
STANDINGCHALLENGE#<metaGame> - sk:
<challengeid>
- pk:
-
Direct challenges — challenge details
- pk:
CHALLENGE - sk:
<challengeid>
- pk:
-
SDG-style standing requests — standing requests for open challenges with a limit
- pk:
REALSTANDING - sk:
<userid>
- pk:
Bots
-
Bot identity — Cognito client linkage and owner
- pk:
BOT - sk:
<clientId>
- pk:
-
Bot display name reservation
- pk:
BOTNAME - sk:
<normalizedName>
- pk:
Automated tournaments
-
Tournaments — signup or in-progress tournaments
- pk:
TOURNAMENT - sk:
<tournamentid>
- pk:
-
Tournament player — player reference
- pk:
TOURNAMENTPLAYER - sk:
<tournamentid>#<division>#<playerid>
- pk:
-
Tournament game — game reference
- pk:
TOURNAMENTGAME - sk:
<tournamentid>#<division>#<gameid>
- pk:
-
Completed tournaments
- pk:
COMPLETEDTOURNAMENT - sk:
<metaGame>#<tournamentid>
- pk:
-
Tournament counter — per metaGame + variants combination (
variantsis a sorted, pipe-delimited variant list)- pk:
TOURNAMENTSCOUNTER - sk:
<metaGame>#<variants> - fields:
counter,over
- pk:
Organized events
-
Events — organizer-run event details
- pk:
ORGEVENT - sk:
<eventid>
- pk:
-
Event players
- pk:
ORGEVENTPLAYER - sk:
<eventid>#<playerid>
- pk:
-
Event games
- pk:
ORGEVENTGAME - sk:
<eventid>#<gameid>
- pk:
WebSockets
-
WebSocket connections — active API Gateway connection registry
- pk:
wsConnections - sk:
<connectionId> userId,invisible,endpoint— connection owner and API GW endpoint URLwatchingGames— string set ofmetaGame#gameIdkeys the client wants game events forwantsPresence— boolean, default true; receive presence snapshot/delta messageswatchVersion—1when the client uses targeted game watch (enables strict filtering)ttl— Unix epoch expiry (24h; refreshed on subscribe andwatchGames)
- pk:
-
WebSocket presence sequence — monotonic counter for presence deltas
- pk:
wsMeta - sk:
presenceSeq seq— incremented on each debounced presence delta broadcast
- pk:
Related docs
- Games and moves
- Challenges
- Tournaments
- Events
- Bots
- WebSockets
- Player blocking
- Recommendations — impression event storage