Diagnostiquer Dolibarr : optimisation MySQL/MariaDB Plan d’action pour passer à l’échelle

Diagnostiquer Dolibarr : optimisation MySQL/MariaDB – Plan d’action pour passer à l’échelle
Version : 1.0 – Novembre 2025


1. Introduction Dolibarr est un ERP/CRM léger, écrit en PHP, qui utilise MySQL/MariaDB comme moteur de persistance. Dans un contexte de petite structure, les performances sont généralement suffisantes. Au fur et à mesure que le volume de données (clients, factures, stock, utilisateurs) augmente, la charge sur le SGBD devient le principal goulot d’étranglement.

Ce document propose :

  1. Les points de diagnostic (symptômes, métriques à relever).
  2. Les leviers d’optimisation (schéma DB, index, configuration serveur, réglages SQL).
  3. Un plan d’action détaillé, étape par étape, pour passer à l’échelle (sauvegarde, refonte, sharding, caching, réplication).

Objectif : permettre à Dolibarr de supporter plusieurs dizaines de millions d’enregistrements et plusieurs centaines de requêtes simultanées sans perte de réactivité.


2. Diagnostic pré‑optimisation

2.1. Collecte des indicateurs de performance

Outil Ce que l’on observe Commande / configuration
SHOW STATUS LIKE 'Queries'/ Nombre de requêtes traitées, taux de cache hit (Queries vs Com_select). mysql -e "SHOW GLOBAL STATUS LIKE 'Queries'"
*`SELECT COUNT() FROM information_schema.tables WHERE engine=’InnoDB’;`** Nombre total d’enregistrements par table. Permet d’estimer l’ampleur des tables.
*`SELECT FROM information_schema.innodbmetrics WHERE name LIKE ‘trx%’ ORDER BY value DESC LIMIT 10;`** Transactions par seconde, latence des commits.
phpMyAdmin ou Adminer Visualisation de la taille des tables, fragmentation, index manquants.
Profilage PHP (xdebug, Blackfire, Tideways) Temps passé dans chaque fonction Dolibarr (ex. getTotalFacture, getProduitByRef).
Logs Apache/Nginx (access_log, error_log) Temps de réponse HTTP, erreurs MySQL (Got timeout).
*`SELECT FROM mysql.slow_log WHERE start_time > NOW() – INTERVAL 1 DAY;`** (si le slow‑log est actif). Requêtes lentes (> 1 s). Activer le slow‑log : slow_query_log = 1 dans my.cnf.

2.2. Analyse des requêtes fréquentes

  1. Identification des top‑SQL (top 10)

    SELECT LEFT(query, 100) AS snippet, COUNT(*) AS exec_count,
    SUM(time) AS total_time, ROUND(AVG(time),3) AS avg_time
    FROM mysql.slow_log
    GROUP BY snippet ORDER BY total_time DESC
    LIMIT 10;

  2. Typologies de requêtes Dolibarr

    • Lecture : SELECT * FROM llx (listes, lignes de factures).
    • Écriture : UPDATE llx (modification de stocks, établissements).
    • Agrégations : SELECT SUM(qty), product_id FROM llx_stock WHERE ... GROUP BY product_id.

    Ces requêtes sont souvent non indexées sur les colonnes de jointure ou de filtrage (ex. fk_product, ref, date, status).


3. Optimisation du moteur MySQL/MariaDB

3.1. Paramètres de configuration (my.cnf / my.ini)

Paramètre Valeur conseillée (exemple) Pourquoi
innodb_buffer_pool_size 30‑70 % de la RAM disponible (ex. 12 GiB sur serveur 20 GiB) Cache des pages InnoDB ; la plupart des tables Dolibarr sont InnoDB.
innodb_log_file_size 1‑2 GiB (ou innodb_log_files_group=2 et innodb_log_file_size=2G) Réduit le flush à chaque commit.
innodb_flush_log_at_trx_commit 2 (au lieu de 1) en lecture/écriture intensives Meilleure performance, perte de < 1 s de données en cas de crash.
innodb_flush_method O_DIRECT Évite le double-cache du OS.
innodb_file_per_table ON Permet le redimensionnement individuel des tables.
max_connections 250 (ou plus selon charge) Dolibarr concatène plusieurs appels simultanés.
thread_cache_size 10‑20 Réduit le coût de création de threads.
table_open_cache 4000 (ou open_files_limit = 65535) Grand nombre de tables ouvertes.
tmp_table_size / max_heap_table_size 256M / 128M Optimise les GROUP BY temporaires.
join_buffer_size 4M (ou 1M si join_buffer_size est déjà suffisant) Allège les jointures non indexées.
query_cache_type OFF (déprécié dans 8.0) Le cache de requêtes MySQL est souvent désactivé par défaut.

