MIRAJv1.0
EN

11. Server Administration

This chapter is intended for administrators who operate miraj-server, the MIRAJ network server. It covers startup and shutdown, configuration (command line and XML file), the layout of the data folder, durability, operational security, monitoring, backup, and performance tuning.

11.1 Starting and stopping the server#

11.1.1 Basic command#

miraj-server.exe --root D:\donnees --lang fr --log

--root designates the server's data folder (one folder per database, plus a miraj subfolder for accounts and internal logs). If it is omitted, the server uses a data folder relative to the current directory.

For troubleshooting and account maintenance (see 11.5.3), the server has a dedicated mode:

miraj-server.exe --root D:\donnees --reset-accounts [--key-dir <dossier>]

11.1.2 Stopping the server#

miraj-server has no clean shutdown sequence: whether the stop comes from a Ctrl-C, from the process being closed, or from a machine power loss, no final checkpoint is written before exit. Every stop must therefore be treated, from the administrator's point of view, as an abrupt stop: it is the write-ahead log (WAL) and its replay at restart that guarantee data integrity (see 11.4), not a careful shutdown phase of the server. This differs from embedded use of MIRAJ (miraj.dll library or native Rust API): there, closing the instance (Miraj::drop) writes everything before returning control. Nothing in the current server code introduces any special handling on a termination signal: check again in a later version whether this point changes.

11.1.3 Command-line options#

All options are optional; the default values are those applied when neither the miraj_config.xml file nor the command line changes them (see 11.2 for the order of precedence).

OptionDefault valueDescription
--root <dossier>dataServer data folder.
--config <fichier><root>\miraj_config.xmlExplicit path of the XML configuration file (see 11.2).
--port <port>7007TCP listening port.
--bind <adresse>127.0.0.1Listening address. A warning is issued if a non-local address is chosen (see 11.5.4).
--lang <code>enLanguage of session error messages: en, fr, zh, hi, es, ar, pt, ru, de, ja.
--logdisabledMaintains <root>\miraj\server.log (connections, SQL errors, internal failures; see 11.6).
--result-buffer <Mo>64Memory kept per connection for a client that is slow to read its result.
--write-timeout <secondes>60Maximum wait for a client that no longer reads its result before disconnection.
--connect-timeout <secondes>10Maximum wait for a client's handshake.
--idle-timeout <secondes>28800 (8 h)Maximum wait for a command from an already connected client.
--key-dir <dossier>profile of the account that launches the serverFolder of the account vault key, outside the data folder (see 11.5.1).
--lock-wait-timeout <secondes>50Maximum wait for an explicit table lock (LOCK TABLES) before error 1205.
--deferred-update ON|OFFONDeferred update: an eligible UPDATE of a row identified by its key does not take it and is re-evaluated at COMMIT, so that two transactions that modify the same row both commit (see 9.2). Initial value of the session variable deferred_update; OFF: the UPDATE takes its row immediately.
--concurrency mvocc|pessimisticmvoccConcurrency model. pessimistic restores the former row-lock model; temporary option, for the time needed to compare the two models.
--lob-threshold <octets>8192Size from which a BLOB value leaves the table and moves to the .bmrj store.
--lob-cache <Mo>128Size of the read cache for offloaded BLOBs.
--event-scheduler ON|OFF|DISABLEDOFFState of the event scheduler at startup; DISABLED forbids SET GLOBAL event_scheduler = ON afterwards.
--parallel-threads <n>one thread per coreThreads that all of the server's queries may occupy in total (Standard edition; no effect in Express edition, with a warning).
--save-policy relaxed|statement|periodicrelaxedDurability policy (see 11.4.1).
--save-interval <millisecondes>5000Interval of the periodic background flush (relaxed and periodic policies).
--slow-query-logdisabledEnables the slow query log.
--slow-query-log-file <fichier><root>\miraj\slow.logSlow query log file; specifying it also enables the log.
--long-query-time <secondes>10Duration beyond which a statement is considered a slow query.
--max-statement-time <secondes>0 (no limit)Duration beyond which a statement is aborted (initial value of the session variable max_statement_time).
--secure-file-priv <dossier>empty (LOAD_FILE disabled)Only folder readable by LOAD_FILE, for accounts with the FILE privilege. Must neither contain nor be located inside the data folder or the keys folder.
--backup-dir <dossier>empty (BACKUP / RESTORE refused)Only folder where BACKUP DATABASE writes and from which RESTORE DATABASE reads; created if it does not exist. Must neither contain nor be located inside the data folder or the keys folder (see 11.7 and chapter 18).
--tls-cert <fichier.pem> / --tls-key <fichier.pem>absentTLS certificate and private key offered during the client handshake.
--require-tlsdisabledRefuses clients that do not connect over TLS (error 3159).
--cluster-config <cluster.toml>cluster.toml next to the executableCluster edition: node configuration in a multi-node replication (see 11.1.4).
--reset-accounts—Recreates the account vault with only the local root (see 11.5.3). Can be used with --root and --key-dir.
--help / -h / /?—Displays usage and exits.

Note on terminology: command-line option names use hyphens (--long-query-time), while XML keys and session variables use underscores (long_query_time); this chapter follows this convention throughout.

11.1.4 Replication (Cluster edition)#

The Cluster edition adds multi-node replication: a primary node writes, secondary nodes receive its log continuously and replay it read-only. Clients connect to a secondary on its usual client port (7007 by default); any write attempt there fails with error

  1. Nodes communicate with each other on a separate port, over mutual TLS (certificate signed by the cluster authority). Configuration is done through --cluster-config:
[node]
id = "n1"                    # stable node identifier
listen = "0.0.0.0:7107"      # inter-node port (not the clients' port)
advertise = "10.0.0.1:7107"  # address advertised to other nodes

[cluster]
name = "gestium-prod"
seeds = ["10.0.0.1:7107", "10.0.0.2:7107"]
tls_cert = "node.pem"        # paths relative to the file
tls_key = "node-key.pem"
tls_ca = "cluster-ca.pem"
ack = "written"              # or "durable": the secondary flushes its
                              # log to disk before acknowledging
journal_retention_mb = 1024  # beyond this, a secondary that was absent is
                              # re-seeded upon its return

At first startup, all nodes are secondaries. The primary role is designated once with:

SET GLOBAL cluster_role = 'primary';

the other nodes then join the primary (copy of the databases, then log stream). Promoting a secondary uses the same command; a former primary restarted after a failover is set aside (@@cluster_role = 'FENCED', read-only) until it is made to rejoin the new primary:

SET GLOBAL cluster_role = 'secondary';

its unreplicated writes are then set aside in <root>\miraj\quarantaine. Replication monitoring:

SHOW CLUSTER STATUS;
SELECT * FROM information_schema.MIRAJ_NODES;
SELECT * FROM information_schema.MIRAJ_REPLICATION;
SELECT @@read_only, @@cluster_role, @@cluster_epoch, @@cluster_primary;

11.2 The miraj_config.xml configuration file#

MIRAJ accepts configuration through an XML file, modeled on a classic .cnf/.ini file. Three levels combine, in this order of increasing priority:

  1. internal default value;
  2. value from the miraj_config.xml file;
  3. command-line argument (highest priority).

The file read is <root>\miraj_config.xml by default, or the path given by --config. A variable that is absent or commented out keeps its default value. Excerpt:

<?xml version="1.0" encoding="UTF-8"?>
<miraj>
    <!-- Language of session error messages (en, fr) -->
    <!-- <language>en</language> -->
    <!-- Save policy (relaxed, statement, periodic) -->
    <!-- <save_policy>relaxed</save_policy> -->
    <!-- Server listening address -->
    <!-- <bind>127.0.0.1</bind> -->
    <!-- Server TCP listening port -->
    <!-- <port>7007</port> -->
    <!-- Maintain the <folder>/miraj/server.log file -->
    <!-- <log>false</log> -->
</miraj>

To apply a value, uncomment the line and change the value; the rest of the file (the other commented-out variables) does not need to be modified.

