Asynchronous processing with Cloud Run and Pub/Sub
Secure asynchronous processing with Cloud Run through Pub/Sub.
Background
Cloud Run will throttle the CPU when there isn’t an active request processing. That means that it isn’t possible to spawn a new thread or goroutine to do background processing within a Cloud Run API. Background processing can for example be to send an email during registration or to send push notifications when a user likes a post, something that the user doesn’t have to wait for.
This article will describe a solution for asynchronous processing where an API running on Cloud Run will send events to Pub/Sub. These events will then be sent back to the API through a Pub/Sub push subscription.
- An API request is sent to a Cloud Run service.
- The API request is processed and events are sent to an event topic in Pub/Sub.
- The API response is returned back the client and the CPU will be throttled.
- Pub/Sub pushes an event back to the API Service for asynchronous processing.
In our example, we will have a golang REST API with two methods.
POST /change-name // Used to change the name of a user
POST /events // Used by Pub/Sub for processing events
Source code and example
The library used for signing and consuming events and an end-to-end example is available at https://github.com/eripe970/pubsub-signing.
Create events
Pub/Sub required that the Cloud Run allows all public traffic to be able to receive events. It supports authentication of requests and as an extra security measurement, we will sign each message to be sure that they are sent from the API. The GCP requirement that the API must be public means that we cannot have an internal API that handles the events.
The event-type is stored on the message as an attribute. Each message is signed with a secret and the signature is verified when the event is processed in the event handler in the API.
The contents of the event will look like below.
{
"message": {
"attributes": {
"event-type": "user.name.changed",
"signature": "cc6d0457d8b4ec0e994d793302cd6962a0d12101abbc79e561f532a826eca4ee"
},
"data": "eyJOYW1lIjoibmV3LW5hbWUifQ=="
},
"subscription": "api-events"
}
Event-type will be used for the deserialization of the event when it is processed in the api.
The signature is a hex-encoded signature that will be verified when the message is deserialized.
Data is base64 encoded payload (automatically encoded by GCP).
Subscription can be used to know which subscription the message originates from (automatically added by GCP).
Consume Events
The code below constructs the message and validates the signature. The event will also be deserialized depending on its type.
Developing locally
One option when developing locally is to use the Pub/Sub emulator from https://cloud.google.com/pubsub/docs/emulator. Another option is to have two implementations for publishing messages. One to use at GCP that publishes messages to Pub/Sub and one to use locally that makes a HTTP request directly back to the API.
Cloud Run vs Cloud Functions
Events can also be processed by a Cloud Function instead of an API on Cloud Run. Some pros with processing with Cloud Run instead of Cloud Function.
- Support languages/versions that Cloud Functions does not yet support (any docker image).
- Cloud Run can handle multiple types of events/messages in one deployment.
- May not be impacted by cold starts in the same way as Cloud Functions.
Summary
Using Cloud Run in combination with a push subscription on Pub/Sub is a good alternative to Cloud Functions for handling asynchronous processing of events. Try it out!