FanFan - a Serverless Fan-out, Fan-in Framework for Google Cloud Pub/Sub
2.3K
FanFan behaves like a "plugin" for Pub/Sub - adding task Fan-out, Fan-in behaviours.
JobRequest message (range, foreach or fixed fan-out spec)Task messages, emits them to Pub/Pub & tracks their statusTask message, emitting a TaskOutcome for eachJobOutcome when all tasks have succeeded, failed or timed out.FanFan works well in local development environments (in Docker, with the Pub/Sub emulator). In production, it is intended to be deployed to Google Cloud Run, but should work in Kubernetes too.
FanFan introduces 4 touch points (topics) between your application and Pub/Sub. By default these topics each have a single push subscription.
Once FanFan is deployed, it should be transparent to your application as all integration is via Pub/Sub messages.
sequenceDiagram
autonumber
Your App->>+Pub/Sub: Fan-out Job Request
par
Pub/Sub->>+Your App: Run Task (1-n)
Your App->>-Pub/Sub: Task Outcome
end
critical Once all Tasks done
Pub/Sub->>Your App: Job Outcome (ok|fail)
end
A more detailed sequence diagram including the FanFan service can be found SEQUENCE.md
The framework consists of
A minimum of 4x Pub/Sub topics & subscriptions are required.
Tooling/scripts are provided to set these up for local development and in your Google Cloud project.
fanfan-job-request JobRequest
Used by your app to submit fan-out requests.
fanfan-task Task
This is where all the fanned-out tasks get sent.
Your application must respond - either by HTTP push (the default) or pull subscription.
More notes on scaling options later.
See below for details of an optional 5th "dead letter" topic.
fanfan-task-done TaskOutcome
For each task, you will need to submit an outcome
fanfan-job-done JobOutcome
And finally, once all Tasks are complete, FanFan will publish a final message for your app to consume.
(Optional) fanfan-task-dead-letter Task
Recommended for improved error handling.
This ensures that FanFan knows when a task has failed to be processed by the task handler.
More advanced setup for topics & subscriptions is possible depending on your use case. Options include, but are not limited to:
The provided client libraries abstract away the underlying JSON data structures, but these will give you an idea of the flow of data & messages through the framework:
Full JSON Schema & spec to follow (@todo).
Three different fan-out models are supported - foreach, range and fixed.
JSON payload examples are below, but these structured are abstracted away in the client libraries.
foreach ['cat', 'dog', 'mouse'] as 'pet'for range(1900, 2000) as 'year'JobRequestThis is the initial instruction sent by your application to create a fanned-out workload.
{
"action": "action_string",
"fanout": {
"foreach": {
"as": "some_field",
"items": [ 10, 6, 82 ]
}
},
// Optional payload
"payload": {
"other": "data"
}
}
{
"action": "action_string",
"fanout": {
"range": {
"as": "some_field",
"start": 1,
"stop": 10,
"step": 1
}
}
}
{
"action": "action_string",
"fanout": {
"tasks": 1000
}
}
TaskEvery Task comes to your application in this format.
{
"jobId": "uuid4fmt-job1-4e45-b280-5091c8f3cd69",
"taskId": "uuid4fmt-task-4623-b368-b6103a6ad738",
"action": "action_string",
"payload": {
"some_field": 10,
"other": "data"
},
}
TaskOutcomeFor each Task processed, you emit a message as follows. Status string enumerations are provided in the client libraries.
The payload property may be used in future versions for pipeline or map-reduce style operations where we need to capture the output of a Task.
{
"jobId": "uuid4fmt-job1-4e45-b280-5091c8f3cd69",
"taskId": "uuid4fmt-task-4623-b368-b6103a6ad738",
"status": "SUCCEEDED",
"payload": null
}
JobOutcomeAnd finally, once all tasks have succeeded or failed, your application is informed.
The request property always contains a copy of the original JobRequest.
The payload property may be used in future versions for pipeline or map-reduce style operations where we need to present the output of a Job.
{
"jobId": "uuid4fmt-job1-4e45-b280-5091c8f3cd69",
"status": "SUCCEEDED",
"taskCounts": {
"SUCCEEDED": 1,
"FAILED": 0
},
"startedTsp": 286965000,
"tookMs": 50,
"payload": null,
"request": {
"action": "action_string",
"fanout": {
"foreach": {
"as": "some_field",
"items": [ 10, 6, 82 ]
}
},
"payload": {
"other": "data"
}
}
}
You will need 3 additional services as follows.
You can use any of the Pub/Sub emulators recommended by Google, here I use an image with some additional tooling that runs on port 8681, which we need to configure in the other services.
# PubSub Message Bus, exposes port 8681
pubsub-emulator:
image: fluentthinking/gcloud-pubsub-emulator:latest
Out-of-the-box Redis
# FanFan Persistence Layer
redis:
image: redis:6.2-alpine
restart: always
command: redis-server --loglevel warning
And finally, FanFan itself. We need to tell it where to find Redis, the Pub/Sub emulator & the Push subscription target
for your application. This is where the Pub/Sub emulator will send Task and TaskOutcome payloads (i.e. how your app
executes the workloads)
# FanFan API
fanfan:
image: fluentthinking/fanfan:latest
environment:
# Which port should the FanFan API expose?
PORT: 8080
# Where is the Pub/Sub emulator?
PUBSUB_EMULATOR_HOST: pubsub-emulator:8681
# Project name for building Pub/Sub topics & subscriptions
GCP_PROJECT: get-fanfan
# Redis server
REDIS_HOST: redis
REDIS_PORT: 6379
# Pub/Sub Push endpoints
APP_PUSH_TARGET: http://myapp:8080
# FANFAN_PUSH_TARGET: http://fanfan:8080
depends_on:
- redis
- pubsub-emulator
If you've set up your compose file correctly, you should see something like this appear when you run docker-compose up
PubSub emulator ready
Setting up topics, subscriptions
Creating topic [projects/get-fanfan/topics/fanfan-job-request]
Creating topic [projects/get-fanfan/topics/fanfan-task]
Creating topic [projects/get-fanfan/topics/fanfan-task-done]
Creating topic [projects/get-fanfan/topics/fanfan-task-dead-letter]
Creating topic [projects/get-fanfan/topics/fanfan-job-done]
Subscription created [projects/get-fanfan/subscriptions/fanfan-job-done-push] (push)
Subscription created [projects/get-fanfan/subscriptions/fanfan-task-done-push] (push)
Subscription created [projects/get-fanfan/subscriptions/fanfan-task-dead-push] (push)
Subscription created [projects/get-fanfan/subscriptions/fanfan-job-request-push] (push)
Subscription created [projects/get-fanfan/subscriptions/fanfan-task-push] (push) w/DeadLetter
Starting FanFan
Once you have the FanFan infrastructure running, you can use our client libraries to emit JobRequest payloads (or
build them yourself) to the projects/get-fanfan/topics/fanfan-job-request topic.
Installation steps using the gcloud cli tool can be found here INSTALL_GCLOUD.md
Content type
Image
Digest
sha256:92ce479c8…
Size
108.7 MB
Last updated
4 months ago
docker pull fluentthinking/fanfan