A second file, miraj_default.xml, is regenerated at every startup of the server: it lists all recognized variables with their current default value. It is a reference file, never read back by the server — do not write your own settings in it, they would be erased at the next startup. Use it to retrieve, version after version, the complete list of available keys and their default value.

The keys of the XML file cover the same settings as the command-line options: language, save_policy, save_interval, checkpoint_interval, lock_wait_timeout, deferred_update, concurrency, key_dir, lob_threshold, lob_cache, event_scheduler, parallel_threads, slow_query_log, slow_query_log_file, long_query_time, max_statement_time, secure_file_priv, bind, port, log, result_buffer, write_timeout, connect_timeout, idle_timeout, tls_cert, tls_key, require_tls.

checkpoint_interval (60000 ms, i.e. 60 s, by default) has no equivalent on the command line: it is the interval between two checkpoints (see 11.4.2), adjustable only through this file.

Recommended usage: the XML file is suited to a permanent setting, specific to an instance (one data folder), that you do not want to repeat at each launch in a script or a Windows service; the command line remains useful for a one-off setting (diagnostics, test script) that must take precedence over the file.

11.3 Layout of the data folder#

<dossier racine>/
  miraj_config.xml       configuration (if present)
  miraj_default.xml      reference of default values, regenerated at startup
  <base>/
    <table>.mrj          one table = one file (MIRA v2 format)
    <table>.bmrj         store of the table's long BLOBs (created on first deposit)
    journal.mrl          write-ahead log (WAL) of the database
  miraj/
    accounts.mra         encrypted account vault
    server.log           server log, if --log is active
    slow.log             slow query log, if enabled
    quarantaine/          unreplicated writes of a FENCED node (Cluster edition)

Practical points for administration:

  • Each database is a subfolder of the root folder; each table is a .mrj file in that subfolder. Creating or dropping a database amounts to creating or deleting that subfolder (done by the server itself through CREATE DATABASE / DROP DATABASE, never by hand while the server is running).
  • A BLOB that exceeds --lob-threshold (8192 bytes by default) is deposited in the .bmrj file of its table rather than in the .mrj; this file only appears after the first deposit of a BLOB of that size. Both files must be copied together: a .mrj without its associated .bmrj loses its offloaded BLOB values. After lowering --lob-threshold, values already stored that reach the new threshold move to the .bmrj when the database is opened (the table is rewritten at the next checkpoint). On the primary of a cluster, this move goes through the log, within a cap of 256 MiB per table (see 12.7): beyond that, the values remain in the table, readable, and a AVERTISSEMENT line is written to the server log.
  • journal.mrl is the database's write-ahead log: it makes it possible to replay the writes made after each table's last checkpoint in the event of an abrupt stop (see 11.4). Never delete or modify it by hand.
  • The account vault miraj/accounts.mra is specific to the data folder; its decryption key is stored elsewhere (see 11.5.1). Copying a data folder without its key makes the vault unreadable at restart.
  • Backup: with the server stopped (see 11.1.2 and 11.7), a plain copy of the root folder (databases, miraj/, configuration files) is enough to obtain a consistent image of it, provided that you also copy the account vault key if it is needed for the restoration (see 11.5.1 and 11.7).

11.4 Durability and crash recovery#

11.4.1 Save policies (save_policy)#

MIRAJ writes each modification statement to the log (WAL) of the database concerned before considering the statement complete; how this log reaches the disk depends on the chosen policy:

PolicyBehaviorMaximum loss tolerated on an abrupt process stopMaximum loss tolerated on a machine power loss
relaxed (default)The log is written to its file before the statement returns, without waiting for the disk; a background thread flushes it to disk every --save-interval ms (5000 by default).None (the file has already received the write from the operating system).The writes of the last --save-interval interval.
statementLike relaxed, additionally waiting for the flush to disk at the end of each write statement.None.None.
periodicNothing is written before the background thread runs (every --save-interval ms).The writes of the last --save-interval ms.The writes of the last --save-interval ms.

These three policies can also be chosen per session, without changing the server setting:

