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.
- rpg-db treats game state like critical infrastructure, not a demo schema.
- Its core design uses a shared Entity model so RPG relationships stay composable as the world grows.
- Patroni, etcd, and HAProxy keep writes pinned to one leader while replicas absorb reads.
- Flyway, seeding, and synthetic load testing make the database evolvable and measurable under pressure.
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
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.
| Concern | Typical game database | rpg-db |
|---|---|---|
| Writes | May hit any node or a manually managed primary | Pinned to the elected primary through HAProxy and Patroni |
| Reads | Usually handled ad hoc | Routed to replicas for scaling |
| Failure handling | Often left to infrastructure later | Built into cluster behavior from the start |
| Schema evolution | Patch scripts or one-off dumps | Flyway versioning with migration history |
| Validation | Manual testing | Synthetic workload generation and metrics |
| Observability | Optional | Prometheus 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 model | One-off schema dump | Flyway migration chain |
|---|---|---|
| Risk | Hard to reason about after the fact | Each step is explicit and versioned |
| Seeding | Often disconnected from schema state | Seeder checks migration history first |
| Rollback thinking | Manual and messy | At least visible in the migration timeline |
| Game design impact | New features can break old data | Features 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.
| Dimension | Typical sample repo | rpg-db |
|---|---|---|
| Availability | Not a design goal | Primary and replica behavior are explicit |
| Observability | Usually absent | Metrics and dashboards are first-class |
| Workload realism | Few canned queries | Synthetic load mirrors RPG access patterns |
| Operational maturity | Mostly setup code | Backups, health checks, and routing are part of the design |
| Mental model | Database as storage | Database 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.