Game Data in 2025: Storage, Logic, and Latency for Real Players
An opinionated field guide for teams shipping web apps and games right now. Stored procedures are back. JSON meets relational. Edge computing demands logic lives near data.
An opinionated field guide for teams shipping web apps and games right now.
Today's Reality
It's 2025. Players expect instant state sync, fair ranking, and zero drama during live events. Backend teams expect sane costs, observable systems, and tools that don't fight them. The database isn't just storage anymore; it's where core rules live, where contention shows up, and, increasingly, where we run code.
Three shifts set the tone:
- Global-by-default delivery. Edge networks and serverless runtimes made distance the enemy. Your data layer must keep up — read replicas, global routing, and pragmatic write strategies.
- JSON at the edges, relational at the core. Teams build in JSON; operations demand normalized truth. Dual access models (document + relational) are finally real.
- Logic locality. We moved business logic closer to data again — procedures, functions, triggers — for lower latency, fewer round trips, and safer transactions.
Stored Procedures in 2025: Not Nostalgia — Leverage
Stored procedures aren't a throwback; they're a performance and reliability tool — especially for games. Executing logic where the data sits means:
- Less network, lower latency. A leaderboard recompute, inventory swap, and purchase validation happen on the server in a single trip.
- Atomicity as a feature. Multi-step updates (matchmaking outcome + stat deltas + rewards + notifications) commit or roll back together.
- Security by surface reduction. Instead of arbitrary ad‑hoc queries, you expose curated entry points.
- Centralized evolution. Change logic once, everywhere. No more half‑migrated rules across microservices.
For live games, this is the difference between a smooth event and a Saturday fire drill.
Real Implementation: The Loot Box Transaction
Consider a typical game transaction: A player opens a loot box. The system must:
- Verify the player owns the loot box key
- Deduct the key from inventory
- Run probabilistic loot table logic
- Award items based on RNG and player progression state
- Update multiple tables atomically
- Return the result to the client
#### Oracle PL/SQL: Enterprise Game Systems
Oracle's PL/SQL remains the gold standard for complex game logic requiring absolute consistency.
CREATE OR REPLACE PROCEDURE open_loot_box(
p_player_id IN NUMBER,
p_loot_box_id IN NUMBER,
p_results OUT SYS_REFCURSOR
) AS
v_key_count NUMBER;
v_loot_table_id NUMBER;
v_awarded_items item_table_type;
BEGIN
-- Verify key ownership
SELECT COUNT(*) INTO v_key_count
FROM player_inventory
WHERE player_id = p_player_id
AND item_id = p_loot_box_id
AND quantity > 0
FOR UPDATE;
IF v_key_count = 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Insufficient loot box keys');
END IF;
-- Deduct key
UPDATE player_inventory
SET quantity = quantity - 1
WHERE player_id = p_player_id AND item_id = p_loot_box_id;
-- Execute loot table logic
SELECT loot_table_id INTO v_loot_table_id
FROM loot_boxes
WHERE id = p_loot_box_id;
-- Award items (complex probabilistic logic)
v_awarded_items := roll_loot_table(v_loot_table_id, p_player_id);
-- Insert awarded items
FORALL i IN v_awarded_items.FIRST..v_awarded_items.LAST
INSERT INTO player_inventory (player_id, item_id, quantity)
VALUES (p_player_id, v_awarded_items(i).item_id, v_awarded_items(i).qty)
ON DUPLICATE KEY UPDATE quantity = quantity + VALUES(quantity);
COMMIT;
-- Return results
OPEN p_results FOR
SELECT * FROM TABLE(v_awarded_items);
END;
Why Oracle excels here:
- FOR UPDATE row locking prevents race conditions in inventory systems
- User-defined types (
item_table_type) enable complex data structures - Nested function calls (
roll_loot_table) encapsulate game logic - Sub-5ms execution on modern hardware with proper indexing
#### PostgreSQL: The Open-Source Contender
PostgreSQL's PL/pgSQL has matured into a legitimate Oracle alternative for game backends.
CREATE OR REPLACE FUNCTION open_loot_box(
p_player_id INTEGER,
p_loot_box_id INTEGER
) RETURNS TABLE(item_id INTEGER, item_name TEXT, quantity INTEGER, rarity TEXT) AS $$
DECLARE
v_key_count INTEGER;
v_loot_table_id INTEGER;
v_awarded_item RECORD;
BEGIN
-- Verify and deduct key in one statement (PostgreSQL CTE magic)
WITH key_check AS (
UPDATE player_inventory
SET quantity = quantity - 1
WHERE player_id = p_player_id
AND item_id = p_loot_box_id
AND quantity > 0
RETURNING 1
)
SELECT COUNT(*) INTO v_key_count FROM key_check;
IF v_key_count = 0 THEN
RAISE EXCEPTION 'Insufficient loot box keys';
END IF;
-- Get loot table
SELECT lt.id INTO v_loot_table_id
FROM loot_boxes lb
JOIN loot_tables lt ON lb.loot_table_id = lt.id
WHERE lb.id = p_loot_box_id;
-- Roll loot (using PostgreSQL's random() and weighted selection)
FOR v_awarded_item IN
WITH rng AS (
SELECT random() AS roll
),
weighted_items AS (
SELECT
i.id,
i.name,
lt.quantity,
i.rarity,
SUM(lt.weight) OVER (ORDER BY lt.weight DESC) AS cumulative_weight
FROM loot_table_entries lt
JOIN items i ON lt.item_id = i.id
WHERE lt.loot_table_id = v_loot_table_id
)
SELECT id, name, quantity, rarity
FROM weighted_items
CROSS JOIN rng
WHERE rng.roll * (SELECT SUM(weight) FROM loot_table_entries WHERE loot_table_id = v_loot_table_id) <= cumulative_weight
LIMIT 1
LOOP
-- Insert awarded item
INSERT INTO player_inventory (player_id, item_id, quantity)
VALUES (p_player_id, v_awarded_item.id, v_awarded_item.quantity)
ON CONFLICT (player_id, item_id)
DO UPDATE SET quantity = player_inventory.quantity + EXCLUDED.quantity;
RETURN QUERY SELECT v_awarded_item.id, v_awarded_item.name, v_awarded_item.quantity, v_awarded_item.rarity;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Why PostgreSQL competes:
- CTEs (Common Table Expressions) enable elegant query composition
- Window functions simplify weighted random selection
- RETURNING clauses reduce round-trips
- Sub-8ms execution on properly tuned instances
- $0 licensing cost
#### MongoDB: JavaScript at the Data Layer
MongoDB's server-side JavaScript functions are underutilized in game backends.
db.system.js.save({
_id: "openLootBox",
value: function(playerId, lootBoxId) {
const session = db.getMongo().startSession();
session.startTransaction();
try {
const inventory = session.getDatabase("game").player_inventory;
const lootBoxes = session.getDatabase("game").loot_boxes;
// Verify and deduct key
const updateResult = inventory.updateOne(
{ player_id: playerId, item_id: lootBoxId, quantity: { $gt: 0 } },
{ $inc: { quantity: -1 } },
{ session }
);
if (updateResult.matchedCount === 0) {
throw new Error("Insufficient loot box keys");
}
// Roll and award (abbreviated)
const lootBox = lootBoxes.findOne({ _id: lootBoxId }, { session });
const awardedItem = rollLootTable(lootBox.loot_table_entries);
inventory.updateOne(
{ player_id: playerId, item_id: awardedItem.item_id },
{ $inc: { quantity: awardedItem.quantity } },
{ upsert: true, session }
);
session.commitTransaction();
return awardedItem;
} catch (error) {
session.abortTransaction();
throw error;
} finally {
session.endSession();
}
}
});
Trade-off: JavaScript execution is slower than native PL/SQL or PL/pgSQL. Expect 12-20ms for complex procedures.
First Principles for Game Data
- Design for the latency budget. Decide what must be under 20 ms, what survives 200 ms, and what can be async.
- Pick a consistency model on purpose. Not everything needs serializable; server‑authoritative flows often do.
- Separate hot state from history. Keep twitchy, frequently mutated state close to players; archive analytics elsewhere.
- Prefer normalized truth with JSON façades. Let the app speak JSON while storage enforces integrity.
The 2025 Landscape (Game‑Centric)
Oracle Cloud — Normal Database
What it is: The classic enterprise relational engine with PL/SQL, packages, triggers, native compilation paths, mature optimizer. Why it fits games: Strong procedures + packages make it easy to house ranking, validation, anti‑cheat checks, and transactional flows. Analytics and governance are first‑class. Trade‑offs: Cost and operational complexity. Excellent vertical scale; global, multi‑region patterns require careful planning.Oracle Cloud JSON DB + JSON Relational Duality (23ai)
What it is: Data stored relationally; exposed as updatable JSON documents via Duality Views. You can read/write a whole object graph (e.g., Player → Inventory → Loadouts) as one JSON doc while the DB maintains normalized tables under the hood. Why it fits games: You keep developer‑friendly JSON access without giving up constraints, joins, or ACID. Add AI Vector Search, Graph, and Spatial when your game needs recommendations, social graphs, or geodata — without bolting on new systems. Trade‑offs: Still Oracle: great power, non‑trivial cost. But for teams wanting JSON ergonomics + relational truth, it's a rare bull's‑eye.Supabase / PostgreSQL
What it is: Managed Postgres with JSONB, functions/procedures, triggers, and real‑time change streams. Why it fits games: Fast to adopt, strong developer ergonomics, and a rich extension ecosystem (e.g., pgvector for embeddings, PostGIS for maps). Great for indie to mid‑scale games, and for teams who want SQL without the enterprise tax. Trade‑offs: You can absolutely build serious procedure‑driven backends here; you just don't get Oracle‑style packages and native compilation out of the box. For extreme global write patterns, you'll need an explicit plan.Amazon Aurora (PostgreSQL‑compatible)
What it is: Cloud‑native Postgres with auto‑scaling storage, read replicas, and global database features like write‑forwarding. Why it fits games: If you're already on AWS, Aurora is a pragmatic path to global reads and high throughput, with familiar stored procedure semantics. Trade‑offs: Easier than rolling your own, but you're in the AWS way of doing things. True multi‑master globally still has caveats; costs can rise with global footprints.MongoDB Atlas
What it is: A document database with flexible schemas and easy horizontal scale. Why it fits games: Rapid evolution of player state and content payloads. Trade‑offs: Stored procedures and transactional richness lag relational systems. You'll often move more logic to services.Azure Cosmos DB
What it is: Globally distributed document store with multiple APIs and tunable consistency. Why it fits games: Global reach with managed multi‑region reads/writes. Trade‑offs: Stored procedures (JS) and complex transactional idioms aren't as expressive as mature relational stacks; costs scale with global ambitions.Worth Knowing (Adjacencies)
- Neon (serverless Postgres): scale‑to‑zero, branching, and separation of storage/compute for cost‑sensitive backends.
- PlanetScale (Vitess/MySQL): proven sharding, global gateways, safer schema changes — very strong for massive MySQL users.
- Cloudflare D1 + Hyperdrive: sqlite at the edge and pooled Postgres connections near users; great for reducing connection storms and latency from edge runtimes.
- Redis/Upstash: hot ephemeral state and rate‑limiting; pair with relational truth for speed.
- Vector/AI in DB: pgvector, Oracle AI Vector Search, or dedicated engines; keep embeddings near the transactional truth when possible.
Updated Scoring (Game-Focused)
| Alternative | PUT | GET | Stored Proc | Scalability | Ease | Cost | Validation | Search |
|---|---|---|---|---|---|---|---|---|
| Oracle Cloud Normal DB | 5 | 5 | 5 | 4 | 3 | 3 | 5 | 5 |
| Oracle JSON DB (Duality) | 5 | 5 | 5 | 5 | 5 | 4 | 5 | 5 |
| Supabase/Postgres | 4 | 4 | 5 | 4 | 5 | 5 | 5 | 5 |
| Amazon Aurora (Postgres‑comp.) | 5 | 5 | 5 | 5 | 4 | 3 | 5 | 5 |
| MongoDB Atlas | 4 | 4 | 2 | 5 | 5 | 4 | 3 | 4 |
| Azure Cosmos DB | 5 | 5 | 3 | 5 | 4 | 3 | 3 | 4 |
Why anything < 5?
- Oracle Normal — Scalability (4): Horizontal/global takes careful design.
- Oracle Normal — Ease (3), Cost (3): Power at a price; heavier to run.
- Oracle JSON — Cost (4): Simpler than full Oracle, still enterprise tier.
- Supabase/Postgres — PUT/GET (4/4): Fantastic general case; JSON+relational duality engines can edge it at extreme churn.
- Aurora — Ease (4), Cost (3): AWS‑integrated and scalable; costs rise with global.
- MongoDB — Stored Proc (2), Validation (3), Search (4): Flexibility wins, transactional depth doesn't.
- Cosmos — Stored Proc (3), Validation (3), Cost (3): Global strengths, but relational‑style logic depth/cost trade‑offs.
Patterns You Can Ship This Quarter
Pattern 1 — Regional realtime core
Setup: App/API at the edge, Postgres (Supabase/Aurora) in‑region, Redis for hot state, procedures for authoritative updates, changefeeds to clients. Use cases: competitive multiplayer, live leaderboards, cosmetics inventory.Pattern 2 — Global read‑heavy, write‑constrained
Setup: Regional read replicas + write‑forwarding to primary; procedures encapsulate writes; cache hot reads at the edge. Use cases: seasonal events, store fronts, player profiles.Pattern 3 — Duality for JSON‑first teams
Setup: Oracle JSON Duality Views provide JSON façades on normalized data; procedures handle ranking, fraud checks; vector search in‑DB for recommendations. Use cases: cross‑platform titles with evolving payloads and strict integrity.A Short Note on AI in the Data Layer
The exciting part isn't sprinkling embeddings everywhere. It's co‑locating intelligence with truth: store player embeddings next to progress, run shortlist ranking inside the DB, and call out to models only when necessary. Keep inference off the critical path when milliseconds matter; use it for better defaults and smarter queues.
What to Decide Now
- Where does your authoritative logic live? If not near the data, justify the latency.
- How will you operate globally? Reads, writes, retries, and failure semantics.
- What will you pay for simplicity? Sometimes "enterprise" is cheaper than three DIY systems.
- How will your team evolve the schema safely? JSON façades over normalized cores keep both speed and integrity.
Conclusion: The Storage War Evolved
In 2025, database selection for game backends is no longer a "pick one and commit" choice. It's an ongoing architectural dialogue between consistency requirements, latency budgets, and operational complexity.
The storage war isn't over. It's evolved into a sophisticated chess game where every piece has a purpose.
Ship the fun stuff — but respect the data path. The fastest teams aren't the ones with the trendiest stack. They're the teams who know where to put the rules, how to minimize distance, and when to let the database do what it does best.
---
About the Author: Odd-Arild Meling has architected game backends since 2016, from mobile idle games to AAA multiplayer systems. He's debugged race conditions in Oracle RAC clusters at 3 AM and celebrated sub-5ms PostgreSQL queries that saved millions in infrastructure costs. Currently building edge-first architectures at Gothar.