Lichess uses MongoDB as its primary database, storing over 4.7 billion games and supporting millions of active users. The architecture emphasizes async operations, denormalization for read performance, and strategic indexing for fast queries.
The largest collection in Lichess, storing 4.7B+ games:
Game Document Schema
case class Game( _id: GameId, // Unique 8-character game ID players: Players, // White and Black player info status: Status, // Created, Started, Mate, Draw, etc. turns: Int, // Number of half-moves (ply) startedAt: Instant, finishedAt: Option[Instant], winnerId: Option[UserId], binaryPieces: ByteArray, // Compressed board state binaryMoves: ByteArray, // Compressed move list clock: Option[Clock], // Time control settings daysPerTurn: Option[Int], // For correspondence games mode: Mode, // Casual or Rated variant: Variant, // Standard, Chess960, etc. analysed: Boolean, // Has computer analysis metadata: Metadata // Opening, tournament ID, etc.)case class Players( white: Player, black: Player)case class Player( userId: Option[UserId], rating: Option[Int], ratingDiff: Option[Int], provisional: Boolean, aiLevel: Option[Int] // For games vs computer)
Key optimizations:
Binary encoding: Moves and positions compressed to ~50 bytes per game
Denormalization: Player data embedded (no joins needed)
Selective indexing: Only frequently queried fields indexed
Game Compression
Games use custom binary encoding to minimize storage:
case class User( _id: UserId, // Username (unique, case-insensitive) username: String, // Display name perfs: Perfs, // Ratings by variant/time control enabled: Boolean, // Account active roles: List[String], // ROLE_ADMIN, ROLE_COACH, etc. profile: Option[Profile], seenAt: Option[Instant], // Last online playTime: Option[PlayTime], // Total time playing count: Count, // Game counts, AI games, etc. createdAt: Instant, lang: Option[String], // Preferred language plan: Option[Plan] // Patron status)case class Perfs( bullet: Perf, blitz: Perf, rapid: Perf, classical: Perf, correspondence: Perf, chess960: Perf, // ... other variants)case class Perf( glicko: Glicko, // Rating, RD, volatility nb: Int, // Number of games recent: List[IntRating], // Recent rating history latest: Option[Instant] // Last played)
Design notes:
All rating data embedded in user document (fast profile queries)
case class Study( _id: StudyId, name: String, members: List[StudyMember], // Collaborators with permissions position: Position.Ref, // Current chapter/node position ownerId: UserId, visibility: Visibility, // Public, Unlisted, Private settings: Settings, from: From, // Created from game/scratch likes: Likes, createdAt: Instant, updatedAt: Instant)case class Chapter( _id: ChapterId, studyId: StudyId, name: String, root: Node, // Tree of moves/variations tags: Tags, // PGN tags setup: Setup, // Starting position order: Int)// Chapters stored in separate collection// One study can have many chapters