SET SESSION save_policy = 'statement';

(a fourth value, manual, exists internally but is refused as a server policy: it would never write anything unless the session orders it, which makes no sense as a default policy).

How to choose:

  • statement maximizes safety (no possible loss, including during a power outage) at the cost of a per-statement write latency tied to the disk — to be chosen for data that no loss, however small, can be allowed to affect.
  • relaxed (the default setting) offers a good compromise: no loss in the event of a stop of the server process alone (the most frequent case in practice), and a loss window bounded by --save-interval only in case of a power loss of the machine itself.
  • periodic further reduces write latency in exchange for an identical loss window in both cases (process or machine) — to be reserved for workloads where write throughput takes precedence over strict durability guarantees.

In all cases, the log itself remains consistent: what may be lost are the very last writes not yet flushed, never the integrity of the file.

11.4.2 Checkpoints#

A checkpoint consolidates the current state of the tables and makes it possible to truncate the write-ahead log accordingly. It is triggered automatically:

  • every checkpoint_interval milliseconds (60,000 ms, i.e. 60 s, by default — adjustable only via miraj_config.xml, see 11.2);
  • when the log exceeds 64 MB;
  • on the clean closing of an embedded instance (Miraj::drop).

11.4.3 Recovery after an abrupt stop#

At startup, the server opens the table files of each database and then replays the log records that follow the LSN (log sequence number) already present in each .mrj file. File writes are atomic (temporary file, flush, then replacement — on Windows through MoveFileExW with MOVEFILE_WRITE_THROUGH), and the BLOB store (.bmrj) is always flushed to disk before the log and the table file that reference it: an abrupt stop can at worst leave orphaned BLOB bytes in the .bmrj, never a reference without content. According to the project's technical file, this mechanism was validated by 120 random abrupt kills of the process during writes, with no inconsistency observed.

As noted in 11.1.2, miraj-server has no clean shutdown: it is precisely this recovery mechanism, and not a shutdown sequence, that guarantees data integrity at each restart.

11.5 Operational security#

11.5.1 Account vault and key management#

Accounts (users, passwords, privileges, roles) are kept in an encrypted vault authenticated with AES-256-GCM, <root>\miraj\accounts.mra — never logged, written atomically, protected against the restoration of an old copy by an increasing generation number.

The 32-byte key that protects this vault is stored outside the data folder:

  • on Windows, it is encrypted with DPAPI for the account that launches the server, in %LOCALAPPDATA%\MIRAJ\keys;
  • on other systems, in a file with 0600 permissions;
  • --key-dir <dossier> (or the XML key key_dir) lets you choose another location, for example to share the key between several services or to store it on a medium separate from the data folder.

Consequence for backup and deployment: copying only the data folder is not enough to be able to reopen the accounts on another machine or under another Windows account; the key (or its --key-dir folder) must also be transferred, protecting it at least as well as an administrator password.

11.5.2 Accounts without a password and network listening#

A local root account, without a password, exists as soon as the data folder is created; it is the only account that manages the other accounts. By default the server listens on the loopback interface (127.0.0.1): this setting implicitly protects an account that still has no password against remote access. Choosing a non-local listening address (--bind) triggers a warning, and the server flags the accounts without a password that would then become reachable remotely. In practice, before opening listening beyond the loopback interface, give root a password:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'un mot de passe robuste';

11.5.3 --reset-accounts: recovery procedure#

An account vault that is altered, deleted, or whose key is missing/unreadable prevents the server from starting. The local recovery procedure is:

miraj-server.exe --root D:\donnees --reset-accounts [--key-dir <dossier>]

This command recreates the vault with the local root as its only account, without a password, then exits (it does not start the network server). It must be run locally, with access to the data folder and, implicitly, access equivalent to that of the key. After a --reset-accounts:

  • all previously defined accounts and privileges are lost (only the local root remains); recreate the application accounts and grant back the necessary privileges;
  • give root a password again without delay before restarting the server with non-local listening (see the ALTER USER command above).

Use this procedure only as a last resort (corrupted vault, lost key, administrator account locked with no other access): it restores nothing, it starts over from a blank account state.

