Memory and concurrency
Every function books memory in the slice's shape,
from 8MB to 256MB, and there is no default. That one number does three
jobs: it is what the function is billed at, it is the memory its invocations may use, and it is
what decides how many of them run at once. Most of this page is about the third,
because it is the one that surprises people.
A compiled function floors at 16MB, not 8MB.
16MB is refused at deploy rather than served badly, because the reservation clamps to
the pool instead of failing cleanly: single requests would return 200 and the slice
would only give way under concurrency. See what it costs for what each
floor prices at.
The booking is a pool, not a per-call cap
It would be reasonable to read a booking of 64 MB as "each call may use 64 MB". It does not mean that. The booking is a pool the function's concurrent calls share: while calls are in flight, what they hold together stays inside it.
concurrency = what you booked / what one call actually costsSo a function measured at 8 MB per call runs eight-up inside a 64 MB booking, and a function measured at 30 MB runs two-up inside the same booking. Booking more buys both headroom per call and more calls at once, from one dial. Nothing else needs configuring, and there is no separate concurrency setting to keep in step with it.
This is why the cheapest tier is a real choice and not a trap.
When the pool is full
A new call that will not fit is refused with 503 and
Retry-After: 1. It clears on its own as calls finish, and the response carries
X-Drift-Reason: FUNCTION-MEMORY so your own logging can tell it apart from anything
else that answers 503.
It is not a 429, and the difference is the point. A 429
means the caller sent more than they were allowed and the remedy is theirs. This is the opposite:
the caller did nothing wrong, and the remedy is yours — book more memory, which is also more
concurrency. Those two point at different knobs, so they carry different codes.
Work is refused at the door. A call that is admitted has room; the platform does not accept work it has no space for and discover the problem halfway through.
When one call outgrows the booking
Estimates are estimates, so occasionally a call that was admitted grows past what its function
booked. That single invocation is stopped and its caller gets 500 with
X-Drift-Reason: EVICTED. Everything running beside it keeps going —
the other calls to that function, every other function, and the rest of the slice.
That containment is the entire reason it works this way. The alternative is letting one call keep growing until something larger runs out of memory, at which point everything in flight dies together and one bad request becomes an outage. One failed request is a much better outcome, and it is the one you get.
An eviction usually means the booking is too small, not that the code is wrong.
memory, and your function's log says so at the moment it happens —
naming the function and the figure. It carries no Retry-After, because retrying the same
request against the same booking fails the same way.
Your process may be reused
Starting a process is not free: sandboxing it and initialising its language runtime costs about 2.5 milliseconds of CPU before a line of your code runs. Paying that on every request is most of what a compiled function costs, so Drift keeps the process alive and sends it the next call.
So a function can see state it left behind. A package-level variable, a cached client, a connection you opened last call — those survive into the next invocation of the same function. That is usually what you want, and it is why a database handle is worth keeping in a global. It also means anything you leave in a global is still there next time, so do not use one to hold a single request's data.
The bound is fixed, and it is part of the contract rather than an implementation detail:
- A process serves at most 200 invocations, then it is retired and the next call gets a fresh one.
- A process idle for 60 seconds is retired, so a function that stops being called stops holding memory.
- It is retired immediately when you deploy, so a call never reaches the previous version of your code.
What is never shared: one function's process is never given to another
function, and never to another tenant. Your scratch directory is still created fresh for
every single call and removed after it, so os.TempDir() is private to one
invocation even though the process is not. Nothing about the sandbox changes — the
same restrictions apply on call 200 as on call 1.
Today process reuse applies to Go; Rust, Ruby and PHP still get a fresh process per call. Python and Node are reused more broadly, and have been since they launched: one interpreter serves every function of that language in your slice, so two of your Python functions can see each other's module state. Still only ever your own slice — never another tenant's.
Picking the number
Guessing high wastes money and guessing low means calls are refused under load, and neither is discoverable by reading your own source. So don't guess — the platform has been measuring it all along:
drift file benchmark prints what every function in your slice has cost and the
size it should book — the high-water mark per function, what it currently books, what it
should book instead, and the concurrency that booking is buying you today. Add
--apply and it opens the slice's shape
with each measured recommendation already filled in, so the number that gets bought is the one
that was measured. Nothing is bought until you submit the form.
It also reports how many calls each figure is based on, which is the number to look at first. A recommendation backed by three invocations of one code path is not the same claim as one backed by thousands, and a function nobody has called yet says so rather than reporting a confidently small number.
Any whole number of MB in range is legal. If you measured 20 MB, book 20 — there is no grid to round up to, because rounding up would bill you for memory you never asked for.
One more 503
A slice under overall memory pressure sheds new work briefly with 503 and
Retry-After: 5, carrying X-Drift-Reason: SLICE-MEMORY. It clears by
itself and needs nothing from you; it is listed here so that your logs can tell it apart from a
function that has filled its own pool, which does need something from you.
Every code and header on this page is collected in one table on Limits and alerts, alongside timeouts and rate limits.