New Python FDK is here!

Denis Makogon
5 min readFeb 15, 2019

--

The Python FDK has been updated with significant performance improvements for cold starts, but some changes are breaking.

This post will help you move to the latest FDK so that you can reap the benefits.

Investigating FDK Cold-start Performance

A New Core

If you are familiar with our Python FDK source code you have probably noticed that we use aiohttp as a base HTTP server implementation. There were a couple of reasons for this:

  • In a few lines of code, you can get a working HTTP server over UNIX domain socket.
  • Event-driven, compatible with asyncio, every HTTP route handler is a coroutine which allows function developers to write a function that works with background operations using native async/await syntax.

Despite its prettiness, we decided to get rid of this because of the aiohttp cold start problem. We figured out that the FDK takes almost 10 seconds to start with “hello world” code:

import time:       810 |    8001160 | fdk

A bit of investigation uncovered that aiohttp needed 8 seconds to init all its own imports and start an HTTP server. Since our requirement is 5 seconds max to open a function’s socket, this makes aiohttp a non-starter. On a related note we’ll cover measuring imports and how to deal with heavy ones in a future post.

Shipping the new FDK

A week ago we introduced a brand new shiny Python FDK with tons of positive changes — including replacement of aiohttp. Unfortunately, to reap these benefits, the FDK update will break some things, especially the entry-point to a function. You need to expect the following changes:

  • Boilerplate will change (both func.py and generated Dockerfile)
  • Minimum Python version is 3.7.1
  • Function’s data parameter type changes from str to io.BytesIO
  • Unit testing framework is going to change

Dockerfile

The migration path to a new Dockerfile is short — one line. The entrypoint will change from:

ENTRYPOINT["python", "func.py"]

to:

ENTRYPOINT["/python/bin/fdk", "func.py", "handler"]

Here are the bare minimum changes necessary to be applied to a Dockerfile in order to adopt the new FDK requirements:

As you can see the entrypoint has changed. Now you need to set it in the following manner:

/python/bin/fdk <your-function-file> <function-name>

Please note that <your-function-file> is a file where <function-name> a method with signature(ctx, data=None lives. As already mentioned in the dockerfile above the entry point will look like:

/python/bin/fdk /function/func.py handler

Even if your function has many files, you still have a consolidated entrypoint to your code. It’s like exposing the main function while /python/bin/fdk is a runner that consumes your main function and starts a serverless function.

If you’ve been using your own Dockerfile that wasn’t derived from the Dockerfile that our CLI is generating, then you need to search in your $PATH where the CLI fdk was installed (on Linux, it will be installed to /usr/local/bin/fdk ). Most of the time if you’ve been using:

pip install --target <location> ...

then you need to search for fdk CLI at <location>/bin/fdk.This is what the Fn CLI does by calling the following command:

pip install --target /python ...

func.yaml

If you’ve been using the Fn CLI to build functions without modifying theruntime in func.yaml to docker instead of python there are a couple things you need to update: you’ll need the CLI at the latest version and then pin the Python runtime version to python or python3.7.1 :

func.py

import json
import io
from fdk import response
async def handler(ctx, data: io.BytesIO=None):
name = "World"
try:
body = json.loads(data.getvalue())
name = body.get("name")
except (Exception, ValueError) as ex:
print(str(ex))
return response.Response(
ctx, response_data=json.dumps(
{"message": "Hello {0}".format(name)}),
headers={"Content-Type": "application/json"}
)

As you can see nothing much has changed here. Starting now, the FDK will provide data as io.BytesIO type. The reason for doing this is that io.BytesIO provides a nice interface for reading data, and JSON frameworks (whether built-in or 3rd-party) can work with io datatypes easily.

Also the entry point to the function changed, i.e., func.py is no longer considered as the main module ( __main__ ) which means that the following section:

if __name__ == "__main__":
fdk.handle(handler)

no longer has any effect. Please note that the FDK will fail-fast with an appropriate message if an old-style FDK format is used.

Here’s a diff of code which highlights the necessary changes:

“data” type has changed

With the new FDK, the data parameter is changing from str to io.BytesIO. The simplest way to migrate is to wrap your data processing code with one line of code: data = data.read(). If you’ve been using the json lib to turn incoming data into a dictionary, you’ll need to replace: json.loads with json.load like so:

try:
dct = json.load(data)
except ValueError as ex:
# do here whatever is reasonable

Newer unit test signature

@pytest.mark.asyncio
async def test_parse_request_without_data():
call = await fixtures.setup_fn_call(handler)
content, status, headers = await callassert 200 == status
assert {"message": "Hello World"} == json.loads(content)
assert "application/json" == headers.get("Content-Type")
@pytest.mark.asyncio
async def test_parse_request_with_data():
call = await fixtures.setup_fn_call(handler, content=json.dumps({"name: "John"}).encode("utf-8"))
assert 200 == status
assert {"message": "Hello John"} == json.loads(content)
assert "application/json" == headers.get("Content-Type")

The FDK is evolving and this is the second time the FDK has changed the unit test signature. This time it’s because aiohttp is gone and so the unit testing framework had to be updated.

We believe these changes are very positive for Python developers using the Fn Project — in part due to the 8x improvement on start times! But we also understand breaking changes can sometimes be frustrating. It’s important that as we move towards a 1.0 release of Fn in 2019, we nail the design of the system to satisfy even the most demanding customer workloads.

You may find the following links useful as a general follow-up on this post:

Finally, join the Fn Army on Slack!

--

--