I've been working with Azure Functions for a while now, and the v4 runtime that reached GA with .NET 6 is a significant improvement. The cold start problem that plagued .NET Functions in previous runtime versions has been largely addressed, making it more viable for production use.

The v4 isolated process model is a major factor in this improvement. By running function code in its own process, separate from the Functions host, Azure Functions v4 eliminates the dependency version conflicts that were a problem in the in-process model. This also enables support for future .NET versions independent of the Functions runtime.

But the isolated model isn't without trade-offs. Deployment package sizes typically increase by 15-30% compared to the in-process model due to the need to include additional runtime components. This can slightly offset cold start gains if the deployment is not optimized with trimming or AOT compilation. We've also seen cases where teams over-allocated dependencies in the isolated process, leading to memory bloat and slower startup times in early 2022 deployments.

So, how do you reduce cold starts in .NET Azure Functions? If you're on the Premium plan, pre-warmed instances can eliminate cold starts entirely, which is ideal for latency-sensitive functions. Alternatively, native AOT compilation for .NET 8 Functions can reduce cold start times from 1-2 seconds to under 100ms. For functions on the Consumption plan, the Flex Consumption plan, announced in preview for 2024, provides per-instance scaling with faster warm-up. However, in 2022, AOT for .NET 6 was still in preview, and we observed 15-20% slower cold starts in some AOT-compiled functions due to incomplete reflection data at runtime.

Durable Functions is another key feature that provides stateful orchestrations on top of Azure Functions. This allows you to express long-running workflows, such as human approval steps or external system polling, as orchestrator functions that are automatically checkpointed. The programming model makes complex workflows more readable, and the storage backend, whether it's Azure Storage or the newer Microsoft SQL Server backend, serialises and replays orchestration state.

But when is Azure Functions not the right choice? If you have latency-sensitive APIs where cold starts are unacceptable on the Consumption plan, or if you need to run long-running CPU-intensive tasks, Azure Functions might not be the best fit. Additionally, if you have workloads with consistent high traffic, the per-invocation pricing of the Consumption plan might exceed the cost of always-on container instances.

The use case fit for serverless is really event-driven, irregular workloads. If your workload is consistent and predictable, you might be better off with a different architecture. But if you have workloads that are driven by external events, such as changes to a database or messages from a queue, Azure Functions can be a great choice.

I've seen this play out in real-world scenarios, where Azure Functions has been used to handle event-driven workloads, such as processing messages from a queue or responding to changes in a database. In these cases, the ability to scale up and down quickly, without having to worry about provisioning or managing servers, has been a major benefit.

Overall, Azure Functions v4 with .NET 6 is a solid choice for building serverless applications, especially when combined with Durable Functions for orchestration. By understanding the strengths and limitations of Azure Functions, you can make informed decisions about when to use it, and how to get the most out of it.

As I work with more customers who are adopting Azure Functions, I'm seeing a common pattern emerge. They're using Azure Functions to handle the event-driven workloads, and then using other services, such as Azure Kubernetes Service, to handle the more predictable, long-running workloads. This hybrid approach allows them to get the best of both worlds, and to take advantage of the strengths of each service.