Database administration: MariaDB, MySQL and PostgreSQL in production
Exit

Lesson 1 of 12 · Install and configure

MariaDB and MySQL in production: installation, files and my.cnf

55 min Debian 13 Versions MariaDB 10.11, MariaDB 11.4, MariaDB 11.8, MySQL 8.4

Objective

Install a MariaDB or MySQL server the way it is done in production: the right version, the right repository, settings in your own files, a system prepared for a database, and the certainty of knowing which value the server really kept.

Key points

  • A production database is a life cycle: you choose an LTS version (long-term support) and follow its minor bug-fix releases.
  • The files to know: the datadir (/var/lib/mysql), the socket (/run/mysqld/mysqld.sock), the error log, the my.cnf files.
  • my.cnf is read in a precise order, with inclusions; for a repeated option, the last value wins.
  • You never modify the package's files: you add your own file (90-production.cnf) and check the effective value.
  • The server is also tuned around the database: systemd limits, swap, a dedicated volume mounted with noatime.

1. The job: what the DBA adds to "apt install"

The Debian, Red Hat and Ubuntu courses showed you how to install MariaDB, create a database and an account. Here, you take the place of the person who is accountable for the database: it must last for years, absorb the load of the MasterLinux shop on the evening of the sales, be backed up, replicated, monitored and upgraded without loss.

Questions to ask yourself before installing:

Question Why
Which version, supported until when? MariaDB publishes LTS versions (10.11, 11.4, 11.8…) maintained for about 5 years; MySQL has its 8.4 LTS and short-lived "Innovation" releases.
Distribution package or official repository? Debian 13 ships MariaDB 11.8; the MariaDB or MySQL repository gives a precise version and faster updates. You choose one source and stick to it.
Storage engine? InnoDB for everything (transactions, crash recovery). MyISAM has no place in production.
Which machine? RAM (the InnoDB cache), SSD/NVMe disks, a dedicated volume for the data.

2. Install and locate the files

apt install mariadb-server mariadb-backup        # Debian / Ubuntu
dnf install mariadb-server mariadb-backup        # Rocky / Alma (MySQL: dnf install mysql-server)

systemctl enable --now mariadb
mariadb -e "SELECT VERSION(), @@datadir, @@socket, @@log_error;"
Item Debian / Ubuntu Rocky / Alma
Main file /etc/mysql/my.cnf → mariadb.cnf /etc/my.cnf
Included directories /etc/mysql/conf.d/, /etc/mysql/mariadb.conf.d/ /etc/my.cnf.d/
Data (datadir) /var/lib/mysql /var/lib/mysql
Error log systemd journal (journalctl -u mariadb) or /var/log/mysql/error.log /var/log/mariadb/mariadb.log
Service name mariadb (alias mysql) mariadb or mysqld

The commands have been renamed: mariadb (client), mariadb-dump, mariadb-admin, mariadb-backup, mariadb-binlog. The old mysql* names still exist as links on many systems, but they are gradually disappearing.

3. How my.cnf is read

On Debian, /etc/mysql/mariadb.cnf looks like this:

[client-server]
socket = /run/mysqld/mysqld.sock

!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mariadb.conf.d/

Three rules to remember:

  1. Sections (groups) say who reads the option: [mysqld], [server], [mariadb], [mariadb-11.8] (this version only) for the server; [client], [mysql], [mariadb-dump] for the tools.
  2. !includedir reads the files of the directory in alphabetical order, and only those ending in .cnf.
  3. For a repeated option, the last one read wins. A forgotten 99-vieux.cnf beats your 90-production.cnf.

To know what the server will read, without restarting it:

my_print_defaults --mysqld          # all the server options, in reading order
mariadbd --print-defaults           # same thing, as seen by the server binary
mariadb -e "SELECT @@innodb_buffer_pool_size/1024/1024/1024 AS go, @@max_connections;"   # CURRENT value

In the lab, the command cnf-effectif <option> gives the final value the server would have at startup (the equivalent of SELECT @@option).

Dynamic and static variables

Some settings can be changed on the fly:

SET GLOBAL max_connections = 300;            -- immediate, but lost at restart
SET GLOBAL innodb_buffer_pool_size = 10*1024*1024*1024;   -- online resizing (InnoDB)

Others require a restart (innodb_log_file_size on older versions, bind_address…). Operations rule: every change made on the fly is also written to the file, otherwise it disappears at the next restart. MySQL 8 offers SET PERSIST, which writes to mysqld-auto.cnf in the datadir; it is handy, but the whole team must know about this file, otherwise nobody understands where a value comes from.

4. The settings that matter for a dedicated server

# /etc/mysql/mariadb.conf.d/90-production.cnf — ml-db-01, 16 GB of RAM, dedicated
[mysqld]
bind_address                   = 10.10.20.11   # the private network address, never 0.0.0.0 without a firewall
skip_name_resolve              = ON            # no reverse DNS on each connection (accounts by IP)
max_connections                = 300

