Aller au contenu

Mongoose : performances et production

Définis dans le schéma :

schemaClient.index({ email: 1 }, { unique: true });
schemaCommande.index({ client: 1, passeeLe: -1 });
schemaCommande.index(
{ statut: 1 },
{ partialFilterExpression: { statut: { $ne: 'expediee' } } },
);
schemaArticle.index({ nom: 'text', description: 'text' });
schemaSession.index({ creeLe: 1 }, { expireAfterSeconds: 3600 }); // suppression automatique
Type d’index Usage
Simple, composé Filtres et tris (l’ordre des champs compte)
unique Interdit les doublons
partialFilterExpression Index limité à certains documents
text Recherche textuelle
expireAfterSeconds Suppression automatique (TTL)

Mongoose crée les index au démarrage (autoIndex: true par défaut). Sur une grosse collection, c’est coûteux et peut ralentir le démarrage : désactiver en production et gérer les index par une étape de déploiement.

await mongoose.connect(process.env.MONGODB_URI, {
autoIndex: process.env.NODE_ENV !== 'production',
});

Script de synchronisation (à lancer lors d’un déploiement) :

import mongoose from 'mongoose';
import './models/index.js';
await mongoose.connect(process.env.MONGODB_URI);
await Promise.all(Object.values(mongoose.models).map((m) => m.syncIndexes()));
await mongoose.disconnect();
const plan = await Commande.find({ client: id })
.sort({ passeeLe: -1 })
.explain('executionStats');
console.log(
plan.executionStats.totalDocsExamined,
plan.executionStats.nReturned,
);

totalDocsExamined très supérieur à nReturned indique un index manquant. Plus de détails : MongoDB : index et performances.

Pratique Effet
.lean() Évite la création de documents Mongoose
.select('champ1 champ2') Ne lit que les champs utiles
.limit() systématique sur les listes Évite de charger toute la collection
Curseur (.cursor()) pour les gros traitements Mémoire constante
countDocuments avec filtre indexé, ou estimatedDocumentCount Évite un comptage lent
Éviter les populate en boucle Une requête par appel : regrouper
bulkWrite pour de nombreuses écritures Moins d’allers-retours
await Client.bulkWrite([
{ updateOne: { filter: { _id: id1 }, update: { $set: { actif: false } } } },
{ insertOne: { document: { nom: 'Eve', email: 'eve@exemple.com' } } },
{ deleteOne: { filter: { _id: id2 } } },
]);
await mongoose.connect(process.env.MONGODB_URI, {
maxPoolSize: 20,
minPoolSize: 2,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
});

Dimensionner maxPoolSize en fonction du nombre d’instances de l’application (chaque processus a son propre pool) et de net.maxIncomingConnections du serveur MongoDB. Avec le mode cluster de PM2 (voir PM2 : mode cluster), le nombre de connexions se multiplie par le nombre d’instances.

Dans mongosh :

db.setProfilingLevel(1, { slowms: 100 });
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty();

Voir MongoDB : index et performances. Activer le mode debug de Mongoose pour afficher les requêtes (développement seulement) :

mongoose.set('debug', process.env.NODE_ENV !== 'production');
  • Compte MongoDB dédié à l’application, avec le rôle readWrite sur sa seule base (voir MongoDB : utilisateurs et droits).
  • Chaîne de connexion dans l’environnement ; mot de passe encodé.
  • MongoDB n’écoute que sur 127.0.0.1 ou le réseau privé ; pare-feu limité aux adresses des applications (voir UFW : écrire des règles).
  • mongoose.set('sanitizeFilter', true) et validation des entrées contre l’injection NoSQL.
  • Ne pas renvoyer de champs sensibles (select: false, toJSON).
  • Chiffrer les connexions (TLS) quand la base est sur un autre serveur : ?tls=true.

/etc/monapi.env (droits 640) :

NODE_ENV=production
PORT=3000
MONGODB_URI=mongodb://app:<mot-de-passe>@127.0.0.1:27017/boutique?authSource=boutique

/etc/systemd/system/monapi.service :

[Unit]
Description=API monapi (Express et Mongoose)
After=network.target mongod.service
Wants=mongod.service
[Service]
User=monapi
Group=monapi
WorkingDirectory=/srv/monapi/app
EnvironmentFile=/etc/monapi.env
ExecStart=/usr/bin/node src/serveur.js
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
Fenêtre de terminal
sudo systemctl daemon-reload
sudo systemctl enable --now monapi
sudo journalctl -u monapi -f

Voir Express : sécurité et production et systemd : écrire un service. L’application doit se reconnecter après un redémarrage de MongoDB : Mongoose le fait automatiquement.

Les données se sauvegardent côté MongoDB (voir MongoDB : sauvegarde et restauration), pas par Mongoose.

Symptôme Piste
Démarrage lent sur grosse base autoIndex actif : le désactiver en production
E11000 sur un champ unique après ajout de l’index Doublons existants : les nettoyer avant de créer l’index
MongoServerSelectionError après une coupure MongoDB injoignable : systemctl status mongod, réseau, pare-feu
MongoPoolClearedError / connection pool cleared Coupure réseau : vérifier la stabilité, ajuster serverSelectionTimeoutMS
Trop de connexions côté MongoDB Plusieurs instances × maxPoolSize : réduire le pool
Mémoire qui croît Lectures sans lean() sur de grosses listes, curseur non consommé, documents conservés en cache
Lenteur d’une requête explain('executionStats') : index manquant

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Aucune propre à Ubuntu. La compatibilité dépend des versions de Mongoose, du pilote MongoDB intégré et du serveur MongoDB installé ; le chemin de node dépend de l’installation (which node).