À tester : après modification, redémarrer le service et surveiller SHOW ENGINE INNODB STATUS\G et les métriques SELECT * FROM performance_schema.events_waits_summary_by_instance;.

3.2. Schéma de base de données

Table Problème fréquent Solution d’indexation
llx_facture Jointure récurrente sur fk_soc, date INDEX idx_facture_soc_date (fk_soc, date)
llx_stock Filtrage sur ref, product_id, location INDEX idx_stock_ref (ref), INDEX idx_stock_loc (location)
llx_product Recherche par reference, name INDEX idx_product_ref (reference), FULLTEXT idx_product_name (name)
llx_categorie Recherche par ref INDEX idx_categorie_ref (ref)
llx_user Recherche par login INDEX idx_user_login (login)
llx_price Calculs de prix unitaires Partitionner par year si > 1 M de lignes.

Conseils supplémentaires

  • Utiliser PREFERRED_INDEX_METHOD=BTREE pour les colonnes de type INT ou VARCHAR courts.
  • Supprimer les index redondants (SHOW INDEX FROM table;) qui ne sont jamais utilisés (ex. idx_x).
  • Décomposer les tables volumineuses (PARTITION BY RANGE (year_created)) si elles dépassent 500 Mo.

3.3. Cache et couche d’abstraction

  • PHP OpCache : activer opcache.enable=1, opcache.memory_consumption=256, opcache.max_accelerated_files=20000.
  • Redis / Memcached : mettre en cache les listes statiques (ex. GET_PRODUCT_BY_REF) qui changent peu.
  • Reverse proxy (Varnish) ou Nginx FastCGI Cache pour les pages d’affichage en lecture seule.


4. Plan d’action concret (Road‑map)

4.1. Phase 0 – Pré‑production

Étape Action Outils Résultat attendu
0.1 Cartographier le modèle : exporter le schéma (mysqldump --no-data) mysqldump, SchemaSpy Diagramme complet (ER).
0.2 Baseline de charge : 5 k requêtes simulées (JMeter ou Locust) JMeter + script dolist.php Rapport de latence, % de requêtes > 2 s.
0.3 Config sauvegardée : enregistrer le my.cnf actuel, prendre des snapshots de la BDD. SELECT @@version, SHOW VARIABLES LIKE 'innodb_%' Documentation de l’état initial.

4.2. Phase 1 – Nettoyage du schéma

  1. Audit des index : SELECT table_name, index_name, seq_in_index FROM information_schema.statistics WHERE table_schema='mydb';
  2. Création d’index ciblés (exemples) :
    ALTER TABLE llx_facture ADD INDEX idx_facture_soc_date (fk_soc, date);
    ALTER TABLE llx_stock ADD INDEX idx_stock_ref (ref);
    ALTER TABLE llx_price ADD INDEX idx_price_product (product_id);
  3. Vérifier la correction avec EXPLAIN sur les requêtes lentes.
  4. Test de performance post‑indexation (répéter la charge).

KPIs : Temps moyen des requêtes > 1 s doit passer sous 300 ms ; taux de « slow queries » < 1 %.

4.3. Phase 2 – Tuning du serveur MySQL

  1. Activer le slow‑query‑log si non déjà :
    slow_query_log = 1
    long_query_time = 0.5
    log_queries_not_using_indexes = 1
  2. Mettre à jour les variables selon le tableau 3.1 (ajustement progressif).
  3. Surveiller les indicateurs Innodb_buffer_pool_read_requests vs Innodb_buffer_pool_reads → taux de hit > 95 %.
  4. Re‑évaluer les paramètres après 48 h de charge réaliste.

4.4. Phase 3 – Optimisation de la couche PHP

Action Détails
Profilage blackfire ou tideways → identifier les fonctions qui consomment > 10 ms.
Refactorisation Regrouper les appels répétés (ex. charger la même liste de produits) dans une seule requête.
Cache des résultats Utiliser LLX_CACHE_CONF de Dolibarr : define('CACHE_FORCE', 1); ou cache_sql dans conf.php.
Lazy‑loading Ne charger que les champs nécessaires (SELECT ref, label FROM llx_product LIMIT 100).

4.5. Phase 4 – Scalabilité horizontale (si besoin)

Situation Solution recommandée
Croissance > 10 M d’enregistrements Partitionnement InnoDB : par année ou par statut (PARTITION BY LIST (status)).
Pointe de trafic Replication en lecture (master‑slave) avec un read-only proxy (ProxySQL) qui redirige les SELECT vers les esclaves.
Besoin d’une architecture distribuée Sharding par fk_soc ou date à l’aide de MySQL Fabric ou ProxySQL + MySQL Router.
Haute disponibilité Galera Cluster ou MariaDB ColumnStore si les écritures massives sont rares.

