De root cause van Apps Script-traagheid
Apps Script is traag wanneer het te veel aanroepen doet naar Google-services zoals Sheets, Drive en Gmail. Elke aanroep naar getValue(), getRange() of sendEmail() communiceert via de server. In een lus van 1000 iteraties zijn dat 1000 serverrondritten.
De oplossing is simpel: batch alles. Lees eenmaal, verwerk in JavaScript, schrijf eenmaal.
Batch lezen met getValues()
function traag() {
const blad = SpreadsheetApp.getActiveSheet();
const aantalRijen = blad.getLastRow();
let totaal = 0;
for (let i = 1; i <= aantalRijen; i++) {
totaal += blad.getRange(i, 1).getValue();
}
Logger.log(totaal);
}
function snel() {
const blad = SpreadsheetApp.getActiveSheet();
const waarden = blad.getRange(1, 1, blad.getLastRow(), 1).getValues();
const totaal = waarden.reduce((som, rij) => som + Number(rij[0] || 0), 0);
Logger.log(totaal);
}
Het verschil: traag() doet N serveraanroepen, snel() doet er 1.
Batch schrijven met setValues()
function schrijfTraag(blad, data) {
for (let i = 0; i < data.length; i++) {
blad.getRange(i + 1, 1).setValue(data[i].naam);
blad.getRange(i + 1, 2).setValue(data[i].waarde);
}
}
function schrijfSnel(blad, data) {
const matrix = data.map(item => [item.naam, item.waarde]);
blad.getRange(1, 1, matrix.length, matrix[0].length).setValues(matrix);
}
Gebruik flush() spaarzaam
Roep SpreadsheetApp.flush() alleen aan het einde van een zware schrijfoperatie aan, of wanneer je de sheet tussentijds zichtbaar wilt bijwerken. Een flush() binnen een lus vertraagt je script drastisch, omdat het bij elke iteratie alle openstaande wijzigingen wegschrijft.
CacheService voor herhaalde berekeningen
Met CacheService voorkom je dat je dure berekeningen of externe aanroepen opnieuw uitvoert. De maximale bewaartijd is 6 uur (21600 seconden); zonder waarde gebruikt Apps Script standaard 600 seconden. Houd er rekening mee dat een bewaartijd slechts een suggestie is: bij veel data kan een item eerder worden verwijderd.
function berekenMetCache(invoer) {
const cache = CacheService.getScriptCache();
const cacheKey = `berekening_${invoer}`;
const gecachede = cache.get(cacheKey);
if (gecachede !== null) {
return JSON.parse(gecachede);
}
const resultaat = zwareBerekening(invoer);
cache.put(cacheKey, JSON.stringify(resultaat), 600);
return resultaat;
}
function zwareBerekening(invoer) {
Utilities.sleep(100);
return invoer * 2;
}
Kies de juiste cache-scope
getScriptCache() deelt data tussen alle gebruikers van het script, getUserCache() is per gebruiker en getDocumentCache() is gekoppeld aan het actieve document. Elke waarde mag maximaal 100 KB groot zijn; gebruik voor grotere of langere opslag PropertiesService.
Service-aanroepen buiten lussen houden
function suboptimaal() {
const blad = SpreadsheetApp.getActiveSheet();
for (let i = 1; i <= 100; i++) {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const naam = spreadsheet.getName();
blad.getRange(i, 3).setValue(naam);
}
}
function optimaal() {
const blad = SpreadsheetApp.getActiveSheet();
const naam = SpreadsheetApp.getActiveSpreadsheet().getName();
const aantalRijen = blad.getLastRow();
const data = Array(aantalRijen).fill([naam]);
blad.getRange(1, 3, aantalRijen, 1).setValues(data);
}
Meerdere bladen tegelijk verwerken
function verwerkMeerdereBladen() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const bladen = ss.getSheets();
const alleData = bladen.map(blad => ({
naam: blad.getName(),
data: blad.getDataRange().getValues(),
}));
const verwerkt = alleData.map(({naam, data}) => ({
naam,
totaal: data.slice(1).reduce((som, rij) => som + Number(rij[1] || 0), 0),
}));
const resultaatBlad = ss.getSheetByName('Samenvatting') || ss.insertSheet('Samenvatting');
resultaatBlad.clearContents();
resultaatBlad.getRange(1, 1, 1, 2).setValues([['Blad', 'Totaal']]);
const outputData = verwerkt.map(r => [r.naam, r.totaal]);
resultaatBlad.getRange(2, 1, outputData.length, 2).setValues(outputData);
}
Externe aanroepen parallel uitvoeren
Wanneer je meerdere HTTP-verzoeken moet doen, is UrlFetchApp.fetchAll() veel sneller dan een lus met losse fetch()-aanroepen, omdat de verzoeken parallel lopen.
function haalParallelOp(urls) {
const requests = urls.map(url => ({url, muteHttpExceptions: true}));
const responses = UrlFetchApp.fetchAll(requests);
return responses.map(response => {
if (response.getResponseCode() !== 200) {
return null;
}
return JSON.parse(response.getContentText());
});
}
Uitvoeringstijd meten
function metTijdsmeting(fn, naam) {
const start = Date.now();
const resultaat = fn();
const duur = Date.now() - start;
Logger.log(`${naam}: ${duur}ms`);
return resultaat;
}
function benchmarkVergelijking() {
const blad = SpreadsheetApp.getActiveSheet();
const testData = Array.from({length: 500}, (_, i) => [i, i * 2, i * 3]);
blad.getRange(1, 1, testData.length, 3).setValues(testData);
metTijdsmeting(() => {
let som = 0;
for (let i = 1; i <= 500; i++) {
som += blad.getRange(i, 2).getValue();
}
return som;
}, 'Cel-voor-cel');
metTijdsmeting(() => {
const waarden = blad.getRange(1, 2, 500, 1).getValues();
return waarden.reduce((s, r) => s + r[0], 0);
}, 'Batch');
}
Hoeveel sneller is batch-lezen echt?
In tests is batch-lezen ongeveer 10 tot 100 keer sneller dan cel-voor-cel. Voor 500 rijen duurt cel-voor-cel typisch 8 tot 15 seconden, terwijl batch-lezen vaak onder de halve seconde blijft. De exacte cijfers hangen af van de belasting op de Google-servers, maar de orde van grootte is consistent.
Wanneer gebruik ik SpreadsheetApp.flush()?
Gebruik flush() als je tussentijds UI-feedback wilt tonen tijdens een lange operatie, of wanneer een vervolgaanroep afhankelijk is van wijzigingen die je net hebt weggeschreven. Roep het nooit binnen een lus aan.
Hoe optimaliseer ik externe API-aanroepen?
Gebruik UrlFetchApp.fetchAll() voor parallelle verzoeken, cache antwoorden met CacheService en batch je data zodat je minder verzoeken hoeft te doen.
Kan ik multi-threading gebruiken voor parallelle verwerking?
Nee, Apps Script is single-threaded. Je kunt wel fetchAll() gebruiken voor parallelle HTTP-aanroepen, maar je eigen JavaScript-code wordt altijd sequentieel uitgevoerd.
Hoe voorkom ik timeout-fouten bij grote datasets?
Een uitvoering mag standaard maximaal 6 minuten duren. Splits zware verwerking op in batches en gebruik een time-driven trigger of een doorlopende status in PropertiesService om het werk over meerdere runs te verdelen.
Bestaand script optimaliseren
- Meet de huidige uitvoertijd met tijdsmeting-wrappers.
- Identificeer de hotspot: zoek naar
getValue()ofsetValue()in lussen. - Vervang cel-voor-cel operaties door
getValues()ensetValues(). - Verplaats constante aanroepen zoals
getActiveSpreadsheet()buiten lussen. - Voeg
CacheServicetoe voor herhaalde externe aanroepen. - Meet opnieuw en vergelijk de resultaten.
Performance-optimalisatie in Apps Script komt vrijwel altijd op hetzelfde neer: verminder het aantal service-aanroepen door te batchen. Dit ene principe geeft de grootste verbetering en voorkomt timeout-fouten in productie.