[AUDIO LOGO]
JEFF HULEATT: Since 2017, Cloud Functions for Firebase
has been the programmatic glue that
binds the Firebase apps we build together.
It lets us run server-side code in a trusted
environment without managing or scaling our own servers.
That means we can do really useful stuff,
like send a notification in Firebase Cloud Messaging when
a user receives a new chat message that shows up
in Firestore, or resize images to improve their loading
time on devices with poor internet connection,
and even ping our team's chat room when a test run is
completed in Firebase Test Lab.
And across Firebase and Google Cloud,
we've been working hard to make Cloud Functions for Firebase
even more useful with Second-Gen functions.
In fact, the infrastructure behind Second-Gen functions
has now graduated from public preview
and is generally available, meaning
we're confident you can use it to write
production-quality apps.
My name is Jeff.
DANIEL LEE: And my name is Daniel.
JEFF HULEATT: And we're excited to talk
about some new stuff that makes functions
more accessible to write.
Daniel is also going to take us behind the scenes
of Second-Gen functions so that we
can learn how to optimize both the cost and efficiency
of the functions we write.
Let's start with something new.
We write functions as triggers that respond
to events in our app, whether that's
an HTTP request, a new document being written in Firestore,
or a user requesting to sign in.
And we've traditionally written these triggers
in JavaScript or TypeScript because Node.js has always
been the only language supported by Cloud Functions for Firebase
until now.
We're excited to announce that Cloud Functions for Firebase
now supports Python.
To write a function with Python, I
start with the Firebase init command
from the Firebase command line interface.
It will generate the files I need to get started.
Most important of these is main.py,
pie which is where I'll put my function code.
In main.py, I'm going to import from the New Firebase Functions
Python library.
In this case, I want to create a callable function, which
can be called directly from an app with the Firebase client
SDKs.
So I'm going to import the HTTPS function module.
The HTTPS module's on-call function
is a special kind of wrapper function called the decorator.
It tells Firebase how to wire up the code below it
as a callable function and is also
the spot where we can pass in configuration about how we'd
like the function to behave.
And then I'll write a function that, in this case,
just always returns Hello, world!
That, plus the Firebase deploy, is
all I need to send my Python code out into the world.
The public preview of Python functions
supports the full range of triggers supported
by Node Second-Gen functions.
That means the Python Admin SDK makes
it easy to talk to Firebase services from a function,
and the Firebase Emulator Suite supports
Python functions for fast local iterative development.
I'm really excited about this because I
hope that this opens up all sorts of opportunities
for the Pythonistas out there, including science and big-data
applications with libraries like NumPy and machine learning
applications with TensorFlow.
And speaking of machine learning,
let's build something to get a clearer
idea of the kind of things we can do with Python functions.
I work in the New York City Google office.
And one thing that I think is under-appreciated in New York
City--
a city known for its huge buildings--
is all the cool trees.
And to tell the world about it, and since I'm a terrible poet,
I want to make an app that lets anyone
ask the new PaLM API to create a poem about trees in New York.
I've already built out the front end of my app.
It's a web app that uses the Firebase JavaScript
SDK to send a request to a Cloud Function
when somebody clicks the Compose button.
The part we need to implement today
is the function, which will call the PaLM
API to generate the poem and send the result back to the web
app so it can display it to the user.
So let's build the function.
To start, we'll set up a callable function,
just like before.
I'll again import from the Firebase Functions library
and define a function with the on_call decorator.
To talk to the PaLM API, I'll need an API key
that I have stored in Cloud Secret Manager.
This keeps that key safe.
To access the key, I'll use the params module in the Firebase
Functions Library.
Then I can specify which secret I'm looking for
and also pass the secret to my function decorator
so that the value of the secret gets bound to my function,
so I can access it later.
Inside my function, I'll parse the request
that comes in so that I know which
tree to write a poem about.
I'll then insert that into a prompt for a poem.
Finally, we'll use the PaLM API.
I'll configure it using the secret.
I set up earlier, send the poem request I built,
and return the poem back to my app.
And here it is working.
In about 10 lines of Python code,
we've done what I set out to do.
We have a working API that will create poems
about trees in New York City.
Now, I can start sharing this web app with the world as is.
But since this is a Second-Gen function,
I know that there is additional configuration available to make
my function perform even better, whether it
is written in Python or Node.
And I just so happen to have the tech lead of Cloud Functions
for Firebase here with me.
Hey, Daniel, any advice?
DANIEL LEE: I'm so glad you asked.
The second generation of Cloud Functions for Firebase
brings together substantial enhancements.
We can set request timeout to up to an hour for those functions
with longer than usual processing time.
We can create more powerful instances
with up to 32 gigs of memory.
And finally, functions can now be
deployed to new regions like London or Delhi.
Second generation of Cloud Function
also gives you more granular control
over how your function uses the available compute resources.
Concurrency is a new configuration only available
for second generation of Cloud Functions,
and it is one of my favorites.
When used properly, concurrency can improve both performance
and lower the cost of your function.
Before I talk about concurrency, let's review Jeff's code
and see how Cloud Function executes the code in response
to an API request.
When a request arrives from Jeff's web app
to generate a poem about trees in New York,
Cloud Function will look for a virtual computer, also known
as an instance, to run Jeff's code to process the request.
Let's walk through Jeff's code from a perspective
of the instance that Cloud Function has found for us.
First, the instance gets the type of tree species
from the request body.
Then the instance makes a request to the PaLM API
and waits for a response containing the generated poem.
This step takes some time, maybe on the order of a few seconds,
waiting for a response from the PaLM API.
Finally, the instance wraps up the poem in a response
object to complete the execution.
And off goes our request, carrying back
the poem to spark creativity for Jeff's inner poet.
So far, so good.
Let's consider another case.
What happens when another request
arrives while the instance is already processing a request?
Traditionally the rule has been one instance, one request.
So when the instance we have is busy processing a request,
Cloud Function needs to spin up a brand-new instance
to process it.
Now we have two active instances handling the incoming requests
in parallel.
The simple predictable scaling behavior
is at the heart of Cloud Functions.
The process of spinning up a new instance is totally automatic
and allows APIs built on Cloud Functions
to handle many requests in parallel.
Now, there are a few things to keep in mind with the scaling
model.
First, creating a new instance takes some time
that will add to the overall time taken
to process the request.
This is what is often referred to as cold start.
With cold start, we may see the API response slower than usual
from time to time.
And this is because Cloud Function
needs extra time to prepare a new instance
and, once it's ready, execute the server code.
Second, having two active function instances
will generally cost more than having one active function
instance.
The good news is that, with Cloud Functions,
you only pay whenever your function instance is serving
requests, not when is sitting idle,
waiting for the next request to arrive.
So how does concurrency fit in here?
Let's take a few steps back to the moment
when a new request arrived while our function instance was
busy processing a request.
As I mentioned before, one instance, one request.
But is our instance actually busy?
Inspecting Jeff's code again, I'm
noticing that this busy instance is just
waiting for the response from the PaLM API.
It isn't doing any demanding calculations
that require a lot of the CPU and memory resources.
It probably has some room to spare to do other useful stuff,
like handle another incoming request.
But because of the one instance, one request rule, it couldn't.
Now, this is where concurrency steps in.
Concurrency defines the maximum number
of requests that can be processed simultaneously
by a single instance.
In the second generation of Cloud Functions,
we can set concurrency to be greater than 1,
allowing instance to share resource available on it
among many requests.
That means that when Cloud Function receives
a new request, instead of spinning up
a new instance to process it, we can use the existing instance
to process both requests.
Let's take concurrency for a spin.
Configuring concurrency setting is easy.
Simply pass in the value of concurrency
we want for our API--
I'm going to try 20 here-- and redeploy the function.
And now let's put concurrency to the test.
I'm going to put Jeff's second-gen function
under a modest load and see how many instances were spun up
to serve all requests.
First up, our function deployed with concurrency set to 1.
Under a modest load, our function
with concurrency set to 1--
following the first-gen one request, one instance model--
required about 18 instances to handle
all the incoming requests.
Now, I'm going to keep everything
the same-- same code, same setup,
but change the concurrency to 20.
The function with concurrency set to 20 only
require two instances to handle all the incoming requests.
That's a lot better than 18.
This is exciting for a few reasons.
One, requiring fewer instances means
that Cloud Function didn't have to spin up a new instance very
often.
Remember, whenever Cloud Function
spins up a new instance, we experience cold start.
And that means that Jeff's function with concurrency
set to 20 experience less cold start, which
means that Jeff's web app will generate poems
more quickly for more users.
Another benefit of needing fewer instances
is that Jeff may end up paying less.
How is that possible?
With concurrency, Cloud Function can fully
utilize the CPU and memory resources that
are assigned to each instance.
Consider Jeff's code again.
Instead of spending most of his time waiting
for the response from the PaLM API from a single API request,
the instance with concurrency set to 20
can go and do something more productive, like process
another request.
JEFF HULEATT: Wow.
Thanks, Daniel.
I know you set concurrency to 20.
But I see in the documentation that the concurrency limit
is 1,000.
I'm just going to set my poem-generating function's
concurrency all the way up to 1,000 to take advantage
of the magic of concurrency.
DANIEL LEE: Not so fast, Jeff.
Remember that concurrency is about sharing resources
on an instance among multiple requests.
So if your concurrency is set too high,
a request might struggle to get the CPU and memory resources it
needs to process the request.
Now, there isn't a magic formula we
can use to determine the concurrency setting that
is correct for any app.
So my recommendation is to start low and slowly ramp up
the concurrency setting to find a good number that's
a good match for your API.
That can be 100.
That can be 1.
It really depends on what the server code is doing.
JEFF HULEATT: OK, OK.
It's not magic.
But I'm still excited to hear that concurrency
can both improve performance and reduce the cost of functions.
[AUDIO LOGO]
We've covered a lot here.
Let's recap everything we've discussed.
We know Cloud Functions for Firebase
lets us add all kinds of functionality to our app
without having to manage or scale our own servers.
And now, in the second generation
of Cloud Functions for Firebase, we
can finally use another programming language, Python,
and the awesome ecosystem of libraries that accompanies it.
We can also tune our functions to reduce cold starts
with concurrency.
And the Second-Gen infrastructure
has graduated from public preview
and is now generally available, which
means we're confident that it will serve you well
in production apps.
If you'd like to try building Second-Gen functions with Node
or Python, take a look at the Firebase Docs,
which have a helpful guides and code
snippets to explain every aspect of Cloud
Functions for Firebase.
Or peruse the function samples GitHub repository
to see example code built to show
the kinds of problems that can be solved with Cloud Functions.
You can even jump straight into code
by using the Firebase CLI's Firebase init functions
command to generate some starter code for you.
Then you're just a Firebase deploy away
from working functions.
We can't wait to see what you build with Python functions
and hope you learned how to make faster, cheaper serverless
APIs for your Firebase app with the help of concurrency
on Cloud Functions for Firebase Second-Gen. Thanks
for learning about functions with us.
Daniel and I hope you enjoyed this session
and have a happy Google I/O.
[AUDIO LOGO]