rpg-db: The RPG Database That Thinks Like Production Infrastructure

A PostgreSQL cluster for game worlds, built with Patroni, HAProxy, Flyway, and observability from the start.

8 to 10 min read View on GitHub More from KitchiDioless

A fortified game kingdom rises from a grid of data tiles, with a database core at the center and routing gates, sentinels, and monitoring dials layered around it. The scene explains that this RPG project is really an infrastructure system first, with game state protected by orchestration, routing, and observability.
The repo’s big idea is not just storing RPG data. It is running that data like a service that can survive failure.
Key Takeaways

A Game Database Built Like a Service, Not a Demo

Most RPG database examples stop at table design. rpg-db does the opposite. It wraps a game schema inside a real operations stack: PostgreSQL 15, Patroni, etcd, HAProxy, Flyway, Prometheus, Grafana, backups, and a synthetic load generator.

That changes the meaning of the project. The interesting question is not just how to store characters. It is how to keep a game world consistent when a node fails, reads spike, or the schema changes again.

The Real Trick Is the Entity Table

A central engraved slab labeled as the shared entity layer splits into two branches, one toward player data and one toward character data. The shared fields sit in the middle while specialized fields peel outward, showing how the schema separates common identity from role-specific details.
The schema uses a composition-over-inheritance pattern, so shared RPG attributes live once and specialized behavior diverges cleanly.

The schema’s main idea is simple: shared identity goes in one place, and gameplay-specific details branch out from there.

This is composition over inheritance in database form. Instead of stuffing every RPG concept into one wide table, Entity becomes the stable center. Player and Character then attach the details that only belong to each role.

CREATE TABLE Entity (
  id BIGINT PRIMARY KEY,
  name TEXT NOT NULL,
  race_id BIGINT NOT NULL,
  level INT NOT NULL
);

CREATE TABLE Player (
  entity_id BIGINT UNIQUE REFERENCES Entity(id),
  save_x INT,
  save_y INT
);

CREATE TABLE Character (
  entity_id BIGINT UNIQUE REFERENCES Entity(id),
  home_location TEXT
);

That structure matters because RPG data is relational by nature. A character is not just a row. It is an identity, a set of shared attributes, and a collection of role-specific state that should evolve without breaking the rest of the world.

Writes Go Through One Door, Reads Through Another

The operational core is the failover path. Patroni tracks leadership, etcd stores cluster state, and HAProxy sends traffic to the right PostgreSQL node. Writes go to the elected primary. Reads can fan out to replicas.

ConcernTypical game databaserpg-db
WritesMay hit any node or a manually managed primaryPinned to the elected primary through HAProxy and Patroni
ReadsUsually handled ad hocRouted to replicas for scaling
Failure handlingOften left to infrastructure laterBuilt into cluster behavior from the start
Schema evolutionPatch scripts or one-off dumpsFlyway versioning with migration history
ValidationManual testingSynthetic workload generation and metrics
ObservabilityOptionalPrometheus and Grafana are part of the stack

The important detail is not just that failover exists. It is that the application does not need a different mental model when a node dies. The routing layer absorbs the change. The game backend keeps speaking to the database the same way.

The Load Service Is a Synthetic Player With a Job

load_service/main.py acts like an internal player bot for the database. It runs RPG-shaped queries, compares indexed and non-indexed behavior, and exports Prometheus metrics so latency becomes visible instead of anecdotal.

That is a serious choice. Many projects prove correctness. This one also tries to prove performance shape, which is what matters when player inventory lookups, quest joins, and reputation checks start happening at the same time.

Flyway Turns Game Design Into a Migration Story

Flyway gives the repo a history. New tables, new indexes, and new gameplay concepts arrive as ordered migrations instead of a fragile dump-and-recreate cycle. The seeder also checks migration state before it writes data, which keeps setup aligned with schema versioning.

Change modelOne-off schema dumpFlyway migration chain
RiskHard to reason about after the factEach step is explicit and versioned
SeedingOften disconnected from schema stateSeeder checks migration history first
Rollback thinkingManual and messyAt least visible in the migration timeline
Game design impactNew features can break old dataFeatures can be introduced in controlled steps

That is the difference between a database you can demo and a database you can keep changing. An RPG grows by adding systems. Skills, items, analytics, and balancing all arrive later. The project is built to survive that reality.

Why This Feels More Like Bank Infrastructure Than Game Dev

The comparison is not flattering to most toy backends. A demo database asks whether the tables are right. rpg-db asks whether the world survives failover, whether reads can scale, whether migrations stay ordered, and whether the workload is measurable.

DimensionTypical sample reporpg-db
AvailabilityNot a design goalPrimary and replica behavior are explicit
ObservabilityUsually absentMetrics and dashboards are first-class
Workload realismFew canned queriesSynthetic load mirrors RPG access patterns
Operational maturityMostly setup codeBackups, health checks, and routing are part of the design
Mental modelDatabase as storageDatabase as a service that protects game consistency

That is why the repo stands out. It does not just model a game world. It models the operational burden of keeping that world alive.