11.5.4 Recommendations for a production deployment#

  • Keep listening on the loopback interface (127.0.0.1, the default value) as long as a firewall or an application tunnel does not protect wider access; choose a non-local address only after having secured all accounts with passwords.
  • Enable TLS (--tls-cert / --tls-key) and consider --require-tls as soon as clients connect across a network that is not physically isolated.
  • Keep the account vault key (--key-dir) separate from the backup medium of the data folder, or clearly document that it must be copied separately during a restoration.
  • Leave secure_file_priv empty (default value, LOAD_FILE disabled) unless explicitly needed; if you enable it, point it to a dedicated folder, distinct from the data folder and the keys folder.
  • Systematically start with --log (see 11.6) to keep a trace of SQL errors and internal incidents.
  • Keep in mind the absence of a clean shutdown (11.1.2): plan the save policy (11.4.1) according to the data loss you can tolerate in the event of a power loss, rather than counting on a careful shutdown of the service.

11.6 Monitoring#

11.6.1 SHOW STATUS and SHOW VARIABLES#

SHOW VARIABLES;              -- current session/server settings
SHOW VARIABLES LIKE 'save_%';
SHOW STATUS;                 -- activity counters
SHOW STATUS LIKE 'Miraj_%';

SHOW VARIABLES returns the same values as SELECT @@name for each variable; this is what common client tools (graphical consoles, connectors) read at connection time.

SHOW STATUS exposes, as of now, in particular:

VariableMeaning
Miraj_parallel_queriesNumber of queries that used parallel execution (Standard edition).
Miraj_parallel_queries_serializedQueries that fell back to serial execution for lack of available threads.
Miraj_parallel_workersThreads in the configured parallelism budget.
Miraj_parallel_workers_busyThreads currently busy.
Slow_queriesNumber of queries recorded in the slow query log.
Max_statement_time_exceededNumber of statements aborted for exceeding max_statement_time.

This list will grow over the course of versions; consult SHOW STATUS without a filter for the complete list of the running instance.

11.6.2 information_schema#

information_schema (read-only) provides a structured view useful for administration, in particular: SCHEMATA, TABLES, COLUMNS, STATISTICS, VIEWS, TABLE_CONSTRAINTS, KEY_COLUMN_USAGE, REFERENTIAL_CONSTRAINTS, as well as the privilege tables USER_PRIVILEGES, SCHEMA_PRIVILEGES, TABLE_PRIVILEGES, APPLICABLE_ROLES, ENABLED_ROLES. An account sees, in SHOW DATABASES, SHOW TABLES and information_schema, only what it has the privilege to access.

In the Cluster edition, information_schema.MIRAJ_NODES and information_schema.MIRAJ_REPLICATION complement SHOW CLUSTER STATUS for replication monitoring (see 11.1.4).

Tables specific to MIRAJ for concurrency: MIRAJ_CONCURRENCY (purge watermark, oldest transaction), MIRAJ_VERSIONS (pending versions, per table) and MIRAJ_DEFERRED_UPDATE (deferred update, one row of counters accumulated since startup):

SELECT INTENTS_RECORDED, ROWS_APPLIED, COMMIT_FAILURES, FAILED_READ_VALIDATION,
       NORMAL_KEY, NORMAL_TRIGGER
FROM information_schema.MIRAJ_DEFERRED_UPDATE;

INTENTS_RECORDED counts deferred UPDATEs, ROWS_APPLIED the rows written at COMMIT, IMAGES_MATERIALIZED the deferred UPDATEs caught up by an ordinary write of the same transaction; COMMIT_FAILURES the COMMITs refused for a transaction that had deferred UPDATEs, broken down by cause (FAILED_WITNESS: row changed so that the WHERE or a column read by an AFTER UPDATE trigger no longer holds; FAILED_ROW_GONE, FAILED_CONCURRENT_WRITER, FAILED_WARNING, FAILED_TABLE_REDEFINED, FAILED_ERROR, FAILED_READ_VALIDATION: the transaction had read a row modified since); ENGINE_RETRIES the autocommit statements replayed by the engine after a conflict; the NORMAL_* columns the UPDATEs of a session set to ON that could not be deferred, by reason (SESSION, FORM, TABLE, TRIGGER, KEY, ROW_COUNT, ROW, CALCULATION). A high FAILED_READ_VALIDATION indicates transactions that read a row before updating it (see 9.2).

