SfSkills Domains Docs GitHub

Home / omnistudio / integration-procedure-cacheable-patterns

integration-procedure-cacheable-patterns

Use when designing Integration Procedures (IPs) with platform cache to cut latency and callout load. Covers Cache Block boundary, cache key design, per-user vs org-wide partitions, invalidation on data changes, and safe fallback on cache miss. NOT for general IP authoring or LWC client-side caching — use omnistudio/integration-procedures.

Open the skill folder on GitHub


Integration Procedure Cacheable Patterns

Integration Procedures orchestrate Data Mappers, Apex, and external callouts, and they sit on UI render paths where their latency is directly user-visible. Many of them return mostly-static data that is re-fetched on every page load. Caching cuts that cost by orders of magnitude — but only if the cache scope, key, partition, and invalidation are designed together. This skill covers those four decisions and the failure modes each one produces when it is made by reflex.


Two Caching Surfaces, Not One

An Integration Procedure has two places to configure response caching, and picking between them is the structural decision that reorganises everything else.

Procedure scope — the Cache Configuration section of the Integration Procedure's configuration panel, sitting alongside Chainable Configuration, Queueable Chainable Limits, and Test Configuration. Trailhead describes it verbatim as: "Use a cache to store frequently accessed, infrequently updated Integration Procedure data. This saves round trips to the database and improves performance." It caches the procedure's response as one unit.

Block scope — the Cache Block element, one of the four designer Groups: "Saves the output of the steps within it to a session or org cache for quick retrieval." It caches only the enclosed steps' output, so the boundary can sit anywhere inside the procedure.

Reach forWhen
Cache Configuration (procedure)Every step is a read, the whole response has one audience, and one freshness contract covers all of it.
Cache Block (element)Some steps must run on every call — an audit write, a correlation id, a mutation, a personalized resolution — or different parts of the response have different audiences or TTLs.

Most non-trivial IPs end up on the block, because "every step is a read" is a stronger condition than it sounds. But the procedure-level setting is real, and for a genuinely read-only procedure it is the simpler answer.

The four designer Groups, for orientation:

GroupBehaviour
Cache Block"Saves the output of the steps within it to a session or org cache for quick retrieval"
Conditional Block"Executes if a specified condition is true or treats the steps within it as a series of mutually exclusive alternatives"
Loop Block"Iterates over the items in a data array, repeating the Actions within it for each item"
Try-Catch Block"Lets you try running the steps inside the block and then catch the error if a step fails"

What The Metadata Type Carries — And What That Does Not Prove

The documented caching surface of OmniIntegrationProcedure at API 67.0 is two fields:

FieldMeaning
responseCacheType"Response cache used for the integration procedure (session or Org)"
isMetadataCacheDisabledboolean, default false — a negative name, so metadata caching is already on

There is no TTL field on this type. responseCacheTtlMinutes is real, but it is documented on OmniDataTransform — the Data Mapper type — not here. Transplanting it into IP metadata is the most common wrong answer in this domain.

Read that as a statement about the metadata surface, not about the designer. It means the cache duration is not round-trippable through this metadata type at API 67.0: record it in the design doc and confirm it in the org rather than diffing it out of source control. It is not evidence that the designer lacks a procedure-level cache setting — the Cache Configuration section above is exactly that setting. A metadata type not surfacing a field is a gap in the metadata type.

Three consequences follow from the block being available as a unit of caching:

  1. You can cache part of a procedure — a shared reference lookup inside the

    block, a personalized resolution outside it.

  2. You can have several blocks with different scopes in one IP.
  3. Anything with a per-call requirement must live outside the block. On a

    hit, no step inside it executes. That is the mechanism, and it is how a caching change becomes a missing-audit-trail incident. The identical hazard applies at procedure scope, only wider: a cached response means the whole procedure was skipped, so a procedure-level cache is only correct when nothing in it needed to run.


The Four Decisions

1. Scope, then boundary

Apply one question to every step: must this be true on the ten-thousandth call as well as the first? If no step answers yes, the procedure is a pure read and procedure-level Cache Configuration is the simpler design. If any step answers yes, you need a Cache Block, and that step goes outside it.

