Aller au contenu
3 novembre 2025·Mis à jour le ·17 min de lecture

Governor Limits Salesforce : comprendre, surveiller et les éviter

Guide pratique sur les governor limits Salesforce — les plus critiques, comment surveiller votre consommation et les patterns pour rester bien en dessous des seuils.

← Retour

Les governor limits sont les règles que Salesforce impose pour garantir qu'aucun org ne monopolise les ressources partagées de la plateforme. Les comprendre est essentiel — les dépasser en production génère des exceptions, des automatisations cassées et des utilisateurs mécontents.

Pourquoi les governor limits existent

Salesforce est une plateforme multi-tenant. Votre org partage les ressources de calcul, de base de données et d'API avec des milliers d'autres. Les governor limits empêchent qu'un seul org consomme une part disproportionnée des ressources. Ce ne sont pas des bugs — c'est l'architecture.

Les limites qui posent vraiment problème

Limites synchrones par transaction

LimiteValeurDéclencheur courant
Requêtes SOQL100SOQL dans une boucle
Instructions DML150DML dans une boucle
Lignes DML10 000Opérations en masse
Lignes SOQL50 000Récupération de tables entières
Taille du heap6 MoStockage de grands jeux de données
Temps CPU10 000 msBoucles complexes, algorithmes
Callouts100Appels API externes

Limites asynchrones par transaction (Batch, Future, Queueable)

LimiteValeur
Requêtes SOQL200
Instructions DML150
Lignes DML10 000
Taille du heap12 Mo (tout l'Apex asynchrone)
Temps CPU60 000 ms

Limites quotidiennes à l'échelle de l'org

LimiteValeur
Exécutions Apex asynchrones (Batch, Future, Queueable, Scheduled)250 000 ou 200 × le nombre de licences utilisateur, la valeur la plus élevée, sur 24 h glissantes
Alertes email (règles de workflow, approbations, flows)1 000 par licence Salesforce standard (15 en Developer Edition), plafonné à 2 000 000 pour l'org
Appels APIVarie selon l'édition

Ces limites d'org se consultent à tout moment avec OrgLimits.getMap() en Apex ou la ressource REST /limits.

Surveiller vos limites

En code Apex

// Vérifier les limites restantes à tout moment de l'exécution
System.debug('SOQL : ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
System.debug('DML : ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
System.debug('Lignes DML : ' + Limits.getDmlRows() + '/' + Limits.getLimitDmlRows());
System.debug('CPU : ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());
System.debug('Heap : ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());

Garde proactive avant les opérations critiques

public class LimitGuard {
    public static Boolean hasSoqlCapacity(Integer needed) {
        return (Limits.getLimitQueries() - Limits.getQueries()) >= needed;
    }
 
    public static Boolean hasDmlCapacity(Integer needed) {
        return (Limits.getLimitDmlStatements() - Limits.getDmlStatements()) >= needed;
    }
}
 
// System.LimitException ne peut être ni instanciée ni interceptée :
// on lève une exception personnalisée, que l'appelant peut gérer.
public class InsufficientCapacityException extends Exception {}
 
// Utilisation
if (!LimitGuard.hasSoqlCapacity(5)) {
    throw new InsufficientCapacityException('Capacité SOQL insuffisante pour continuer.');
}

Les patterns qui préviennent la plupart des violations

1. Jamais de SOQL ou DML dans une boucle

C'est la cause n°1 de violations des governor limits. Une requête par enregistrement = 201 requêtes pour 201 enregistrements.

// ❌ SOQL dans une boucle — dépasse la limite à l'enregistrement 101
for (Opportunity opp : opportunities) {
    Account acc = [SELECT Name FROM Account WHERE Id = :opp.AccountId];
}
 
// ✅ Pattern bulkifié
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : opportunities) accountIds.add(opp.AccountId);
 
Map<Id, Account> accountMap = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);
 
for (Opportunity opp : opportunities) {
    Account acc = accountMap.get(opp.AccountId);
    // ... utiliser acc
}

2. Collecter puis exécuter le DML une seule fois

// ❌ DML dans une boucle
for (Contact c : contacts) {
    c.Statut__c = 'Traité';
    update c; // 1 DML par enregistrement
}
 
// ✅ Collecter puis DML une fois
List<Contact> aMettre = new List<Contact>();
for (Contact c : contacts) {
    c.Statut__c = 'Traité';
    aMettre.add(c);
}
if (!aMettre.isEmpty()) update aMettre; // 1 DML total

3. Utiliser @future ou Queueable pour les callouts

public class SyncExterneService {
 
    @future(callout=true)
    public static void syncVersERP(List<Id> accountIds) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
        // ... faire les appels HTTP
    }
}

4. Requêtes agrégées pour réduire le nombre de lignes

// ❌ Récupérer 50k lignes juste pour les compter
List<Opportunity> all = [SELECT Id FROM Opportunity WHERE AccountId = :accId];
Integer count = all.size(); // consomme 50k de votre limite de 50k lignes !
 
// ✅ Agréger au niveau de la base de données
Integer count = [SELECT COUNT() FROM Opportunity WHERE AccountId = :accId];

Queueable Apex : mieux que @future

Les jobs Queueable sont le remplacement moderne de @future — ils supportent le chaînage et les paramètres typés :

public class SyncQueueable implements Queueable, Database.AllowsCallouts {
    private List<Id> recordIds;
 
    public SyncQueueable(List<Id> ids) {
        this.recordIds = ids;
    }
 
    public void execute(QueueableContext ctx) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :recordIds];
        // ... traitement
 
        // Chaîner le job suivant si plus de travail à faire
        if (plusDeTravail()) {
            System.enqueueJob(new SyncQueueable(prochainLot));
        }
    }
}
 
