Home / omnistudio / dataraptor-transform-optimization
dataraptor-transform-optimization
Use when DataRaptor Transform operations are slow, hit governor limits, or use Apex where formula fields would suffice. Covers formula vs Apex expressions, bulk transform sizing, and chained transform composition. Triggers: 'dataraptor transform slow'. NOT for DataRaptor Extract or Load performance — use omnistudio/dataraptor-patterns.
Open the skill folder on GitHub
DataRaptor Transform Optimization
First: which runtime, and which name
Before touching anything, settle two questions. Getting either wrong makes every subsequent recommendation unreliable, and both are invisible in the artifact itself.
The name changed. Salesforce renamed DataRaptor to Omnistudio Data Mapper. Salesforce Help now carries two parallel trees — "Omnistudio Data Mappers (Managed Package)" and "Omnistudio Data Mappers" — and they describe different runtimes. This skill keeps dataraptor in its name because that is what practitioners still type; everything below applies to a Data Mapper of type Transform.
There are two runtimes. The managed package runtime (the old Vlocity lineage, a custom data model, vlocity_* namespaces) and the standard runtime (Salesforce standard objects and standard APIs). Migration between them is a documented, three-phase process now assisted by the Omnistudio Migration Assistant. A great deal of what is "known" about DataRaptors — including most Apex invocation snippets in circulation — describes the managed package only.
| Concern | Managed package runtime | Standard runtime |
|---|---|---|
| Apex invocation | vlocity_ins.DRGlobal.processObjectsJSON() | ConnectApi.OmniDesignerConnect.executeDataMapper(bundleName, apexInput) |
| Calling Apex from a formula | Function Definition + VlocityOpenInterface | FUNCTION() + Callable |
| Deployment | DataPacks | Metadata API, once the Omnistudio Metadata setting is enabled |
| Storage | Managed-package custom objects | OmniDataTransform / OmniDataTransformItem standard objects |
The Apex row is not cosmetic. Salesforce's documentation for ConnectApi.OmniDesignerConnect.executeDataMapper states that it "replaces the vlocity_ins.DRGlobal.processObjectsJSON() method", that "This Connect API removes the dependency on the managed package", and claims "up to 60% better performance for Data Mapper calls from an Apex class compared to the previous method". If your Transform is invoked from Apex on the standard runtime and the call site still uses the managed-package method, that is the single largest verified optimization available here, and it is a one-line change.
What a Transform actually is
"Transform—Perform intermediate data transformations without reading from or writing to Salesforce."
That sentence is the whole performance model. A Transform issues no SOQL and no DML of its own. It restructures data it was handed. So when a Transform is slow, the cost is in one of exactly three places:
- The evaluator — formulas and custom functions run per mapping, per row.
- The materialization — each Transform in a chain produces a full
intermediate structure in heap before the next one starts.
- The context it inherits — a Transform inside a synchronous Integration
Procedure spends the caller's CPU-time and heap allocation, not its own.
Point 3 is why "the Transform is slow" is so often the wrong diagnosis. It is frequently the last thing added to a transaction that was already at 9,000 ms.
Scope. This skill owns Transform-type Data Mappers. Extract, Turbo Extract, and Load performance — where SOQL, DML, and batch sizing dominate — is omnistudio/dataraptor-patterns and omnistudio/dataraptor-load-and-extract. Caching is omnistudio/integration-procedure-cacheable-patterns and omnistudio/omnistudio-cache-strategies.
Before Starting
- Establish the runtime. Managed package or standard. Everything else
branches on it, and the answer is not visible in the Transform.
- Get a real profile, not a feeling. Capture the Integration Procedure's
own timing output and an Apex debug log for the same execution. You want the Transform's wall-clock share and the transaction's CPU total, because those two numbers ask different questions.
- Measure at production shape. A Transform over 3 rows and one over 3,000
are different programs. Take the row count and the field count from production, not from the designer's preview.
- Establish who consumes the output. Half of Transform optimization is
deleting mappings nothing reads. You cannot do that without knowing the consumers.
- Read the chain before the Transform. If the payload arriving at the
Transform is ten times larger than the fields it uses, the fix is upstream projection, not anything inside this artifact.
Core Concepts
The evaluator hierarchy
| Evaluator | Runs where | Costs |
|---|---|---|
| Direct mapping (Input JSON Path → Output JSON Path) | Transform engine | Cheapest — no expression evaluated |
| Transform Map Values (key-value substitution) | Transform engine | Near-free; a lookup, not an expression |
| Formula (Formulas tab, result to Formula Result Path) | Transform engine | Expression evaluation per row |
| Custom function calling Apex | Apex | Apex CPU, and any SOQL or DML that Apex does — against the caller's limits |
The ordering is the guidance: prefer a direct mapping, then a map-values substitution, then a formula, and reach for Apex only when nothing above can express the requirement. Each step down that list is a larger constant per row and a larger blast radius when the row count grows.
Note what is not in that table: this skill makes no claim about a JavaScript evaluator inside a Transform. The documented extension point for custom logic is Apex — FUNCTION() plus Callable on the standard runtime, a Function Definition plus VlocityOpenInterface on the managed package.
Lists are where Transforms earn their existence, and where they go wrong
The three canonical Transform problems in Salesforce's own examples are all list reshaping: converting a single item to a one-item list, converting a list of objects to a list of values, and changing the hierarchical level of a list.
Two documented behaviours matter for both correctness and performance:
- The output data type has to be right. "Make sure that the output data type for
your mapping is set to
List<Map>." - When several input lists map to one output list, **ordering is not
guaranteed**: "the system does not guarantee the order of the items in the output". Salesforce's own remedy is to "use a formula to combine and filter the lists instead of using direct mappings" — for example
LIST(inputList1, inputList2, inputListNew).
That second one is the trap. A direct multi-list mapping is cheaper and wrong; a LIST() formula is slightly more expensive and deterministic. This is one of the few places in this skill where the right answer costs more, and taking the cheap one produces a bug that appears only under load, when the ordering that happened to be stable in the sandbox stops being stable.
Chained Transforms materialize
Every Transform in a chain builds its complete output structure before the next step reads it. Three chained Transforms over a 2,000-row payload hold three full copies in heap at their peak — against 6 MB synchronously or 12 MB asynchronously. Merging two adjacent Transforms removes an entire copy, and it is usually the largest single win available on a chain that is failing with a heap error rather than a CPU error.
Read the error before choosing the fix: heap and CPU failures look identical from the outside and have opposite remedies. A CPU failure wants fewer or cheaper expressions; a heap failure wants fewer materializations and a smaller payload.
The Transform inherits the caller's limits
A Transform inside a synchronous Integration Procedure runs inside the calling Apex transaction and draws on its allocation:
The synchronous vs asynchronous governor ceilings that bound a Transform are tabulated in