Outside the block, alwaysInside the block
DML (Data Mapper Load, Delete)Data Mapper Extract / Transform
Audit and logging writesHTTP Action — GET only
Correlation ids, timestampsRemote Action that is a pure read
Rate-limit and quota countersDecision Matrix / Expression Set evaluation
Consent capture

2. Key

Platform Cache keys must contain alphanumeric characters only — put() throws Cache.InvalidParamException otherwise — and the maximum key size is 50 characters. Colons, equals signs, hyphens, and underscores are all illegal inside the key segment, which catches people out because omniProcessKey has the form Type_SubType.

Shape:  <procKeyCamelCased><schemaVersion><discriminators, FIXED order>
Legal:  productCatalogV4NAUSD
Illegal: ip:Product_Catalog:v4:region=NA:currency=USD

Include every input that changes the result, plus a schema version you control. Exclude correlation ids, timestamps, and — for org cache — anything identifying the caller. Fix the discriminator order explicitly; deriving it from Map iteration order silently halves the hit ratio.

3. Partition

Decide from the payload's audience, never from the desired hit ratio.

Org cacheSession cache
Scopeall callersone user session
TTL range300 s – 172,800 s (48 h), default 86,400 s300 s – 28,800 s (8 h)
Ends whenTTLTTL or session end, whichever first
Use forcatalogs, reference data, no PIIentitlements, personalized results

The question that decides it: is the subject server-resolved (%UserId% from the session) or caller-supplied (an input-map value)? If the caller supplies the inputs the key is built from, the caller chooses which entry to read. On a guest page that is an enumeration primitive, not a coincidence. And Platform Cache documentation states plainly: "Data in the cache isn't encrypted."

4. Invalidation

A TTL is a staleness bound, not an invalidation mechanism.


Cache Is Not Chainable

These read as alternatives and are orthogonal. Caching reduces repeat cost; Chainable reduces single-execution cost. A cold call gets nothing from a cache — and the cold call is the one that breaches the limit.

SymptomLever
Warm calls slowCache Block
Cold call breaches SOQL / CPU / heap / DMLChainable
DML step immediately before an HTTP calloutChain On Step
Long-running, can be asynchronousQueueable Chainable

Chainable thresholds are bounded by the underlying Apex governor limits: synchronous 100 SOQL / 10,000 ms CPU / 6 MB heap / 150 DML; Queueable Chainable 200 SOQL / 60,000 ms CPU / 12 MB heap. When a step exceeds the configured limits, "the interim results are saved and the step continues in a new transaction." Use both: the block so warm calls skip the work, chainable so the cold call survives.


  1. Measure first. Log IP latency (p50, p95), call volume, and the **serialized

    payload size in bytes**. Cacheability is a decision, not a reflex, and the 100 KB per-item ceiling rules some payloads out before design starts.

  2. Pick the scope, then draw the boundary. Walk every step and ask whether it

    must run on every call. No such step anywhere → procedure-level Cache Configuration. Any such step → a Cache Block with that step outside it. This is the decision with the worst failure mode, so make it before the ones that feel more technical.

  3. Pick the partition from the payload's audience. Org cache only for payloads

    identical for every caller and free of PII; session cache with a server-resolved subject for anything personal; no cache for guest-reachable PII.

  4. Build the key in one helper: alphanumeric only, ≤ 50 characters, fixed

    discriminator order, schema-version prefix retained for purging. Unit-test that two differently-ordered input maps produce the same key.

  5. Choose a TTL inside the platform range — 5 min to 48 h (org), 5 min to 8 h

    (session). If the freshness contract is tighter than 5 minutes, record the decision not to cache and stop.

  6. Design invalidation before shipping: version bump for shape changes, purge

    path for value emergencies. Answer the question "a wrong value ships at 09:00 — what makes the fix visible before the TTL expires?"

  7. Make the miss path structurally safe. Null-check getPartition(), treat a

    null get() as routine, and confirm no user-visible error can originate in the cache layer. Then capacity-plan for the cold-start load after a full eviction, not for the steady-state hit ratio.


Review Checklist


Worked Examples (see references/examples.md)

Common Gotchas (see references/gotchas.md)

Top LLM Anti-Patterns (full list in references/llm-anti-patterns.md)


Official Sources Used

See references/well-architected.md for the full source list with the specific claim each source grounds.

Package files