> For the complete documentation index, see [llms.txt](https://axiomemu.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://axiomemu.gitbook.io/docs/dataserver/dataserver.md).

# Overview

DataServer is the only component that talks to SQL Server. Every GameServer routes persistence (character load/save, inventory, warehouse, guild, chaos box, rankings, cash shop) through it. DataServer also owns the Write-Ahead Log that records critical save operations before the SQL transaction runs so interrupted saves can be detected and replayed on the next startup.

## Role

* Loads characters and inventories on login, saves them on logout and periodic autosave
* Handles warehouse, chaos box, personal shop, and cash shop persistence
* Stores guild, alliance, Castle Siege, and Gens state
* Writes a Write-Ahead Log entry before character, warehouse, and chaos box saves; entries are replayed on startup when the database's `LastSaveId` shows the save was not committed
* Rejects GameServer connections whose IP is not whitelisted
* Filters character names against a bad-word list (guild names only get the baseline space-and-quote check, not the list)

## File Location

```
DataServer/
├── DataServer.exe
├── DataServer.ini
├── AllowableIpList.txt
├── BadSyntax.txt
├── WAL/                        (created on first run)
│   └── WAL_YYYYMMDD.bin
└── METRICS/                    (created on first run)
    └── DSMetrics_YYYYMMDD.csv
```

All configuration lives in the DataServer root. There is no `Data/` subfolder.

## Configuration Files

| File                                                         | Purpose                                                                        |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| [`DataServer.ini`](/docs/dataserver/dataserver-ini.md)       | Database, port, logging, license                                               |
| [`AllowableIpList.txt`](/docs/dataserver/allowableiplist.md) | GameServer IP whitelist                                                        |
| [`BadSyntax.txt`](/docs/dataserver/badsyntax.md)             | Forbidden substrings in character names (creation, availability check, rename) |

## Startup Order

{% stepper %}
{% step %}

#### License activation

Reads `LicenseKey` from `DataServer.ini` and validates it with the license service. Failure aborts startup before any network or database work.
{% endstep %}

{% step %}

#### Primary database connection

Connects using `DataServerODBC`, `DataServerUSER`, `DataServerPASS`. Required; failure logs `Could not connect to database` and prevents the TCP port from opening.
{% endstep %}

{% step %}

#### Connection pool

Opens 4 additional SQL connections for parallel query processing. All pool connections use the same credentials as the primary. Any pool connection failure logs `Could not connect pool connection N` and aborts the open; DataServer never half-opens the port.
{% endstep %}

{% step %}

#### TCP listen

Opens `DataServerPort` for GameServer connections. If the listen call fails, every SQL connection is closed and the server idles with no port open.
{% endstep %}

{% step %}

#### Whitelist and filter load

Reads `AllowableIpList.txt` then `BadSyntax.txt` from the DataServer root. The whitelist is consulted inside the accept-condition callback, so the check runs for every incoming connection without any allocated socket state.
{% endstep %}

{% step %}

#### Guild manager and timers

Starts the 1-second timer (status bar, metrics display, license heartbeat) and initializes the in-memory guild manager.
{% endstep %}

{% step %}

#### WAL init and replay

Creates the `WAL` folder if missing, opens today's `WAL_YYYYMMDD.bin`, and scans it for entries with status `pending`. For each pending entry, DataServer queries the target row's `LastSaveId` in SQL; if the DB column is still behind the WAL entry id, the save is replayed and marked complete. Already-applied entries are skipped. Incomplete saves are counted in a red log line.
{% endstep %}

{% step %}

#### Metrics

Creates the `METRICS` folder if missing, opens today's `DSMetrics_YYYYMMDD.csv`, and starts a background thread at `THREAD_PRIORITY_BELOW_NORMAL` that writes a row every 10 seconds.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
If primary or pool connection fails, DataServer does not open its TCP port. GameServers will queue save requests and eventually disconnect players. Fix the database before restarting.
{% endhint %}

## Dependencies

* **SQL Server** with the `MuOnline` database restored from the shipped backup
* **ODBC DSN** (name configured in `DataServerODBC`) registered on the DataServer host
* **GameServer** IPs listed in `AllowableIpList.txt`
* Writable working directory for `WAL/` and `METRICS/` subfolders

## Write-Ahead Log

Before executing a character, warehouse, or chaos box save, DataServer appends a WAL entry with status `pending` to `WAL/WAL_YYYYMMDD.bin` and flushes it to disk. After the SQL transaction commits, the entry is updated in place to status `complete`. A crash between the two writes leaves the entry pending.

On startup, DataServer walks the WAL file and for each pending entry queries the target row's `LastSaveId` column in SQL:

* If `LastSaveId` is greater than or equal to the WAL entry id, the save already committed and the entry is skipped
* Otherwise the save is replayed against the database, `LastSaveId` is written with the WAL entry id, and the entry is marked complete

Details:

* One WAL file per calendar day; the file rolls automatically when the day of month changes
* Each entry is a fixed-size header followed by the raw save payload; do not edit by hand
* Entry types covered: character save, warehouse save, chaos box save. Inventory-only saves are not written separately because they are part of the character save
* The log is compacted in place after many completes, keeping only still-pending entries; a `.bak` copy of the pre-compact file is left next to it
* WAL files accumulate across days; archive or delete old ones as disk allows
* A non-zero pending count on startup is a signal to investigate the previous shutdown

## Metrics

Every 10 seconds the metrics thread appends a row to `METRICS/DSMetrics_YYYYMMDD.csv` covering query count and timings, queue adds, overflows, queue peak depth, save counts, and derived per-second rates. The CSV header is written automatically when a new daily file is created. The DataServer window title and status bar show the live queries-per-second, queue peak, and overflow count.

Use metrics to verify the pool is keeping up. A sustained queue peak near the limit or any overflows mean either SQL Server is too slow or the pool size is undersized for the player load.

## Common Issues

* **"Could not connect to database"** - verify the ODBC DSN name, credentials, and that SQL Server is reachable
* **"Could not connect pool connection N"** - the DSN works for 1 connection but rejects more; check SQL Server `max connections` and the login's allowed sessions
* **"\[WAL] Found N incomplete saves from previous session"** - last shutdown was not clean. Review the log for the affected characters; the data on disk is whatever was committed before the crash
* **GameServer connects then drops** - its IP is not in `AllowableIpList.txt`
* **Character name creation fails for valid-looking names** - a `BadSyntax.txt` entry matches as a substring
