Skip to main content

Staying Within Usage Limits

Your contract with Prismatic outlines fair use limits, typically measured in gigabyte-seconds of compute per instance per month. In this article we cover how we compute usage, and provide some recommendations we have for building efficient integrations.

How usage is measured

When an instance's flow is invoked (either by webhook, schedule, via AI/MCP, or manually), the amount of time the flow takes to run is noted. At the end of the month, the total number of seconds an instance ran is multiplied by the amount of memory allocated to the instance to compute the instance's total gigabyte-seconds (GB-seconds). By default, an instance is allocated with 1GB of memory (though, you can allocate more as needed).

Here are a couple of examples:

Example 1: An instance with the default instance profile receives an average of 2000 webhook requests each day. It takes about 3 seconds to process the request, transform the data, and send the data upstream. The instance consumes about 180,000 GB-s of compute each month.

2000 executions/day x 3 seconds x 30 days = 180,000 GB-s

Example 2: An instance runs on a schedule four times per day. It fetches a large CSV file, compares its contents to an upstream system, and syncs data upstream. Due to the size of the datasets, it runs with 8GB of memory to avoid OOM errors. On average, the flow takes 5 minutes (300 seconds) to run. The instance consumes about 288,000 GB-s of compute each month.

4 executions/day x 300 seconds x 30 days x 8 GB = 288,000 GB-s

Find where usage is going

You can view your total execution compute usage by clicking your organization name on the bottom left of the web app, and then navigating to the Utilization tab.

You can find similar graphs for specific instances by navigating to an instance and opening the instance's Utilization tab.

If you'd like a regular report of instance compute usage, you can query instanceDailyUsageMetrics in the API to rank instances by spendMbSecs. An example of how to page over records and pull that data is available in our examples repo in GitHub.

Once you've found your most compute-intensive instances, examine some recent executions. Logs display the number of seconds each step took. Are there specific steps that take longer than they seem they should (e.g. a step that makes an HTTP request takes 30 seconds, etc).

Building compute-efficient flows

Building compute-efficient workflows is critical for two reasons:

  1. They ensure you stay within fair use limits
  2. Data syncs faster between your app and the other apps or services your customers use

Prefer webhooks over polling

A polling trigger runs an execution every time it checks for changes, whether or not anything changed. Even though an execution that finds no new data stops immediately, you still pay for the runner to start up and query the third-party API.

An instance polling every five minutes runs 8,640 times per month. At two seconds per check, that is over 17,000 GB-s per instance, per month - spent entirely on finding nothing:

288 executions/day x 2 seconds x 30 days = 17,280 GB-s

A webhook-based app event trigger only runs when data actually changes. Many connectors register and tear down webhooks automatically when an instance is deployed or removed, so there is no extra work for your customers. If a connector does not support webhooks, you can build the subscription logic into a deploy flow or a custom trigger.

If polling is your only option, widen the interval and consider whether you need to poll around the clock. A cron schedule of */15 8-17 * * 1-5 costs about a tenth of a five-minute round-the-clock schedule.

Measure your empty polls

On an executions screen, click Filter and select Exclude executions without trigger-detected changes to see how many of your polling executions found work to do.

Subscribe only to webhook events you care about

When you do subscribe to webhooks, subscribe only to the events you care about. Most apps let you scope a subscription by event type and object type. Subscribing to every object change when you only need closed opportunities can multiply your executions many times over.

Poll diffs over entire data sets

When you fetch data on a schedule, request only the records that changed since your last run rather than re-fetching everything.

Save a cursor - usually an updatedAt timestamp or the ID of the last record you processed - to flow state at the end of each execution, and use it to filter your next request. Built-in polling triggers maintain this cursor for you.

Push the filter into the API request itself (updated_since=, a SOQL WHERE clause, an OData $filter) rather than fetching everything and discarding most of it in a code step. Fetching 50,000 records to find the 40 that changed costs you the full download, the memory to hold it, and the time to process it.

Consider a flow that syncs a 50,000-record dataset four times a day. A full sync takes four minutes; an incremental sync of a few dozen records takes five seconds:

