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:

  1. 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.
  1. JSON at the edges, relational at the core. Teams build in JSON; operations demand normalized truth. Dual access models (document + relational) are finally real.
  1. 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:

  1. Verify the player owns the loot box key
  2. Deduct the key from inventory
  3. Run probabilistic loot table logic
  4. Award items based on RNG and player progression state
  5. Update multiple tables atomically
  6. Return the result to the client
Traditional approach: Six round-trips between application server and database. At 5ms per query, that's 30ms minimum—before business logic. Stored procedure approach: One round-trip. Sub-10ms total, including business logic execution at the data layer.

#### 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
Scores are comparative w.r.t. game backends prioritizing JSON access, transactional integrity, and global reach.

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

  1. Where does your authoritative logic live? If not near the data, justify the latency.
  2. How will you operate globally? Reads, writes, retries, and failure semantics.
  3. What will you pay for simplicity? Sometimes "enterprise" is cheaper than three DIY systems.
  4. 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.