> Raw Markdown twin (generated at build time from the source Markdown). Rendered page: https://docs.gatellm.io/en/practices/script-performance · Doc index: https://docs.gatellm.io/en/llms.txt


# Script performance and memory

Scripts are the gateway's most expressive but also most expensive rewriting approach. This page is the single authority on script performance and memory; the other pages ([Upstream and model fields](/en/reference/upstreams-models-fields.md), [Scripting API reference](/en/reference/scripting-api.md), [Scripting configuration](/en/reference/scripting-config.md), [Write your first script transform](/en/howto/write-script-transform.md)) only point back here.

## Overhead comes from serialization

The script engine (QuickJS-based) receives the JSON body parsed into objects. The overhead mostly comes from JSON serialization/deserialization, whose latency grows linearly with body size.

## Latency reference

| Body size | Typical single-script latency |
|-----------|-----------------|
| < 10 KB | millisecond-level |
| ~100 KB | tens of milliseconds |
| ~1 MB (large multimodal conversation with inline base64) | rises significantly, and consumes memory |

::: tip Don't use scripts for simple rewriting
For simple field add/delete/modify, use `request_payload` declarative rules — they rewrite the request body directly inside the gateway without going through script-engine serialization, giving the best performance. For selection, see [Request rewriting and routing: how to choose](/en/practices/routing-and-transform.md).
:::

## Anti-pattern: using after-slot scripts for pure routing decisions

::: danger Don't use after-slot scripts for pure routing decisions on large bodies
After-slot scripts read the entire body (including inline base64) into script-engine memory, and large multimodal conversations can easily trigger script-engine memory OOM; also, under `script_error_mode = log-and-continue`, the route switch is **silently skipped**.

For these "detect image/web access → switch model" **pure routing decisions**, as long as the target uses the **same protocol** as the current route, use native `switch-route` rules instead — their predicates only do structural/small-value checks and never read binary content bytes, disk spooling stays enabled for large request bodies, and a body containing base64 stays on disk as placeholders + a disk temporary file rather than being read into memory.

Only fall back to scripts when the switch is **cross-protocol**, or the check logic exceeds what "field existence/small-value equality" can express — and put it in the **before slot** to read the client's original format (the before slot runs before protocol translation and has not yet been parsed into the target-protocol body).
:::

## Writing advice

- **Read only the fields you need**: don't `JSON.stringify(body)` the entire body; fetch fields on demand.
- **Return early**: `return body` as soon as the condition fails, to reduce unnecessary processing.
- **Avoid large object copies inside loops**.
- **Streaming response scripts**: process per chunk and avoid accumulating state.

## Choosing the error mode

- `log-and-continue` (default): if the script fails, the original body passes through and the request is not interrupted. Good for non-critical rewriting.
- `log-and-reject`: if the script fails, the request is rejected outright. Use this when compliance requirements are strict (redaction must not leak).

See [Scripting configuration](/en/reference/scripting-config.md) for the fields.

## FAQ

**Q: What if the script times out?**
`SCRIPT_MAX_OPERATIONS` (default 2000) caps the maximum operations per script execution. Exceeding it aborts execution. Infinite loops or overly heavy logic are caught by this.

**Q: A multimodal conversation script OOMs, but I genuinely need to read image content to decide?**
Reading image content to decide inherently loads base64 into memory. Consider turning the check into a structural check (use the `switch-route` `when` predicate to check "is there an image block" rather than reading image content), or divert traffic with a protocol override at the upstream/model level.

**Q: Both before and after slots have scripts configured — what's the execution order?**
Before runs first (and can trigger an immediate `useModel` switch), after runs later. Once before triggers a switch, the original route's after does not execute. See [Scripting API reference](/en/reference/scripting-api.md).

**Next**: [Request rewriting and routing: how to choose](/en/practices/routing-and-transform.md) for selection; [Scripting API reference](/en/reference/scripting-api.md) for the API; [Scripting configuration](/en/reference/scripting-config.md) for configuration.
