Skip to content

Commit 2628546

Browse files
committed
add README.md
1 parent 52fd18c commit 2628546

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

README.md

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# DataCenterPlugin
2+
3+
Centralized data persistence layer for Minecraft server backends, providing unified database access, caching, and cross-plugin data consistency.
4+
5+
---
6+
7+
## Why This Exists
8+
9+
In a multi-plugin Minecraft server, each plugin typically manages its own data, leading to:
10+
11+
- duplicated database connections
12+
- inconsistent data models
13+
- race conditions across plugins
14+
- difficulty maintaining consistent updates across systems
15+
16+
DataCenterPlugin centralizes persistence into a single layer, enforcing consistent data access patterns and reducing duplication across plugins.
17+
18+
---
19+
20+
## Overview
21+
22+
DataCenterPlugin acts as the core data service for a Minecraft server backend.
23+
24+
It provides:
25+
26+
- unified database access
27+
- schema management
28+
- caching for performance
29+
- consistent read/write patterns
30+
31+
Other plugins depend on it for reliable player data storage and retrieval.
32+
33+
---
34+
35+
## Core Responsibilities
36+
37+
- Manage database connections using HikariCP
38+
- Register schemas and create tables automatically
39+
- Provide unified data access via `DataKey` abstractions
40+
- Maintain in-memory caching with TTL
41+
- Ensure consistency across plugins accessing shared data
42+
- Perform atomic upsert operations
43+
44+
---
45+
46+
## Architecture / Design
47+
48+
### Internal Components
49+
50+
- **DataCenter**
51+
Central manager handling connections and schema registration
52+
53+
- **DataKey**
54+
Defines a data domain (table + schema metadata)
55+
56+
- **Column**
57+
Maps Java types to SQL types
58+
59+
- **Data / SingleData / MultipleData**
60+
Represent stored entities and persistence logic
61+
62+
---
63+
64+
### Data Flow
65+
66+
```
67+
Player Event
68+
69+
Plugin Logic
70+
71+
DataCenter.get(DataKey, UUID)
72+
73+
Cache lookup (if enabled)
74+
75+
Database query (on miss)
76+
77+
Data object returned
78+
79+
Modification → Upsert
80+
81+
Cache update
82+
```
83+
84+
---
85+
86+
### Cross-Plugin Interaction
87+
88+
Plugins access the system through a shared `DataCenter` instance.
89+
90+
Each plugin:
91+
92+
1. Registers its `DataKey`
93+
2. Uses typed access to read/write data
94+
3. Relies on DataCenter for consistency
95+
96+
This avoids tight coupling while maintaining shared state.
97+
98+
---
99+
100+
## Data & State Management
101+
102+
### Storage Architecture
103+
104+
- **Primary storage:** MariaDB
105+
- **Connection pooling:** HikariCP
106+
- **Cache:** In-memory, per-module TTL-based
107+
- **Configuration:** YAML-based
108+
109+
---
110+
111+
### Data Models
112+
113+
- **SingleData**
114+
One value per player (e.g., credits)
115+
116+
- **MultipleData**
117+
Structured multi-column data (e.g., stats, rewards)
118+
119+
---
120+
121+
### Consistency Model
122+
123+
- Writes use an upsert strategy (UPDATE → fallback INSERT)
124+
- Cache is invalidated immediately on write
125+
- Read-after-write consistency is guaranteed within a single server instance
126+
- No distributed locking; consistency relies on controlled access through DataCenter
127+
128+
---
129+
130+
## Key Features
131+
132+
- Modular data registration via `DataKey`
133+
- Automatic table creation
134+
- TTL-based caching for frequently accessed data
135+
- Type-safe schema definitions
136+
- Built-in support for leaderboard queries
137+
138+
---
139+
140+
## Interesting Engineering Decisions
141+
142+
### Upsert-First Persistence
143+
144+
Instead of separating INSERT and UPDATE:
145+
146+
- Attempt UPDATE
147+
- If no rows affected → INSERT
148+
149+
This ensures:
150+
151+
- simpler calling code
152+
- correct handling of new players
153+
- idempotent operations
154+
155+
---
156+
157+
### CSV-Based Storage (Rewards)
158+
159+
Some modules store multiple values in a single column using CSV.
160+
161+
Tradeoff:
162+
163+
- simpler queries
164+
- less normalized schema
165+
166+
Chosen due to limited relational complexity needs.
167+
168+
---
169+
170+
### High-Precision Currency
171+
172+
Credits stored as:
173+
174+
```
175+
NUMERIC(15,3)
176+
```
177+
178+
Avoids floating-point rounding issues common in game economies.
179+
180+
---
181+
182+
## Design Tradeoffs
183+
184+
- Simplicity over full normalization (e.g., CSV storage)
185+
- Local caching instead of distributed caching (single-server assumption)
186+
- Explicit SQL instead of ORM for control and predictability
187+
- Auto-commit transactions instead of manual transaction control
188+
189+
---
190+
191+
## Challenges & Solutions
192+
193+
### 1. Cross-Plugin Data Access
194+
195+
**Problem:**
196+
Multiple plugins required shared access to player data without tight coupling.
197+
198+
**Solution:**
199+
Introduced `DataKey` registry pattern:
200+
201+
- plugins declare schemas
202+
- receive typed access objects
203+
- remain independent but consistent
204+
205+
---
206+
207+
### 2. Performance Under Load
208+
209+
**Problem:**
210+
Frequent reads (e.g., credits during PvP) caused excessive DB load.
211+
212+
**Solution:**
213+
Added module-specific TTL caching:
214+
215+
- reduced redundant queries
216+
- maintained acceptable staleness
217+
218+
---
219+
220+
### 3. Schema Evolution
221+
222+
**Problem:**
223+
Schema changes risk breaking existing deployments.
224+
225+
**Solution:**
226+
227+
- used `CREATE TABLE IF NOT EXISTS`
228+
- avoided destructive migrations
229+
- allowed incremental schema updates
230+
231+
---
232+
233+
## Example Flow: Player Earns Credits
234+
235+
1. Plugin requests credits:
236+
```
237+
Credits credits = dataCenter.get(CreditsKey.INSTANCE, playerUUID);
238+
```
239+
240+
2. Cache is checked
241+
242+
3. On miss → database query executed
243+
244+
4. Plugin updates value:
245+
```
246+
credits.add(100.0);
247+
```
248+
249+
5. Upsert operation persists data
250+
251+
6. Cache updated
252+
253+
---
254+
255+
## Limitations
256+
257+
- Designed for single-server architecture (no multi-server sync)
258+
- No distributed cache coherence
259+
- No explicit transaction boundaries beyond single queries
260+
- Schema migrations are manual
261+
- No concurrency control beyond server-thread model
262+
263+
---
264+
265+
## Usage
266+
267+
This is a library plugin.
268+
269+
Example:
270+
271+
```
272+
DataCenter dataCenter = SolarDataCenter.ins.getDataCenter();
273+
Credits credits = dataCenter.get(CreditsKey.INSTANCE, playerUuid);
274+
```
275+
276+
---
277+
278+
## Tech Stack
279+
280+
- Java 8
281+
- Spigot API (1.8.8)
282+
- MariaDB
283+
- HikariCP
284+
- YAML configuration
285+
- Maven
286+
287+
---
288+
289+
## Notes
290+
291+
Used in a live multiplayer server environment with frequent player-driven data updates.
292+
293+
The system prioritizes:
294+
295+
- consistency
296+
- simplicity
297+
- performance
298+
299+
over feature completeness.
300+
301+
---
302+
303+
## TODO / Improvements
304+
305+
- Persist schema metadata and detect missing columns on startup
306+
- Safely add new columns (`ALTER TABLE ADD COLUMN`) without breaking existing data
307+
- Replace all raw SQL with prepared statements
308+
- Centralize query construction to avoid unsafe dynamic SQL
309+
- Improve cache handling (configurable TTL, better invalidation)
310+
- Add basic transaction support for multi-step operations
311+
- Improve error handling and logging

0 commit comments

Comments
 (0)