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 :
- Les points de diagnostic (symptômes, métriques à relever).
- Les leviers d’optimisation (schéma DB, index, configuration serveur, réglages SQL).
- 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
-
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; -
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). - Lecture :
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\Get les métriquesSELECT * 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=BTREEpour les colonnes de typeINTouVARCHARcourts. - 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
- Audit des index :
SELECT table_name, index_name, seq_in_index FROM information_schema.statistics WHERE table_schema='mydb'; - 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); - Vérifier la correction avec
EXPLAINsur les requêtes lentes. - 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
- Activer le slow‑query‑log si non déjà :
slow_query_log = 1
long_query_time = 0.5
log_queries_not_using_indexes = 1 - Mettre à jour les variables selon le tableau 3.1 (ajustement progressif).
- Surveiller les indicateurs
Innodb_buffer_pool_read_requestsvsInnodb_buffer_pool_reads→ taux de hit > 95 %. - 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).
- Métriques à suivre :
- Latence 95e percentile < 200 ms. – Taux d’erreurs MySQL < 0.1 %.
- Utilisation CPU < 70 % sur le serveur DB.
- Validation fonctionnelle : QA fonctionnelle sur les modules critiques (dévis, factures, stocks).
- 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 :
- Diagnostiquer les requêtes lentes et identifier les colonnes non indexées.
- Adapter le schéma (index, partitions) aux profils d’accès réels.
- Ajustar les paramètres InnoDB (pool, log, flush) pour maximiser le débit tout en limitant la perte de données.
- Adapter la couche PHP (opcache, cache de résultats) afin de réduire le nombre de trips vers la BDD.
- 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