forked from Kweebec_Network/core-vitals
101 lines
4.3 KiB
Markdown
101 lines
4.3 KiB
Markdown
# Database
|
|
|
|
Shared MySQL pool, exposed to all plugins via `DatabaseService` from NetworkCore. Use this for durable state that needs to survive restarts and be queried later: player profiles, punishment history, currency balances, leaderboards, anything you'd run a SQL query against.
|
|
|
|
For ephemeral observability data (counters, latency), use the metrics layer instead. See [04_Metrics.md](04_Metrics.md).
|
|
|
|
## API
|
|
|
|
```java
|
|
public interface DatabaseService {
|
|
DataSource getDataSource();
|
|
boolean isHealthy();
|
|
int getActiveConnections();
|
|
int getIdleConnections();
|
|
int getTotalConnections();
|
|
}
|
|
```
|
|
|
|
That's it. We expose the `DataSource` and stay out of your way. Use JDBC directly, jOOQ, MyBatis, JdbcTemplate, whatever you like.
|
|
|
|
## Getting a connection
|
|
|
|
Always use try-with-resources so connections return to the pool:
|
|
|
|
```java
|
|
DatabaseService db = NetworkCore.getInstance().getDatabase();
|
|
if (db == null) {
|
|
// MySQL disabled in NetworkCore config; degrade gracefully
|
|
return;
|
|
}
|
|
|
|
try (Connection c = db.getDataSource().getConnection();
|
|
PreparedStatement ps = c.prepareStatement("SELECT name, level FROM players WHERE uuid = ?")) {
|
|
ps.setString(1, uuid.toString());
|
|
try (ResultSet rs = ps.executeQuery()) {
|
|
if (rs.next()) {
|
|
return new PlayerRecord(rs.getString("name"), rs.getInt("level"));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Never hold a connection across an RPC call, a thread hop, or a wait. Pool exhaustion is the most common way Hikari-backed plugins fall over.
|
|
|
|
## Threading
|
|
|
|
The pool is fully thread-safe. Get a connection from whichever thread needs it. JDBC calls themselves block, so:
|
|
|
|
- Don't run heavy queries on the Hytale world thread - it stops world ticking.
|
|
- Don't run heavy queries on the MessageBus worker pool - it backs up the bus.
|
|
- Use a dedicated executor or `CompletableFuture.supplyAsync` for I/O-heavy work.
|
|
|
|
## Schema management
|
|
|
|
NetworkCore owns the *pool*, not your schema. Each plugin is responsible for creating and migrating its own tables.
|
|
|
|
Conventions:
|
|
- **Table prefix per plugin.** The Staff plugin prefixes everything with `staff_`. Pick something unique to your plugin so two plugins don't fight over `players`.
|
|
- **Bootstrap on startup.** In your plugin's `start()`, run a `CREATE TABLE IF NOT EXISTS ...` for every table you need. This makes a fresh install just-work without manual setup.
|
|
- **Migrations as numbered scripts.** When you change a schema, add a `migrations/002_add_email_column.sql` and a tiny runner that tracks applied migrations in a `<plugin>_schema_version` table. Don't reach for Flyway/Liquibase until you've added a third migration - those frameworks earn their weight on real projects, not on the second migration.
|
|
|
|
## Configuration
|
|
|
|
See the [mysql section in 03_Configuration.md](03_Configuration.md#mysql). Default is `enabled: false` so a server that doesn't have MySQL configured doesn't crash on boot. Flip to `true` once you've put credentials in place via `MYSQL_PASSWORD` env var.
|
|
|
|
## What you see in /networkcore status
|
|
|
|
When MySQL is healthy:
|
|
```
|
|
Database: ok (active=2 idle=8 total=10)
|
|
```
|
|
|
|
When disabled:
|
|
```
|
|
Database: disabled
|
|
```
|
|
|
|
When configured but failing:
|
|
```
|
|
Database: unhealthy
|
|
```
|
|
|
|
If you ever see "unhealthy", check the boot log for the connection error and verify host/port/credentials.
|
|
|
|
## What is *not* in the box
|
|
|
|
- **No ORM.** Plain `DataSource` only. Pick your own.
|
|
- **No migration framework.** Bootstrap with `CREATE TABLE IF NOT EXISTS` per plugin.
|
|
- **No automatic encryption at rest.** That's MySQL's job, not the plugin's. Use MySQL Enterprise's TDE or AWS RDS encryption.
|
|
- **No read replicas.** Single pool to a single MySQL endpoint. Read replicas are an MySQL Router or ProxySQL concern, not a Hytale plugin concern.
|
|
|
|
## Sizing the pool
|
|
|
|
`max_pool_size = 10` is fine for a single server with a handful of plugins. Rules of thumb:
|
|
|
|
- Each plugin doing heavy queries needs maybe 2-4 connections worth of headroom.
|
|
- The pool is per Hytale-server-instance, not network-wide. If you have 5 servers each with pool=10, MySQL needs to handle 50 concurrent connections.
|
|
- Make sure MySQL's `max_connections` is comfortably above the *sum* of all pools that point at it.
|
|
|
|
Don't crank `max_pool_size` to "fix" pool exhaustion. Pool exhaustion is almost always a leaked connection or a query that's holding the connection too long. Find the leak, then re-evaluate sizing.
|