# Performance optimaliseren in Apps Script [[TOC]] ## 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() ```javascript 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() ```javascript 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); } ``` :::warn title="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. ```javascript 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; } ``` :::tip title="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 ```javascript 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 ```javascript 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. ```javascript 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 ```javascript 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'); } ``` :::faq ### 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. ::: :::howto title="Bestaand script optimaliseren" 1. Meet de huidige uitvoertijd met tijdsmeting-wrappers. 2. Identificeer de hotspot: zoek naar `getValue()` of `setValue()` in lussen. 3. Vervang cel-voor-cel operaties door `getValues()` en `setValues()`. 4. Verplaats constante aanroepen zoals `getActiveSpreadsheet()` buiten lussen. 5. Voeg `CacheService` toe voor herhaalde externe aanroepen. 6. 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.