Function Compute run time extensions based on traditional resident applications can effectively help you eliminate idle costs. This topic describes the run time extension features and billing of Function Compute, and how to configure the PreFreeze and PreStop functions by using the console, Serverless Devs, and SDKs.

Long-running applications and FaaS execution environment

Traditional long-running virtual machines or managed container services often use a billing interval that starts when an instance is started and ends when the instance is stopped. You are charged even if no request is executed during this interval. The Function Compute provides a billing granularity of 1 ms and is only billed within the interval where the actual request is available. Instances will be frozen within the period other than the request. This basically eliminates the idle costs of a fully event-driven billing model. However, the freezing mechanism breaks the assumption of long-running processes in traditional architectures and increases the difficulty in migrating applications. Because the Function Compute has a special running environment, facing scenarios without cold start, such as the commonly used open-source distributed trace Tracing Analysis library and third-party APM solution, it will not be able to track and correctly report its data.

The following pain points hinder the smooth migration of traditional applications to a serverless architecture:

  • Data of asynchronous background metrics is delayed or lost. If the data fails to be sent during the execution of a request, the data may be delayed until the next request or data points are discarded.
  • The latency is increased if metrics are sent synchronously. If a method similar to Flush is called after each request is completed, this not only increases the latency of each request, but also causes unnecessary pressure on backend servers.
  • Function graceful offline: When an instance is shut down, it needs to clean up connections, close processes, and report status. Developers cannot grasp the offline timing of the instance in the Function Compute, and there is also a lack of Webhook notification function instance offline events.
graceful_offline

Programming model extensions

Function Compute provides the runtime extension feature to resolve the preceding pain points. The feature extends the existing programming model for HTTP servers by adding the PreFreeze and PreStop webhooks to the existing HTTP server model. Extension developers implement an HTTP handler to monitor lifecycle events of function instances.

mode_comparison
  • PreFreeze: Before each Function Compute service decides to freeze the current function instance, the Function Compute service calls the HTTP GET /pre-freeze path. The extension developer is responsible for implementing the corresponding logic to ensure the necessary operations before the instance is frozen, such as waiting for the metric to be sent successfully. The time when the function calls InvokeFunction does not include the execution time of the PreFreeze hook. pre-frozen_0
  • PreStop: Each time Function Compute decides to stop the current function instance, Function Compute sends an HTTP GET request to the /pre-stop path. Extension developers implement the logic to ensure that necessary operations are completed before the instance is released. For example, database connections are closed and the status is reported or updated. extension_logic

Note

  • The input parameters of PreFreeze and PreStop functions do not include the event parameter.
  • PreFreeze and PreStop functions have no return values. Return logic appended to PreFreeze and PreStop functions does not take effect.
  • You can configure PreStop functions in all runtime environments. However, you cannot configure PreFreeze functions in the Python, PHP, and C# runtime environments.
  • If you use the Java runtime environment, you must update fc-java-core to version 1.4.0 or later. Otherwise, you cannot use PreFreeze and PreStop functions.

Billing methods

The billing method generated in PreFreeze or PreStop is the same as that of InvokeFunction. You are not charged for the number of requests sent to the HTTP hooks. The extension is still applicable in the scenario of multiple concurrency for a single instance. Assume that multiple Invoke requests are executed on the same instance at the same time, the PreFreeze hook is called once after all requests are terminated and before the instance is frozen. In the following figure, for example, if the function specification is 1 GB and the period (assuming 1 second) from the start of t1 PreFreeze to the end of t6 request 2, the instance execution time is t6-t1 and consumes 1s * 1 GB=1 CU.

pre-frozen

Prerequisites

Create a function

Use the Function Compute console

When you use the console to create a function, you cannot configure PreFreeze and PreStop functions in Function Compute. You can configure the PreFreeze and PreStop functions when you update the function.

  1. Log on to the Function Compute console.
  2. In the left-side navigation pane, click Services and Functions.
  3. In the top navigation bar, select the region where the service resides.
  4. On the Services page, click the target service.
  5. On the Functions page, find the function that you want to manage and click Configure in the Actions column.
  6. In the Lifecycle Function section of the Edit Function page, set the function and timeout period, and then click Save.
    Configure lifecycle functions
    Note Each extension function needs to configure a separate function entry and timeout period, where the function entry is [file name].[extension function name]. Assume that you set the handler of the PreStop function to index.preStopHandler when you create a function in the Python runtime environment. In this case, the file name is index.py and the name of the PreStop function is preStopHandler.
  7. After you specify the handler of the extension function, implement the function when you execute the code.
    Note RAM role accounts and international site accounts cannot use the new online IDE. You need to switch to the old console.
    1. In the upper-right corner of the console page, click Back to Old Version.
    2. In the left-side navigation pane, click Services and Functions.
    3. In the Services pane, click the service that you require. On the Functions tab, click the name of the function that you require.
    4. Click the Code tab, enter the extended function code in the Code Management section, and then click Save and Run.
      For example, if you have configured the function entry for PreStop as index.preStopHandler, you must implement the preStopHandler function. This topic uses an event function whose runtime environment is Python 3 as an example. For more information about the sample code of extension functions in different runtime environments, see the Code examples section in this topic. Code of the extension function
      Note The online IDE supports PHP, Python, Node.js, and Custom Runtime; however, it does not support Java, Go, and. NET, as well as custom containers.

