{'fraction_non_alphanumeric': 0.02435226706527155, 'fraction_numerical': 0.006290483308420528, 'mean_word_length': 4.993654348637551, 'pattern_counts': {'":': 0, '<': 0, '<?xml version=': 0, '>': 0, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
Mitigating Cold Starts in Serverless Platforms: A Pool-Based Approach

Rapid adoption of the serverless (or Function-as-a-Service, FaaS) paradigm, pioneered by Amazon with AWS Lambda and followed by numerous commercial offerings and open source projects, introduces new challenges in designing the cloud infrastructure, balancing between performance and cost. While instant per-request elasticity that FaaS platforms typically offer application developers makes it possible to achieve high performance of bursty workloads without over-provisioning, such elasticity often involves extra latency associated with on-demand provisioning of individual runtime containers that serve the functions. This phenomenon is often called cold starts, as opposed to the situation when a function is served by a pre-provisioned"warm"container, ready to serve requests with close to zero overhead. Providers are constantly working on techniques aimed at reducing cold starts. A common approach to reduce cold starts is to maintain a pool of warm containers, in anticipation of future requests. In this report, we address the cold start problem in serverless architectures, specifically under the Knative Serving FaaS platform. We describe our implementation leveraging a pool of function instances, and evaluate the latency compared to the original implementation, resulting in a 85% reduction of P99 response time for a single instance pool.


INTRODUCTION 1.Serverless Platforms
Serverless (also known as Function-as-a-Service, FaaS) is an emerging paradigm of cloud computing that allows developers to abstract away underlying infrastructure down to the "function" level of an application. Compared to traditional cloud offerings such as Platform-as-a-Service (PaaS), serverless allows the developer to focus on the functionality of the service itself without worrying about any environment and system issues, such as scaling and fault tolerance. Most cloud service providers now support some degree of the serverless architecture, e.g. AWS Lambda [1], Google Cloud Functions [4] etc., and various open source frameworks are released, including IBM incubated OpenWhisk [2] and Knative [5]. The back-end of these services vary from existing generic container technology [2, 5] to highly customized virtualization methods [3], but all aim to provide a simple-to-deploy model for developers.

Knative and Knative Serving
Knative [5] is an open source initiative aiming to provide a platform for developing container applications on top of the Kubernetes container orchestration platform. It offers ready-to-use components built with Kubernetes Custom Resource Definitions (CRDs) for developers to build, deploy and scale their functions end-to-end from source code. The project currently provides three components: Figure 1: Knative Control Flow, image obtained from the Knative/Serving repository [6] (1) Build an end-to-end source-to-URL deployment tool for container images.
(2) Eventing management and delivery of events between containers.
(3) Serving a request-driven autoscaling module that can scale to zero.
Knative also leverages the service mesh networking framework Istio for efficient and flexible traffic routing. These components enable Knative to satisfy the requirements of a serverless framework: scaling dynamically (including scaling to zero) and easy to deploy.
Knative Serving [6] takes care of scaling and load-balancing of the serverless functions in Knative. A high level hierarchy of the components in the Serving project is shown in Figure 1. Functions are represented by container images, where each function instance runs as a pod/container. Knative Serving provides high level abstractions on top of Kubernetes to allow service administrators to easily roll out new services. A Knative service is a management object for functions which are defined by configurations. Any updates in the configuration will create a new immutable revision representing the state of the function. Each service also points to a specific route that keeps information of how the requests should be redirected.

Cold Start Problem
Ideally, we would like to have minimal overhead when invoking the functions. However, when the platform needs to spin up the first instance, the underlying resources still needs time for initialization. This bootstrapping also happens when the autoscaler provisions additional instances to handle traffic. The initialization time is unavoidable for each function instance and introduces a delay until the instance can respond to request(s). This issue is very common in serverless platforms, and is known as the cold start problem. Although FaaS offering typically suffer from cold-starts, the overhead each platform incurs varies based on the underlying implementation of the functions.
Knative is built on top of Kubernetes and uses containers as the computational resource. The cold start overhead could be split into two categories: (1) Platform overhead: Overheads introduced due to Knative itself, this includes network bootstrapping, network sidecar injection, pod provisioning, etc. This delay is rather uniform throughout different functions.
(2) Application-dependent initialization overhead: Any overhead caused by the application, e.g. model initialization for a machine learning application. This type of latency varies between different applications. For example, in our experiments with Knative, cold starts for a simple HTTP server caused the application to wait around 5 seconds before the first response, while the delay introduced to a naive image classifier implemented in Tensorflow can be up to 40 seconds. This latency is rather intolerable, especially when the response time for a typical web service is in the order of milliseconds.
Our approach to solve the cold start problem is to maintain a pool of warm pods that will stand-by and be immediately available to functions with increasing demand, eliminating the cold start overhead by provisioning the pods beforehand. This pool can also be shared among multiple different services that use the same function. In this report, we first describe the implementation of a pool system based on Knative Serving. We then evaluate the performance improvement of our solution compared to alternatives (e.g., Knative without a pool of pre-warmed Pods), and discuss results and insights. We also examine existing research of the cold start problem, and highlight some directions for future exploration.

IMPLEMENTATION 2.1 Pool of Pods
We implement the pool as a Knative resource. In figure 2, we can see the underlying native components a revision uses. We reused the revision structure when defining the pool resource, which can be seen in figure 3. Using the same structure allows us to simplify the controlling methods and reduce the overhead of how labels propagate through the components. Since we followed the existing configurations in Knative using CRDs, the augmentation of the resource itself was pretty straightforward once we figure out the dependencies between the control logic. After defining the pool construct, we implement the logic of pod migration from the pool to the target revision (figure 4).

Pod Migration
The original logic of Knative's autoscaling is simple (figure 5. Sidecar containers in the pod will collect and send traffic metrics to the autoscaler. The autoscaler will then scale up/down the revision directly by changing the number of desired pods in the underlying deployment. This will trigger the reconciliation in Kubernetes to create/destroy pods in the replicaSet. Our modification of the logic of the autoscaler can be seen in figure 6: before changing the desired number of pods in the replicaSet, we first check if there are any pods in the pool that can be migrated to the target. By a series of operations on the labels of these pods and selector of the replicaSets, the pods are migrated to the target replicaSet, and the stat collection for these pods are activated so the autoscaler can obtain correct metrics. If the number of new pods needed are more than the number of pods in the pool, the original logic kicks in and spawns an amount of new pods equal to the deficit between the desired number of new pods and the number of pods in the pool. This chain of operations is completed in a sequential order to reduce side effects causing race conditions. It currently takes around 2 seconds for the pods to get migrated.
The pool resource definition took~200 LOC, and the migration code is~350 LOC to implement.

EVALUATION 3.1 Response Time to First Request
We first evaluate our implementation by benchmarking the system with and without the pool of pods. Our system was built on a Google Cloud Platform n1-standard-4 (4 vCPUs, 15 GB memory) instance. We use two different applications for evaluation. The first application is a simple HTTP server implemented in Golang, and does some trivial computation before responding to the client. The second application is a image classifier for dogs and cats implemented with Python and Tensorflow, which needs to initialize and load the model (roughly 200MB in size) into memory before classification could be done. The results are shown in Table 1. We can see that the HTTP server saves approximately 7 seconds, which is over half of the cold start response time, and the delay of the image classifier has been cut down from almost 40 seconds to 7.5 seconds. This improvement is significant and we can see that the longer the application overhead is, the larger the difference pools make.

Simulation on Traces
In addition to empirical evaluation with static requests, we test the system performance in a more realistic, large-scale scenario. We built a simulator in Python based on the results we obtained in the previous section. Since no suitable traces could be obtained to benchmark serverless workloads, our simulation used the Pareto distribution to emulate the inter-arrival time of request events. Wilson [14] suggests Pareto distribution to be a simple approximation of network events when the shape of the distribution is close to 1. We simulated 5 concurrent services, each having 1000 requests in total with inter-arrival times of a Pareto distribution with shape = 1.1. We assume that each service is independent, and a service only needs one instance to handle all the incoming traffic.  We can see that the adding a pool significantly improves the overall response time. Even with 1 pod in the pool, the P95 for both short and long applications are entirely eliminated, and at least 50% reduction for P99. Longer application initialization time increases the extreme percentiles. This is due to more requests pending when the instance takes longer to bootstrap.

Response Time for Fixed Pool Size
In the experiments described in the previous section, we observe that even with one pod in the pool, the improvement is significant. We performed a simulation ranging between 1 to 10 services, while fixing the number of pods in the pool to 1 to see how contention affects the response time. The results are shown in Figure 11. We can see that the improvement in P99 response time falls under 50% if there are more than 5 services. However, the results of 1 to 5 services suggests that we would not need a large pool to reduce the cold start problem. This also shows that we do not need to bring in excessive resources for the pool when leveraging statistical multiplexing.

RELATED WORK
The advent of serverless platforms has been less than five years, and since most details of serverless platforms originated from the industry, there is limited research in the realm. A prewarming mechanism in OpenWhisk is described in [11]. However, the prewarming step only allocates the container runtime (e.g., a container with NodeJS), but not the function itself -which fits well stateless workloads, where functions do not have long application initialization overhead. McGrath et al. [9] also proposed a similar solution in Microsoft Azure with global cold queues that have pre-allocated empty containers ready to be initialized as any function, and a warm stack per function that reuses previous containers. This approach provides the flexibility to reduce platform-wise cold start overhead, but the application overhead cannot be mitigated. A recent work proposed by Akkus et al. [7] turns to alternative methods instead of containers. They use forked processes as the function instance, allowing workloads to run with lower memory footprint and faster initialization time. However, not all programming languages support native forking, limiting the usage to particular languages such as Java and C/C++. Inter-function security is also a concern for process-based FaaSes due to lower degree of isolation between processes.
The cold start program can be also seen as a variation of known computer science models, as follows.
Caching [13] is an active and related topic that has been explored for decades. Cache misses are costly when they happen, similar to the huge penalty in a cold start. The "prefetching/prewarming" concept also originates from cache designs. However, a fundamental difference between caching and our problem is that caching has two dimensions to utilize: temporal locality and spatial locality, while our problem only considers temporal locality. Therefore, many approaches leveraging spatial locality are invalid in our context. Caching also assumes that the benefits are shared among multiple users, i.e. entries in the cache can be accessed by all users requesting the same resource. In the cold start problem, however, instances are mutually exclusive and cannot be shared (for security purposes).
The knapsack problem appears to have similarities to cold starts, where we can model different functions with different values and optimizing the total value with limited resources. However, this model is a static approach not considering different time slots and the traffic distribution. The diminishing marginal return of value of multiple warm functions in the pool also cannot be accurately represented with static values.
Inventory models [10] are stochastic models that model the behavior of inventory warehouses, widely used in operation research. By modeling each function as a type of "product" our system provides, our problem is essentially managing the inventory in a way that minimizes the overall time the clients wait starting from requesting a product until finish using the product. In our case, our model is similar to a multi-reparable-product single-facility model with holding costs [10]. We can extend this formulation based on the system we are trying to mode, e.g. a multi-warehouse problem where we want to model deployments on a multi-server cluster.

FUTURE WORK
In a subsequent study on this problem, the following topics can be considered: • Sharing pools among multiple services should further improve cluster efficiency, while maintaining the latency improvement benefits. • Simulation: Our simulation only covers scaling between 0 and 1. Scaling up multiple instances at a time could be interesting since contention between services would be even more significant. Another improvement would be simulating hybrid traces, i.e. traces in both short and long applications, and observe if the behaviour of different services interact with each other. The impact on resource utilization of the host system could also be taken into account when running the simulations. • Theory: As mentioned in the previous section, mathematical models could be useful to obtain theoretical insights of the cold start problem.

CONCLUSION
Mitigating the cold start problem is crucial for future serverless platforms. With a basic implementation and initial simulations, our solution using a pool of warm function instances suggests that the P99 cold start delay can be reduced by up to 85% with one pod in the pool. Further research and experiments should be conducted in order to analyze the overall trade-offs of our approach.