Note : avant de mettre en place un cluster, valider le coût en maintenance et les exigences de cohérence des données (transactions ACID). ### 4.6. Phase 5 – Tests de charge finale & mise en production 1. Scénario de pic : 500 requêtes simultanées pendant 10 min (Locust).

  1. Métriques à suivre :

    • Latence 95e percentile < 200 ms. – Taux d’erreurs MySQL < 0.1 %.
    • Utilisation CPU < 70 % sur le serveur DB.
  2. Validation fonctionnelle : QA fonctionnelle sur les modules critiques (dévis, factures, stocks).
  3. Plan de reprise : sauvegarde incrémentale (xtrabackup) toutes les 4 h + point‑in‑time recovery (PITR).


5. Checklist de mise en production

Action
1 mysqldump --no-create-info --extended-insert --single-transaction de toutes les tables (backup compressé).
2 Nettoyage des index inutilisés (ALTER TABLE ... DROP INDEX …).
3 Application des nouveaux index critiques.
4 Mise à jour du fichier conf.php ($conf['dir']['cache'] et $conf['currency']['auto_update']).
5 Activation d’OpCache et du cache de session (session.save_handler = redis).
6 Redémarrage du service MySQL et vérification de SHOW VARIABLES LIKE 'innodb_%'.
7 Monitoring (Zabbix / Prometheus) : mysql_global_status, mysql_global_variables, php_fpm_status.
8 Documentation de la configuration (version du my.cnf, paramètres clés, scripts de monitoring).
9 Plan de rollback : restauration à partir du dump du jour précédent.
10 Communication avec les équipes fonctionnelles sur les délais d’indisponibilité prévus.


6. Exemples de scripts utiles

6.1. Capture des requêtes lentes (> 0.5 s) « `bash

mysql -N -e "
SELECT concat(‘# ‘, id, ‘ ‘, event_time)
FROM mysql.slow_log WHERE command_type=’Query’ AND start_time > NOW() – INTERVAL 1 DAY
ORDER BY start_time DESC;
" > /var/log/mysql/slow_queries.log


### 6.2. Génération d’un rapport d’index non‑utilisé
```sql
SELECT t.TABLE_NAME,
i.INDEX_NAME,
ROUND(LEFT(i.INDEX_DEFINITION,70),0) AS definition
FROM information_schema.STATISTICS i
JOIN information_schema.TABLES t ON i.TABLE_SCHEMA=t.TABLE_SCHEMA AND i.TABLE_NAME=t.TABLE_NAME
LEFT JOIN performance_schema.events_waits_current ev
ON ev.OBJECT_NAME = CONCAT('table/', i.TABLE_SCHEMA, '/', i.TABLE_NAME)
WHERE i.INDEX_NAME NOT IN (
SELECT index_name FROM performance_schema.table_stats WHERE STAT_NAME='requests'
)
AND t.TABLE_SCHEMA='mydb'
ORDER BY t.TABLE_NAME;

6.3. Partage de configuration Docker‑Compose (MySQL + Dolibarr)

version: '3.8'
services:
db:
image: mariadb:10.11
restart: always
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: dolibarr
MYSQL_USER: dolibarr
MYSQL_PASSWORD: dolibarr
volumes:
- db_data:/var/lib/mysql
command: ["--innodb-buffer-pool-size=4G",
"--max_connections=300",
"--innodb_log_file_size=2G",
"--slow_query_LOG=ON",
"--long_query_time=0.5"]
dolibarr:
image: dolibarr/dolibarr:latest
depends_on: [db]
ports: ["80:80"]
environment:
PHP_OPCACHE_ENABLE=1
MYSQL_HOST: db volumes:
- ./html:/var/www/html
restart: always
volumes:
db_data:


7. Conclusion

Optimiser Dolibarr sur MySQL/MariaDB ne consiste pas seulement à « mettre plus de RAM ». Il faut :

  1. Diagnostiquer les requêtes lentes et identifier les colonnes non indexées.
  2. Adapter le schéma (index, partitions) aux profils d’accès réels.
  3. Ajustar les paramètres InnoDB (pool, log, flush) pour maximiser le débit tout en limitant la perte de données.
  4. Adapter la couche PHP (opcache, cache de résultats) afin de réduire le nombre de trips vers la BDD.
  5. Planifier la montée en charge (réplication, sharding) seulement si les tests de charge montrent que le serveur unique atteint ses limites.

En suivant le plan d’action présenté (audit → indexation → tuning → tests → mise en production), il est possible de passer d’un environnement limité à des centaines de milliers d’opérations par jour, voire à des millions d’enregistrements, tout en conservant la réactivité attendue par les utilisateurs finaux. —

Document préparé par l’équipe d’architecture
Pour toute question complémentaire, n’hésitez pas à nous contacter : archi@exemple.com

Publications similaires