Monday 18 May 2015

Avoiding Script Timeouts - Part 2

Avoiding Script Timeouts Part 2


So, you've done your very best, but that wonderful script that's going to make your life so much easier and your clients so much happier is still timing out every time it runs.  What can you do?

If you're already selecting only the elements you need and your script is as fast as it can be, there's really only one option to allow scripting to process the entire intended scope - you need to split the process up into smaller chunks.  At the time of writing, AdWords Scripts can run as often as once per hour so there is the opportunity to run the same script up to 24 times per day or 12 hours of run time.  If you can't address your entire scope in 12 hours you need to go back to the drawing board!

So, here are some options for splitting up your intended scope:

#1 - Use Labels

Labels are really useful in scripting since they're a dead easy and quick way to identify elements you want to be addressed by the script.  Let's say you have 20 Campaigns you want to process and your script is timing out before it's finished the 7th Campaign so you're guessing you need to run the script 4 times to be absolutely sure.  Simply apply four Labels, for example "Block_1", "Block_2", etc. to the Campaigns in blocks of 5 (or as appropriate so they have roughly the same "load") then use a condition in your Campaign selector as follows:

.withCondition("LabelNames CONTAINS_ANY ['Block_1']")

When the script runs it will only process the Campaigns with the Label "Block_1".  Run the script 3 more times using the appropriate Labels and you're done.

I'll describe how to do this automatically later, but let's move on to another option.

#2 - Set limits

If you're addressing a large Account it's likely you've got a spread of performance metrics across the elements you're looking to process so you could use conditions to split the scripts up, for example:

.withCondition("Impressions > 1000")

(don't forget you'll need a DateRange condition as well).  This option can be useful where the time that the script runs is important.  You may want to ensure your biggest spenders, for example, are processed first in a day and this becomes increasingly important the more runs you need to make.  If, for example, you need the script to run 10 times before it addresses the whole scope, even if you start at 1am you'll still be into the working day before the last elements are processed so using this sort of condition can ensure the most important ones are done first.

#3 - Use Labels (again)

Another use of Labels is in marking elements are "processed".  So, if you were running your script against Keywords you could apply the Label "Processed" to each Keyword you complete and use a selector with the line:

.withCondition("LabelNames CONTAINS_NONE ['Processed']")

in this way, each time the script runs it'll only work on the Keywords not already processed.  The only real downsides to this method are that you need to use another script to remove these processed Labels before each new "day" of script running and unless you also label Campaigns/Ad Groups your script could be inefficient as it searches Campaigns where all the Keywords are already done.

OK, so there's three options for splitting, but how do you get the script to run more than once?  You could use a number of individual scripts pre-set with their conditions and running at different hours, but that's not very efficient and makes it harder to edit the script (since you have to make sure you make exactly the same edits to each one).  I prefer to use systems based upon the hour of the day and just have the same script run every hour.

For example, the following script snippet will return the hour of the day (in your Account time zone):

var theHour = new Date(Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), 'MMM dd,yyyy HH:mm:ss')).getHours();

If you've used a Label naming convention like the one I have above, you can then build your required Label name using the following:

var labelName = "Block_" + theHour;

So, in your script, you might have the following:

function main() {
  var theHour = new Date(Utilities.formatDate(new Date(),   AdWordsApp.currentAccount().getTimeZone(), 'MMM dd,yyyy HH:mm:ss')).getHours();
  if(theHour > 0 && theHour < 5) {
    var labelName = "Block_" + theHour;
    doProcess(labelName);
  }
}  

function doProcess(labelName) {
  var campIter = AdWordsApp.campaigns()
    .withCondition("LabelNames CONTAINS_ANY ['" + labelName + "']")
    .get();
}

Set that script to run every hour and it will only process the Account in the scripts that run at 1am, 2am, 3am and 4am and will automatically run on the Campaigns with the appropriate Labels.

You can extend this use of Labels and hours to many other situations so go have fun!

In the next post I'll look at some options that'll make the best use of a script you just can't stop timing out.

No comments: