This topic describes the runtime extension feature of Function Compute. This feature is developed based on traditional long-running applications to help you reduce the unnecessary costs generated by idle instances. This topic describes the principles and billing of the runtime extension feature in Function Compute. This topic also describes how to configure the PreFreeze hook and the PreStop hook in the Function Compute console, by using Serverless Devs and SDKs, and provides sample scripts for implementing hooks.

Traditional long-running applications and FaaS execution environment

Traditional long-running virtual machines and 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 when no request is executed in this interval. Function Compute charges you at a billing granularity of one millisecond. You are charged only when requests are sent. The instances are frozen in time periods in which no requests are sent. This way, the unnecessary costs generated by idle instances that use the fully event-driven billing models can be reduced. However, the freeze mechanism breaks down the hypotheses of long-running processes in traditional architectures and makes it difficult to migrate applications. Function Compute runs in a special environment. The system cannot track and report data as expected in scenarios where there is no cold start, such as when the open source distributed Tracing Analysis libraries or third-party application performance management (APM) solutions are used.

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 is sent, or the data points may be 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.
  • To support graceful shutdown of function instances, applications need to close connections, stop processes, and report status when instances are stopped. Developers do not know when to shut down instances in Function Compute. In addition, no webhook is provided to send notifications about shutdown events of function instances.
graceful_offline

Programming model extensions

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

mode_comparison
  • PreFreeze: Each time Function Compute is about to freeze a function instance, Function Compute calls the HTTP GET /pre-freeze path. Extension developers implement the corresponding logic to ensure that required operations are completed before the instance is frozen. For example, developers can configure the system to wait until metrics are sent before it freezes instances. The duration to execute the PreFreeze hook is not included in the duration to call the InvokeFunction operation. pre-frozen_0
  • PreStop: Each time Function Compute is about to stop a function instance, Function Compute calls the HTTP GET /pre-stop path. Extension developers implement the corresponding logic to ensure that required operations are completed before the instance is released. For example, database connections are closed and the status is reported or updated. extension_logic

Precautions

  • The input parameters of the PreFreeze hook and the PreStop hook do not include the event parameter.
  • The PreFreeze hook and the PreStop hook have no return values. The logic that is appended to the PreFreeze hook or the PreStop hook to return values does not take effect.
  • You can configure PreStop hooks in all runtime environments. However, you cannot configure the PreFreeze hook 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 the PreFreeze hook and the PreStop hook.

Billing description

The billing method for PreFreeze or PreStop calls is the same as that for InvokeFunction calls. You are not charged for the number of requests sent to the HTTP hooks. The extensions are also applicable to scenarios in which multiple concurrent requests are executed on a single instance. Assume that multiple invocation requests are concurrently executed on the same instance. After all the requests are executed, the PreFreeze hook is called before the instance is frozen. In the example shown in the following figure, the specification of the function is 1 GB. Assume that the period from t1 when PreFreeze starts to t6 when Request 2 is completed is 1s. The execution time of the instance is calculated based on the following formula: t6 - t1. The consumed resource is calculated based on the following formula: 1s × 1 GB = 1 CU.

pre-frozen

Prerequisites

Create a function

Configure the PreFreeze hook and the PreStop hook in the Function Compute console

When you use the Function Compute console to create a function, you cannot configure the PreFreeze hook and the PreStop hook. You can configure the PreFreeze and PreStop hooks 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 desired service.
  5. On the Functions page, find the function that you modify and click Configure in the Actions column.
  6. In the Instance Lifecycle Hook section of the Modify Function Settings page, configure the hooks and timeout periods, and then click Save.
    Configure lifecycle hooks
    Note Each extension function needs to be configured with a hook and the timeout period. The format of the hook is [File name].[Extension function name]. Assume that you set the PreStop hook 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 hook is preStopHandler.
  7. After you specify the handler of the extension function, implement the function in the code that you want to execute.
    Note RAM users and the accounts that are created on the International site (alibabacloud.com) cannot use the new version of the online IDE. You must switch to the old console.
    1. In the upper-right corner of the console, click Back to Old Version.
    2. In the left-side navigation pane, click Services & 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. In the Code Management section, enter the code of the extension function. Click Save and Invoke.
      For example, if you set the handler of the PreStop hook to index.preStopHandler, you must implement the preStopHandler function. In this topic, an event function whose runtime environment is Python 3 is used as an example. For more information about the sample code of extension functions in different runtime environments, see Sample code. Code of the extension function
      Note The online IDE supports PHP, Python, Node.js, and custom runtime environments. However, the online IDE does not support compiled languages such as Java, Go, .NET, or custom containers.

Use Serverless Devs to configure the PreFreeze hook and the PreStop hook

The following sample code provides an example on how to use Serverless Devs to configure the PreFreeze hook and the PreStop hook:

  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 empty. Otherwise, the function is not updated by default. For example, if you want to turn off the PreFreeze hook, perform the update and deployment operations based on the following configuration. In this case, the timeout parameter of the PreFreeze hook is invalid.

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

Use SDKs to configure the PreFreeze hook and the PreStop hook

You can use SDKs to deploy and update extension functions. The following code provides an example on how to use the SDK for Go to configure PreStop and PreFreeze hooks 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 the PreStop hook and the PreFreeze hook. 
  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
}

Sample code

The following section describes the sample code of extension functions in different runtime environments. The PreFreeze hook is defined in the same manner in which the PreStop hook is defined in a runtime environment. This topic provides the sample code for only the PreStop hook.

 
# -*- 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
    }
}

Query logs related to hooks

After you configure an instance lifecycle hook and execute the code of the hook, you can query the logs of the hook.

  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, find the desired service and click Functions in the Actions column.
  5. On the Functions page, click the desired function and click Logs.
  6. On the Call Request List tab, find the request line that you want to query, copy the Instance ID, and then click Advanced Logs.
    You are redirected to the query and analysis page in the Log Service console. You can use the instance ID to query the Start logs and the End logs of all hooks. You can also use the instance ID and the keyword for instance lifecycle hook to query the Start and End logs of a specified hook. Example: c-62833f38-20f1629801fa4bd***** and PreStop. db_lifecycle_log

    You can also query the request logs based on the request ID in the Start logs and the End logs. If the log does not contain the request ID, click the db_lifecycle_log_context icon to query the log.