Article Type: Troubleshooting
Audience: Application Designers, Developers, Solution Architects
Module: Data Flows, Script Editor
Applies to Versions: Fuuz 3.0+
Data transformation operations in Fuuz can experience significant performance degradation when processing large datasets. This guide helps developers identify performance bottlenecks in JSONata transforms and provides optimization strategies, including migration to JavaScript when necessary.
The following benchmarks demonstrate performance differences between implementation approaches, tested with a workcenter history aggregation transform processing 2,343 records with complex grouping, duration calculations, and multi-level aggregations.
| Implementation | Fuuz Execution Time | Speedup vs Original | Use Case |
|---|---|---|---|
| JSONata (Original) | ~70 seconds | Baseline | Not recommended for large datasets |
| JSONata (Optimized V2) | ~19 seconds | 3.7x faster | Moderate datasets (<5,000 records) |
| JavaScript | ~1-3 seconds | 23-70x faster | Large datasets, complex logic |
| Issue | Cause | Fix |
|---|---|---|
| Exponential slowdown with record count | O(n²) algorithm pattern - nested $filter inside $map |
Use index arithmetic or pre-sort data for O(1) lookups |
| Slow date/time operations | Repeated $toMillis() calls on same values |
Cache millisecond values in variables, compute once per record |
| Repeated filtering of same data | Filtering by modeId, category, etc. multiple times | Pre-filter into separate arrays once, reuse throughout |
| Complex week number calculation | Computing January 1st timestamp for every record | Pre-compute year boundaries, use lookup map |
| Transform fundamentally too slow | JSONata interpreter overhead for large datasets | Migrate to JavaScript Script Node for 20-70x improvement |
Before optimizing, identify the root cause of slow performance:
$filter or $map nested inside another $map or loopExample O(n²) Anti-Pattern:
/* SLOW - O(n²): For each record, filters entire array */
$map($records, function($rec, $idx) {(
$nextRec := $filter($records, function($r, $i) {
$i > $idx and $r.workcenterId = $rec.workcenterId
})[0];
/* ... */
)})
Optimized O(n) Solution:
/* FAST - O(n): Sort once, use index arithmetic */
$sorted := $records^(workcenterId, occurAt);
$len := $count($sorted);
$map($sorted, function($rec, $idx) {(
$next := $idx < $len - 1 ? $sorted[$idx + 1] : null;
$sameWC := $next != null and $next.workcenterId = $rec.workcenterId;
/* ... */
)})
Apply these optimizations to improve JSONata transform performance:
1. Pre-sort and use index arithmetic for lookups:
/* Sort once at the beginning */
$sorted := workcenterHistory^(workcenterId, occurAt);
$len := $count($sorted);
/* Use index to access next record - O(1) instead of O(n) */
$enriched := $sorted#$idx.(
$next := $idx < $len - 1 ? $sorted[$idx + 1] : null;
/* Process record */
)
2. Pre-filter data once, reuse throughout:
/* Filter once at the start */
$downtimeRecords := $enriched[modeId = "disabled"];
$prodRecs := $enriched[modeId = "Production"];
$idleRecs := $enriched[modeId = "Idle"];
/* Reuse filtered arrays throughout transform */
$dtTotal := $sum($downtimeRecords.calculatedDuration);
3. Cache expensive calculations:
/* Cache milliseconds value */
$ms := $toMillis(occurAt);
$yr := $substring(occurAt, 0, 4);
$jan1ms := $toMillis($yr & "-01-01");
/* Reuse cached values */
$dayOfYear := $floor(($ms - $jan1ms) / 86400000) + 1;
4. Use $merge with $each for grouping operations:
/* Efficient grouping with $each */
$byMonth := $each($enriched{month: $}, function($recs, $period) {
$merge([{"period": $period}, $aggStats($recs)])
})^(period);
When JSONata optimization is insufficient, migrate to a JavaScript Script Node:
JavaScript Script Node Syntax:
/* Access input data via $ variable */
const records = $.workcenterHistory;
/* Use native JavaScript array methods */
const sorted = [...records].sort((a, b) => {
const wcCompare = a.workcenterId.localeCompare(b.workcenterId);
return wcCompare !== 0 ? wcCompare : a.occurAt.localeCompare(b.occurAt);
});
/* Process with forEach, map, reduce */
sorted.forEach((rec, idx) => {
const next = idx < sorted.length - 1 ? sorted[idx + 1] : null;
// Process record
});
/* Return result object */
return {
summary: { /* ... */ },
aggregations: { /* ... */ }
};
$ variable.
Use this guide to select the appropriate technology for your transform:
| Criteria | JSONata | JavaScript |
|---|---|---|
| Record Count | < 2,000 records | > 2,000 records or performance-critical |
| Transform Complexity | Simple mappings, basic aggregations | Complex logic, nested loops, multi-pass processing |
| Development Speed | Faster for simple transforms | Requires more code but more flexible |
| Debugging | Built-in Script Editor preview | Console logging, standard debugging |
| Maintainability | Declarative, self-documenting | Requires comments, familiar to developers |
| Performance Target | > 5 seconds acceptable | < 5 seconds required |
^(field1, field2) at the start and index arithmetic for lookups$toMillis() results in variablesforEach, map, reduce, filter are highly optimizednew Set(array.map(x => x.field)).size for counting distinct values$, not payloadContact Fuuz Support if:
When escalating, provide:
The following sample scripts demonstrate the optimization techniques described in this article:
| Version | Date | Editor | Description |
|---|---|---|---|
| 1.0 | 2026-01-01 | Craig Scott | Initial Release - Transform performance optimization guide with JSONata and JavaScript examples |