11.6.3 Logging#

  • --log (or log in the XML file) maintains <root>\miraj\server.log: this file receives, timestamped, only the SQL errors returned to clients (with the statement at fault), internal failures (captured panics, never passed to the caller) and server operating problems. Successful queries do not appear there; connections and errors are however also displayed on standard output.
  • --slow-query-log (or slow_query_log / slow_query_log_file) records in <root>\miraj\slow.log (or the chosen file) each statement longer than --long-query-time seconds, with the volume sent to the client (Bytes_sent). SET GLOBAL log_output = 'TABLE' switches this log to the miraj.slow_log table; FLUSH SLOW LOGS reopens the file after an external rotation.

11.7 Backup and restore#

The server backs up a database hot, without stopping it. Start it with a backup folder (--backup-dir, see 11.1.3), then:

BACKUP DATABASE gestion TO 'gestion-2026-09-24';
RESTORE DATABASE gestion_copie FROM 'gestion-2026-09-24';

The copy is consistent, with all transactions committed up to one instant and none beyond. Writes are suspended for only a few milliseconds. The miraj-backup tool drives these statements from a scheduled task, and miraj-dump exports a database as a portable SQL script, accounts included. Everything is detailed in chapter 18.

These backups do not contain the account vault. Export the accounts with miraj-dump --users, or cold-copy the miraj\ subfolder and the vault key (see below).

Cold copy of the complete folder, for a change of machine with the accounts, for example:

  1. Stop the server. As noted in 11.1.2, miraj-server offers no clean shutdown sequence: treat every stop as an abrupt stop. It is log recovery (11.4.3) that guarantees a consistent folder at the next startup.
  2. Copy the data folder (--root) in its entirety: databases (.mrj, .bmrj, journal.mrl), miraj\ subfolder (accounts, internal logs) and any XML configuration files.
  3. Copy the account vault key separately if it is needed for the restoration on another machine or under another Windows account (see 11.5.1): it is not in the data folder.
  4. To restore, put this folder (and the key, if needed) back at the expected location, then restart the server normally.

For an embedded database (use through miraj.dll or the Rust API, outside the network server), the normal closing of the instance (Miraj::drop) writes everything before returning control: a cold backup after this closing does not have to worry about an abrupt stop.

11.8 Performance: settings to know about#

This chapter remains administration-oriented; for the technical details of parallel execution and its limits, see roadmap.md and docs/LIMITES.md.

Parallel execution (Standard edition only — the Express edition, compiled without the Cargo feature parallel, runs each query on the single thread of its session) spreads large reads across several threads: filters, projections, aggregation, sorting, DISTINCT, INNER/LEFT joins, and the underlying read of UPDATE/DELETE/INSERT ... SELECT — with, in all cases, the same results and the same order as in serial execution.

Two settings control this parallelism:

  • --parallel-threads <n> (server): sets the total number of threads that all the server's queries may occupy together. By default, one thread per core of the machine. Reduce it if the server shares the machine with other services, or adjust it if measurements show contention between concurrent queries.
  • max_parallel_degree (session variable): bounds the degree of parallelism of a given session, within the limit of the server budget above. Useful for reserving most of the threads for interactive processing while letting a background job (import, bulk recalculation) run at a lower degree, or the reverse.

EXPLAIN displays the degree of parallelism chosen for a given query, which makes it possible to check that a setting has the expected effect before generalizing it.

For BLOB volumes, --lob-threshold and --lob-cache (see 11.1.3 and 11.3) also influence performance: a lower threshold offloads more values out of the table files (useful if rows must remain compact in memory), a larger cache reduces repeated reads of large BLOBs at the cost of the memory it occupies.