What Changed in Cold Start Behaviour When Cloud Functions Moved to Gen2
9 min read · updated August 11, 2026
The second generation of Cloud Functions is not a faster version of the first. It is a different product underneath: a function is deployed as a Cloud Run service. Almost every difference in cold start behaviour follows from that one substitution, and the largest of them is not about startup speed at all.
What gen2 actually is
Google’s version comparison describes a 2nd gen function as a Cloud Run service deployed from source code, where a 1st gen function was deployed onto Google’s own function infrastructure. Your source is still built into a container by the platform; what changed is what runs it. Google’s comparison page is the authority on the differences below.
The documented deltas, at the time of writing: concurrency of up to 1,000 concurrent requests per instance against exactly 1 on gen1; request timeouts of up to 60 minutes for HTTP functions and 9 minutes for event-driven ones, against a 9-minute maximum on gen1; up to 16 GiB of memory and 4 vCPU against 8 GB and 2 vCPU; traffic splitting, which gen1 did not support; and events from any Eventarc source — Google cites more than 90 — against direct support for seven.
Concurrency is the change that matters
A gen1 function handled exactly one request per instance. That single constraint decides the cold start story, because it fixes the number of instances: serving 50 simultaneous requests requires 50 instances, and every instance that did not already exist is a cold start. The instances are also mostly idle in wall-clock terms — a function that spends 2 seconds awaiting a model API and 10 milliseconds computing holds an entire instance for the full 2 seconds.
With concurrency above 1, the same 50 simultaneous requests can be served by a handful of instances. Nothing about how long an individual instance takes to start has to change for the user-visible cold start rate to fall sharply, because the number of starts fell. This is worth stating precisely, because it is the claim that is defensible: gen2 reduces cold start exposure. Whether a single instance initializes faster than a gen1 instance did is a much harder claim and not one Google’s comparison page makes.
The trade is real and points the other way in one respect. When concurrency is 1, a cold start delays one request. When concurrency is 80, the requests that arrive while an instance is starting queue behind the same start, so a cold start now delays a batch. Total cold starts go down; the blast radius of each one goes up. For a function calling a model API this is usually a good trade, because the requests are mostly waiting anyway, but it explains why a p99 can look worse after a migration that improved the p50.
Concurrency also changes what your code must be. One request per instance let people use module-level variables as if they were request-scoped, and get away with it. With concurrency above 1, a module-level accumulator, a mutable client configuration, or anything holding per-request state is now shared between concurrent requests. Setting the concurrency flag is a correctness change to the handler, not just a scaling setting.
The knobs gen1 did not have
Because a gen2 function is a Cloud Run service, the Cloud Run mitigations apply to it, and this is the practical benefit of the architecture change.
- Minimum instances. Keep instances alive so requests after idle do not pay a start. This is the only setting that removes the cold start rather than reducing how often it happens.
- Startup CPU boost. Extra CPU during startup and briefly after, which attacks the initialization phase directly rather than the scheduling of it.
- Concurrency. As above, with
--concurrencyon deploy. - Traffic splitting. Revisions can take a percentage of traffic, which makes a change to any of the above testable against real traffic instead of deployed and hoped for.
- Image streaming. Inherited from Cloud Run, with the consequence that image size does not drive startup time the way it does on platforms that pull before starting.
gcloud functions deploy summarise \ --gen2 \ --region=europe-west1 \ --runtime=python312 \ --entry-point=handler \ --trigger-http \ --min-instances=1 \ --concurrency=20 \ --memory=1Gi \ --timeout=120s
What did not get better
Three things, and they are the ones people are surprised by.
Scale to zero still exists. Without a minimum instance count, an idle gen2 function still ends up with no instances and the next request still pays a start. The architecture changed the frequency, not the existence.
Your initialization code is unchanged. If the function spends 1.5 seconds importing libraries and building a client, it spends that on gen2 too. Nothing about being a Cloud Run service makes an import graph smaller. This is where most remaining cold start time actually is, and it is the part you own.
Minimum instances cost money. A minimum of one means one instance billed continuously. For a function invoked a hundred times a day that is a real decision, not a free improvement — and it is the same decision, in a different console, as always-ready instances on an Azure Premium plan.
What to check when you move
A migration is not a redeploy with a flag, because the runtime contract differs in ways that are silent until traffic arrives.
- Audit module-level state for anything that is per-request in disguise, then set
--concurrencydeliberately rather than taking the default. Start low and raise it while watching memory, because concurrent requests share the instance’s memory limit. - Re-check the timeout. HTTP functions can now run far longer, which is useful for a long generation, but a longer timeout also means a wedged request occupies a concurrency slot for longer.
- Re-check the trigger. Event-driven gen2 functions go through Eventarc, so the event payload envelope and the IAM identity that delivers it are not identical to gen1’s direct triggers.
- Deploy with traffic splitting and give the new revision a small percentage first. This is the capability gen1 lacked, and it is worth using precisely on the change that alters concurrency semantics.