innodb_buffer_pool_size        = 10G           # 60 to 70% of the RAM of a DEDICATED server
innodb_log_file_size           = 2G            # redo log: large enough to smooth out writes
innodb_flush_log_at_trx_commit = 1             # 1 = no committed transaction lost in a crash
innodb_file_per_table          = ON            # one file per table (default)
  • Buffer pool: the cache of InnoDB pages. Too small, everything goes to disk; too large, the server swaps or is killed by the OOM killer. Leave RAM for the system, for the connections (each one uses buffers) and for the file cache.
  • innodb_flush_log_at_trx_commit: 1 is the only setting that guarantees durability. 2 or 0 are faster but can lose the last second of transactions in a power cut: keep them for a database you can rebuild.
  • Character set: utf8mb4 everywhere (MySQL's old utf8 only stores 3 bytes and breaks emojis and some characters).

5. Prepare the system

# /etc/systemd/system/mariadb.service.d/limites.conf
[Service]
LimitNOFILE=65535        # a table = open files; the default 1,024 is not enough
TimeoutStopSec=900       # leave time to flush a large buffer pool at shutdown
systemctl daemon-reload && systemctl restart mariadb
cat /proc/$(pidof mariadbd)/limits | grep 'open files'

If systemd kills the server after 90 seconds during shutdown, the database restarts in crash recovery: longer, and worrying on a maintenance evening.

echo 'vm.swappiness = 1' > /etc/sysctl.d/60-mariadb.conf && sysctl --system   # swap as a last resort

The datadir on a dedicated volume (LVM): the database does not fill /, you can grow the volume without touching the system, and you can take a snapshot. noatime avoids a write on every file read.

/dev/vg_bdd/lv_mysql  /var/lib/mysql  xfs  defaults,noatime  0 2

By version

Item MariaDB 10.11 MariaDB 11.4 / 11.8 MySQL 8.4 LTS
Server binary name mariadbd (mysqld as a link) mariadbd mysqld
Default utf8mb4 collation utf8mb4_general_ci uca1400_ai_ci (Debian 13) utf8mb4_0900_ai_ci
Changing innodb_log_file_size on the fly since 10.11 on the fly innodb_redo_log_capacity on the fly
Persistent settings on the fly no (write the file) no SET PERSIST (mysqld-auto.cnf)
TLS for connections to be configured enabled by default since 11.4 enabled by default (generated certificates)

Common pitfalls

  • Modifying 50-server.cnf: it is replaced, or triggers a question, at every package update. Your 90-… file remains yours.
  • An old file that wins: a 99-…cnf or a zz-…cnf from a former contractor cancels your settings. Always check the effective value.
  • Renaming to .bak the wrong way round: fichier.cnf.bak is no longer read (good), but fichier.bak.cnf still is.
  • Buffer pool at 90% of the RAM on a server that also runs PHP or Java: the kernel kills mariadbd in the middle of the day.

Real case

One Friday evening, the MasterLinux shop becomes very slow. Yet the server has 16 GB of RAM, and the team "set the buffer pool to 10 GB" in 90-production.cnf six months earlier. SELECT @@innodb_buffer_pool_size answers… 256 MB. A 99-optimisation-2023.cnf file, copied from an old server, was read last. The whole dataset was going through the disk. The fix took two minutes; the lesson remains: check the effective value after every change.

Lab

On a Debian 13 machine that mimics the MariaDB 11.8 file tree, you will:

  1. write your production settings file;
  2. find out why the server would not keep them, and fix it without deleting anything;
  3. add the systemd override;
  4. tune the swap;
  5. declare the dedicated data volume in /etc/fstab.

The commands my_print_defaults --mysqld and cnf-effectif <option> show you what the server would read.

Quiz

  1. In a directory included by !includedir, in which order are the files read, and which ones?
  2. Two files define max_connections: which one wins?
  3. Why innodb_flush_log_at_trx_commit = 1 in production?
  4. What share of the RAM should you give the buffer pool on a dedicated server, and why not more?
  5. What happens if systemd kills MariaDB during a shutdown that takes too long?
Answers
  1. In alphabetical order, and only the files ending in .cnf.
  2. The one read last.
  3. It is the only setting that writes the redo log to disk at each commit: no committed transaction is lost in a crash.
  4. About 60 to 70%: you must leave memory for the system, the connections and the file cache, otherwise swap or the OOM killer.
  5. The server restarts in crash recovery, which is longer and riskier; hence a longer TimeoutStopSec.

Practical exercises

To do on Debian 13

Do this lab on your own machine

The terminal is yours: a Linux VM (VirtualBox, VMware, Hyper-V, WSL…) or Docker/Podman. The ml tool prepares the exercise and checks your work; the lesson is validated here automatically.

Create a free account to get your personal command and have your labs checked automatically.

Create my account

Tasks

  1. The server ml-db-01 (16 GB of RAM, dedicated to MariaDB) must receive your settings in a file of your own: create /etc/mysql/mariadb.conf.d/90-production.cnf with a [mysqld] section containing innodb_buffer_pool_size = 10G, innodb_log_file_size = 2G, innodb_flush_log_at_trx_commit = 1, max_connections = 300, skip_name_resolve = ON and bind_address = 10.10.20.11.
    Show a hintprintf '%s\n' '[mysqld]' 'innodb_buffer_pool_size = 10G' … > /etc/mysql/mariadb.conf.d/90-production.cnf
    Show the solution
    printf '%s\n' '[mysqld]' 'innodb_buffer_pool_size = 10G' 'innodb_log_file_size = 2G' 'innodb_flush_log_at_trx_commit = 1' 'max_connections = 300' 'skip_name_resolve = ON' 'bind_address = 10.10.20.11' > /etc/mysql/mariadb.conf.d/90-production.cnf

    Check : f=/etc/mysql/mariadb.conf.d/90-production.cnf; grep -qx '\[mysqld\]' $f && grep -Eq '^innodb[_-]buffer[_-]pool[_-]size *= *10G *$' $f && grep -Eq '^innodb[_-]log[_-]file[_-]size *= *2G *$' $f && grep -Eq '^innodb[_-]flush[_-]log[_-]at[_-]trx[_-]commit *= *1 *$' $f && grep -Eq '^max[_-]connections *= *300 *$' $f && grep -Eq '^skip[_-]name[_-]resolve *(= *(ON|1))? *$' $f && grep -Eq '^bind[_-]address *= *10\.10\.20\.11 *$' $f

  2. Check with cnf-effectif innodb_buffer_pool_size (or my_print_defaults --mysqld): your values are not the ones the server would keep. Find the file that overrides them and neutralize it WITHOUT deleting it (rename it so it is no longer read). The effective buffer pool must be 10G and max_connections 300.
    Show a hintIn an !includedir, files are read in alphabetical order and only those ending in .cnf count: 99-… comes after 90-…
    Show the solution
    mv /etc/mysql/mariadb.conf.d/99-optimisation-2023.cnf /etc/mysql/mariadb.conf.d/99-optimisation-2023.cnf.desactive

    Check : test "$(cnf-effectif innodb_buffer_pool_size)" = 10G && test "$(cnf-effectif max_connections)" = 300 && ls /etc/mysql/mariadb.conf.d/ | grep '^99-optimisation-2023' | grep -vq '\.cnf$'

  3. The service must be able to open many files and have time to flush a large buffer pool at shutdown: create the systemd override /etc/systemd/system/mariadb.service.d/limites.conf with, in [Service], LimitNOFILE=65535 and TimeoutStopSec=900.
    Show a hintmkdir -p /etc/systemd/system/mariadb.service.d then printf '%s\n' '[Service]' 'LimitNOFILE=65535' 'TimeoutStopSec=900' > …/limites.conf (on a real server: systemctl daemon-reload)
    Show the solution
    mkdir -p /etc/systemd/system/mariadb.service.d && printf '%s\n' '[Service]' 'LimitNOFILE=65535' 'TimeoutStopSec=900' > /etc/systemd/system/mariadb.service.d/limites.conf

    Check : f=/etc/systemd/system/mariadb.service.d/limites.conf; grep -qx '\[Service\]' $f && grep -qx 'LimitNOFILE=65535' $f && grep -qx 'TimeoutStopSec=900' $f

  4. The kernel must send the server's memory to swap only as a last resort: write /etc/sysctl.d/60-mariadb.conf with vm.swappiness = 1.
    Show a hintecho 'vm.swappiness = 1' > /etc/sysctl.d/60-mariadb.conf (then sysctl --system on a real server)
    Show the solution
    echo 'vm.swappiness = 1' > /etc/sysctl.d/60-mariadb.conf

    Check : grep -Eq '^vm\.swappiness *= *1 *$' /etc/sysctl.d/60-mariadb.conf

  5. The data must live on its own volume: add to /etc/fstab (without touching the other lines) the mount of the volume /dev/vg_bdd/lv_mysql on /var/lib/mysql, as xfs, with the noatime option, fsck check 0 2.
    Show a hintecho '/dev/vg_bdd/lv_mysql /var/lib/mysql xfs defaults,noatime 0 2' >> /etc/fstab (two chevrons: append)
    Show the solution
    echo '/dev/vg_bdd/lv_mysql /var/lib/mysql xfs defaults,noatime 0 2' >> /etc/fstab

    Check : awk '$1=="/dev/vg_bdd/lv_mysql" && $2=="/var/lib/mysql" && $3=="xfs" && $4 ~ /noatime/ && $6=="2" {ok=1} END {exit !ok}' /etc/fstab && grep -q '/boot' /etc/fstab && grep -q 'lv_swap' /etc/fstab