// Enqueue
System.enqueueJob(new SyncQueueable(accountIds));

Que faire quand vous heurtez une limite

LimitException en production

  1. Vérifier les debug logs — identifier quelle ligne a levé l'exception
  2. Trouver la cause racine — souvent un trigger appelant une méthode qui fait du SOQL/DML en boucle
  3. Vérifier la récursion — le trigger s'appelle-t-il lui-même ? Utiliser une garde de récursion fondée sur les Id déjà traités (un simple booléen ignore les lots suivants de 200 enregistrements)
  4. Refactoriser — appliquer les patterns de bulkification
  5. Considérer l'asynchrone — déplacer le traitement lourd vers Batch ou Queueable

Scénario courant : Trigger + Flow déclenché par enregistrement

// Un trigger se déclenche → un Flow déclenché par enregistrement met à jour des enregistrements → le trigger se redéclenche
// Solution : garde de récursion fondée sur les Id déjà traités — un simple booléen
// ignorerait les lots suivants de 200 enregistrements (voir l'article sur les triggers)
 
public class TriggerControl {
    // Clé = trigger + contexte + Id : un enregistrement est traité une fois par contexte
    private static Set<String> processed = new Set<String>();
 
    public static Boolean markProcessed(String triggerName, Id recordId) {
        return processed.add(triggerName + ':' + Trigger.operationType + ':' + recordId);
    }
}
 
// Dans le handler du trigger
List<Account> toProcess = new List<Account>();
for (Account acc : (List<Account>) Trigger.new) {
    if (TriggerControl.markProcessed('AccountTrigger', acc.Id)) {
        toProcess.add(acc);
    }
}
if (toProcess.isEmpty()) return;
// ... votre logique, sur toProcess uniquement

Tableau récapitulatif : anti-patterns → solutions

Anti-PatternProblèmeSolution
SOQL dans une boucleLimite de 100 requêtesCollecter les ID, requête unique avec IN
DML dans une boucleLimite de 150 DMLCollecter les enregistrements, DML unique
SOQL large sans LIMITLimite de 50 000 lignesAjouter WHERE, LIMIT ou agrégat
Récursion de triggerBoucle infinieGarde de récursion fondée sur les Id déjà traités
Callout depuis un triggerCallout synchrone interdit dans un trigger, et refusé après un DML non validé (« uncommitted work pending »)@future(callout=true) ou Queueable + AllowsCallouts
Stockage de listes SObject complètesHeap de 6 MoUtiliser des ensembles/maps d'ID

Conclusion

Les governor limits vous forcent à écrire un meilleur code — bulkifié, efficace et conscient de la consommation de ressources. Les patterns présentés ici (requêtes en masse, DML unique, callouts asynchrones, SOQL agrégé) ne sont pas des contournements — ce sont les fondations du développement Salesforce professionnel. Appliquez-les systématiquement et vous verrez rarement une LimitException en dehors des tests.


Ressources utiles :

Aïcha Imène DAHOUMANE
À propos de l'auteure
Aïcha Imène DAHOUMANE
Administratrice & Développeuse Salesforce

Développeuse Salesforce avec un background en infrastructures IT — je construis des solutions fiables et maintenables avec Apex, Flows, LWC et CI/CD.

PartagerPartager sur LinkedIn