Full sync: 4 executions/day x 240 seconds x 30 days = 28,800 GB-s
Incremental sync: 4 executions/day x 5 seconds x 30 days = 600 GB-s

While you are at it, request only the fields you need. Smaller payloads mean less memory, shorter executions, and smaller step results to serialize.

For the full pattern - an initial backfill followed by incremental updates - see data sync guidelines and large data syncs.

Enable singleton executions

If a scheduled or polling flow takes longer to run than its schedule interval, a second execution starts while the first is still running.

That is expensive, because your cursor is not saved until an execution finishes. A flow that runs every five minutes but takes ten minutes to complete will start a second execution that fetches and processes the exact same records as the first. You pay twice for the same work, and you risk writing duplicate data upstream.

Enable singleton executions on your scheduled and polling triggers so that a new execution is skipped if one is already running. In a code-native integration, see enabling singleton executions for code-native flows.

For webhook-based flows, use flow concurrency to throttle or serialize executions instead.

Use memory efficiently when handling large files

Memory allocation multiplies the cost of an entire execution, not just the steps that need the memory. If you raise an instance to 8 GB so that one step can deserialize a large file, you pay 8 GB for every second of the run:

1 GB instance, 5-minute execution: 300 seconds x 1 GB = 300 GB-s
8 GB instance, 5-minute execution: 300 seconds x 8 GB = 2,400 GB-s

So it is usually cheaper to reduce how much memory your flow needs than to allocate more of it.

Large files are the most common cause of memory pressure. An 80 MB CSV file can consume well over 1 GB by the time it has been downloaded as a buffer, converted to a string, persisted as a step result, and deserialized into JavaScript objects. Rather than holding the whole file in memory, process it as a Node.js stream so that you load, process, and discard one chunk at a time. See handling large files for additional patterns.

A few other habits keep memory - and therefore cost - down:

  • Avoid oversized step results. A loop returns an array containing its final step's result from every iteration, so a 50 KB result across 10,000 iterations produces a 500 MB step result. End the loop with a code step that returns { data: null }.
  • Keep logging proportionate. Thousands of log lines inside a loop force the logger to serialize and commit each one. Log a summary per batch instead of a line per record.
  • Store cursors, not documents. Keep persisted data small - record IDs and timestamps rather than entire payloads.

Memory management covers these scenarios in more detail, and you can enable debug mode to see memory usage after each step.

Leverage bulk APIs if available

When you write records one at a time, network latency dominates your execution time. Five thousand records at 200 ms per request is nearly 17 minutes of compute - long enough to exceed the 15-minute execution limit as well. Sending the same records in batches of 500 takes ten requests and a few seconds.

Many APIs offer some form of bulk endpoint - POST /records/batch, Salesforce's Bulk API, a multi-row SQL insert. Prefer them over a loop that makes one call per record.

The same applies when reading. Request the largest page size the API allows so that you page through a dataset in fewer round trips.

If no bulk endpoint exists, processing data in parallel within a single execution reduces the wall-clock time you spend waiting on the network, and therefore your GB-s.

Parallelism across flows shortens runs, but does not reduce compute

Fanning work out to sibling flows with the cross-flow trigger runs several executions at once, each with its own memory allocation. That helps you stay under the 15-minute execution limit, but the total GB-s stays roughly the same.

Avoid sleeping within executions

An execution that sleeps costs exactly as much as an execution that completes work. The Sleep component is convenient for waiting on a third-party job to finish, but polling a job's status every ten seconds for five minutes costs 300 GB-s per execution and produces nothing:

100 executions/day x 300 seconds x 30 days = 900,000 GB-s

Instead, end the execution and let something else restart the work:

  • Have the third-party app tell you when it is done. Many APIs that kick off long-running jobs accept a callback URL. Point it at a webhook trigger on a second flow that processes the finished result.
  • Check status on a schedule. Write the pending job ID to cross-flow state, and have a scheduled flow check outstanding jobs periodically and process the ones that have finished.
  • Throttle outside the execution, not inside it. If you are sleeping to stay under a third-party rate limit, use flow concurrency instead. Queued requests wait outside of an execution, so you are not billed while they sit in line.