Configure by using Serverless Devs

When you use Serverless Devs to configure the PreFreeze and PreStop extension functions, the sample code for the configuration file is shown as follows:

  codeUri: './code.zip'
  ......
  instanceLifecycleConfig:
    preFreeze:
      handler: index.PreFreeze
      timeout: 60
    preStop:
      handler: index.PreStop
      timeout: 60

To disable an extension function, leave the Handler parameter of the extension function unspecified. Otherwise, the function is not updated by default. For example, to disable the PreFreeze function, deploy and update the function based on the following configuration. In this case, the Timeout parameter of the PreFreeze function is invalid.

  codeUri: './code.zip'
  ......
  instanceLifecycleConfig:
    preFreeze:
      handler: ""
      timeout: 60
    preStop:
      handler: index.PreStop
      timeout: 60

Use SDKs

You can use SDKs to deploy and update extension functions. This topic uses the Go SDK as an example to describe the sample code for configuring the PreStop and PreFreeze functions when you create a function:

package main

import (
  "github.com/aliyun/fc-go-sdk"
  "fmt"
  "os"
)

func main() {
  client, _ := fc.NewClient(
    os.Getenv("ENDPOINT"),
    "2016-08-15", 
    os.Getenv("ACCESS_KEY_ID"),
    os.Getenv("ACCESS_KEY_SECRET"),
  )

  serviceName := "ExtensionService"
  functionName := "ExtensionFunction"
  createFunctionInput := fc.NewCreateFunctionInput(serviceName).WithFunctionName(functionName)
  // Configure PreStop and PreFreeze functions. 
  preStopHook := fc.NewLifecycleHook().WithHandler("index.preStop").WithTimeout(int32(30))
  preFreezeHook := fc.NewLifecycleHook().WithHandler("index.preFreeze").WithTimeout(int32(10))
  instanceLifecycle := fc.NewInstanceLifecycleConfig().WithPreStopHook(preStopHook).WithPreStopHook(preFreezeHook)
  createFunctionOutput, err := client.CreateFunction(createFunctionInput.WithInstanceLifecycleConfig(instanceLifecycle))
  if err != nil {
    fmt.Fprintln(os.Stderr, err)
  } else {
    fmt.Printf("CreateFunction response: %s \n", createFunctionOutput)
  }
  return
}

Code examples

The following part describes the sample code of extension functions in different runtime environments. The PreFreeze function is defined in the same way as the PreStop function in the same runtime environment. Therefore, this topic provides only the sample code of the PreStop function.

 
# -*- coding: utf-8 -*-

import logging

def handler(event, context):
    logger = logging.getLogger()
    logger.info('handler start')
    return "ok"

def pre_stop(context):
    logger = logging.getLogger()
    logger.info('preStop start')
'use strict';

var counter = 0;
module.exports.handler = function(event, context, callback) {
    counter += 2;
    callback(null, String(counter));
};

module.exports.preFreeze = function(ctx, callback) {
    counter += 1;
    callback(null, "");
};
<?php      

$counter = 0;
function fc_func_pre_stop($context) {
    sleep(2);
}

function fc_func_handler($event, $context) {
    global $counter;
    $counter += 2;
    return $counter;
}
 ?>
      
using System;
using System.IO;
using Aliyun.Serverless.Core;

using Microsoft.Extensions.Logging;
namespace Aliyun.Serverless.TestHandlers
{
    public class PreStopAndPojoCounter
    {
        public int counter;
        public PreStopAndPojoCounter()
        {
            this.counter = 0;
        }

        public void PreStop(IFcContext context)
        {
            // todo
        }

        // todo
    }
}
import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.PreFreezeHandler;

import java.io.IOException;

public class PreFreezeNormal implements PreFreezeHandler {
    @Override
    public void preFreeze(Context context) throws IOException {
        // todo
    }
}