3

I have this class that call a Queueble job more than 50 times in a loop. I recive the error: Too many queueable jobs added to the queue. The limit is 50.

Here is the class:

global class NSStatusOrderCronSC implements Schedulable {
global void execute(SchedulableContext sc)
{
    String norder;
    String IdOpp;
    List<Opportunity> opList = [SELECT Id FROM Opportunity Where StageName != 'Cerrado Ganado' AND StageName != 'Cancelado'];
    List<Order> acList = [SELECT Pedido__c, Oportunidad__c FROM Order WHERE Pedido__c != null AND Oportunidad__c in :opList];
    for(Order ac : acList)
    {
        IdOpp = ac.Oportunidad__c;
        norder = ac.Pedido__c;
        if(!acList.isEmpty()){
            ID jobID = System.enqueueJob(new NSQueuebleJob(IdOpp, norder));
        }
    }
}
}

Inside the NSQueuebleJob i have this:

public class NSQueuebleJob implements Queueable, Database.AllowsCallouts {

private String IdOpp;
private String norder;

public NSQueuebleJob(String IdOpp, String norder){
    this.IdOpp = IdOpp;
    this.norder = norder;
}

public void execute(QueueableContext context) {
            String endpoint = 'http://url;
            HttpRequest req = new HttpRequest();
            req.setEndpoint(endpoint);
            req.setMethod('GET');
            req.setHeader('Content-Type', 'application/json');

        //LOGIC to update some fields    
}
}

How can i search all the opportunities that have created orders and update some fields without reaching the limit?

3

Why don't you use Queueable to process a 'Queue' of Order's?

As per the docs:

No limit is enforced on the depth of chained jobs, which means that you can chain one job to another job and repeat this process with each new child job to link it to a new child job. For Developer Edition and Trial organizations, the maximum stack depth for chained jobs is 5, which means that you can chain jobs four times and the maximum number of jobs in the chain is 5, including the initial parent queueable job.

This means you can use a list of SObjects as the 'queue' and then process them one by one until you get to the bottom of your chain. I've included an example of how you might do this.

global class NSStatusOrderCronSC implements Schedulable {
    global void execute(SchedulableContext sc)
    {
        String norder;
        String IdOpp;
        List<Opportunity> opList = [SELECT Id FROM Opportunity Where StageName != 'Cerrado Ganado' AND StageName != 'Cancelado'];
        List<Order> acList = [SELECT Pedido__c, Oportunidad__c FROM Order WHERE Pedido__c != null AND Oportunidad__c in :opList];

        if(!acList.isEmpty()){
            ID jobID = System.enqueueJob(new NSQueuebleJob(acList));
        }
    }

    public class NSQueuebleJob implements Queueable, Database.AllowsCallouts {
        private List<Order> orders;
        public NSQueuebleJob(List<Order> orders)
        {
            this.orders = orders;
        }

        public void execute(QueueableContext qc)
        {
            Order order = orders.remove(0);

            // do whatever you intended to do

            // still more to process?
            if(orders.size() > 0)
            {
                ID jobID = System.enqueueJob(new NSQueuebleJob(orders));
            }
        }
    }
}
2

One of the approach could be to make a Queueable job to make few requests per execution to external endpoint and then process all the returned data. For example:

public class NSQueuebleJob implements Queueable, Database.AllowsCallouts {

  private Map<id, String> OpportunitiesToProcess;

  public NSQueuebleJob(Map<Id, String> inputOpportunities){
      OpportunitiesToProcess = inputOpportunities;
  }

  public void execute(QueueableContext context) {
      Map<Id, String> response_map = new Map<Id, String>();
      Http http = new Http();

      for(Id opportunity_id : OpportunitiesToProcess) {
          String endpoint = constructURL(opportunity_id, OpportunitiesToProcess.get(opportunity_id));
          HttpRequest req = new HttpRequest();
          req.setEndpoint(endpoint);
          HttpResponse response = http.send(request_instance);
          responseMap.put(opportunity_id, response.getBody());
      }

      for(Id opportunity_id : response_map) {
          //Logic to update some fields + collect all changes
      }
      //Logic to perform a DML regarding previously updated fields
  }
}

In the given example, you can pass few records into your queueable job:

List<Opportunity> opList = [SELECT Id FROM Opportunity Where StageName != 'Cerrado Ganado' AND StageName != 'Cancelado'];
List<Order> acList = [SELECT Pedido__c, Oportunidad__c FROM Order WHERE Pedido__c != null AND Oportunidad__c in :opList];
Map<Id, String> queueable_job_params = new Map<Id, String>();
for(Order ac : acList)
{
    IdOpp = ac.Oportunidad__c;
    norder = ac.Pedido__c;
    if(!acList.isEmpty()){
        queueable_job_params.put(ac.Oportunidad__c, ac.Pedido__c);
        ID jobID = 
    }
    if (queueable_job_params.size() >= 20) {
      System.enqueueJob(new NSQueuebleJob(queueable_job_params));
      queueable_job_params.clear();
    }
}
if (!queueable_job_params.isEmpty()) {
   System.enqueueJob(new NSQueuebleJob(queueable_job_params));
}

In this case, you might decide which number of records should be passed into the single instance of queueable job execution. In case if there are a lot of records included -- it would be more wise to use an apex batch job.

Another approach could be Chaining -- basically pass all of the records into Queueable job, but process only as many, as you can based on your Heap Size/ Callout amount/ Total Callout Duration limit(s) per single execution.

0

In addition to the answers herein; I always preface any attempt to enqueue a Queueable with this

if (Util.isEnqueueable) {
  System.enqueueJob(...);
else {
   some fallback strategy - like using a schedulable
}

where Util.isEnqueueable looks like this:

public static Boolean isEnqueueable {
    get {
        return isEnqueueable == null 
         ? Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0 
         : isEnqueueable;
    } set;
}

A static variable (rather than method) is used so testMethods can coerce the state to false and allow for testing of the fallback logic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.