A backend engineer's journey of learning and growth.
by kan01234
Design a Gacha (ガチャ) System for a mobile game that:
+------------------+
| Game Client |
+--------+---------+
|
v
+--------------------+ +-----------------------+
| API Gateway |--------->| Gacha Service |
+--------------------+ +----------+------------+
|
+-----------------+-------------------+
| |
+----------v-----------+ +-----------v----------+
| RNG & Drop Engine | | Player Inventory DB |
+----------+-----------+ +-----------+----------+
| |
+-------------v------------+ +--------------v-------------+
| Gacha Config Service | | Transaction / Currency |
| (rates, events, pity) | | Service (Wallets) |
+--------------------------+ +-----------------------------+
RNG stands for Random Number Generator.
In games (especially gacha games), it’s the core of randomness — it’s how the game decides what you get when you “pull” (e.g., which item or character).
Example:
It’s like rolling a 100-sided die behind the scenes.
Pity is a player-friendly mechanic that increases your chance of getting a rare item after multiple unlucky tries.
There are two main types:
Type | Description |
---|---|
🧠 Soft Pity | Your chance increases slightly each time you don’t get the rare item. Example: UR starts at 1.5%, goes up by 0.1% per pull after 70 pulls. |
💎 Hard Pity | A guaranteed rare drop if you haven’t gotten it after a fixed number of pulls (e.g., UR guaranteed at pull #90). |
Example:
Pull Count | UR Chance |
---|---|
1 | 1.5% |
70 | 1.5% |
71 | 1.6% |
80 | 2.5% |
90 | 💥100% (guaranteed)** |
This system is used in games like Genshin Impact, Fate/Grand Order, and others — it builds trust and reduces frustration for unlucky players.
🧠 You may precompute drop tables per player or shard RNG seed per user/session for anti-cheating.
🎰 Gacha Config (in DB or Config Store)
Field | Type | Description |
---|---|---|
gacha_id | UUID | Unique Gacha pool |
rarity_tiers | JSON | { SSR: 1.5%, SR: 12%, R: 86.5% } |
items | JSON | List of items by rarity |
pity_limit | int | Guaranteed SSR after N pulls |
rate_up_items | List |
Limited-time boosted items |
🧾 Roll History
Field | Type |
---|---|
player_id | UUID |
gacha_id | UUID |
item_id | UUID |
rarity | String |
timestamp | DateTime |
is_rate_up | Boolean |
used_pity | Boolean |
Client sends POST /roll with gachaId and currency info
Gacha Service:
Component | Strategy |
---|---|
Gacha Logic | Stateless, horizontally scalable API |
Config Store | Cached in memory, or in CDN / Redis |
Roll History | Write-heavy, use append-only logs |
Inventory | Strong consistency, ideally in RDB |
RNG | Use secure RNG, consider DRBG |
Wallet | Pessimistic lock or atomic update |
Metric | Use |
---|---|
Rolls per second | Scale monitoring |
SSR/SR drop rates | Fairness and bug detection |
Pity counter triggered | Track behavior and user experience |
Currency consumption | Revenue analytics |
Unique items rolled | Event success metrics |
Data | Store | Why |
---|---|---|
Gacha Configs | Redis / S3 | Fast load, infrequent writes |
Roll History | Append-only DB or Kafka | Easy replay, audit trail |
Inventory / Player | PostgreSQL | ACID for inventory consistency |
Wallet | PostgreSQL | Atomic balance updates |
Analytics | Kafka + Druid | Real-time insights |
If each player rolls independently using RNG, then in theory, over enough rolls, the actual distribution should match the configured rates (e.g., 1% UR, 10% SR, etc.). But in practice:
✅ 1. Soft RNG + Pity System (Industry Standard)
Most games (e.g., Genshin, FGO, etc.):
This simplifies scaling, ensures fairness per user, and is acceptable to players if the mechanics are transparent.
🎯 This is the most common and scalable way to handle gacha drop fairness.
❗️2. Global Rate Enforcement (Not Recommended for Realtime Gacha)
If you want to enforce a global UR quota, you’d need to:
This is problematic:
Only some limited-time lottery events (not real-time gacha) might use this.
🧪 3. Pre-shuffled “Drop Bags” (Deterministic Fairness)
Used in competitive card games or small-scale events.
Mechanism:
Pros:
Cons:
🧠 Better Compromise: Per-Player Bounded RNG
Use a per-player strategy like:
This doesn’t enforce global fairness, but provides a bounded player experience, so no one is “eternally unlucky”.
Technique | Global Rate Control | Player Experience | Scalable | Used In Practice |
---|---|---|---|---|
Per-player RNG + Pity | ❌ | ✅ Excellent | ✅ Yes | ✅ Widely used |
Global Drop Rate Enforcement | ✅ | ❌ Bad | ❌ No | ❌ Rare |
Pre-shuffled Drop Bags | ✅ (per batch) | ✅ Fair | 🚫 No | 🔶 Event use only |
Per-player Bounded RNG | ❌ | ✅ Controlled | ✅ Yes | ✅ Often combined |
Component | Choice | Justification |
---|---|---|
RNG | Secure RNG + server-only | Prevents manipulation |
Gacha config | Cached + dynamic | Event support, fast load |
Currency | Wallet service (locked) | Consistent balance and auditability |
Inventory | RDB | Reliable item tracking |
History | Append log | For audit, replays, user support |
Observability | Metrics + logs + alerts | Operations visibility |