Skip to content

Singleton

The Singleton pattern configures a function with a desired replica count of one. With one replica, every request or connection is routed to the same function process, allowing them to share process-local state and reuse the same resources.

This page uses a deliberately small example: a function performs simulated expensive setup once when its process starts, then reuses the resulting resource for every request.

                                    ┌──────────────────────────┐
[ Client A ] ──┐                    │    Singleton Function    │
[ Client B ] ──┼── connections ────►│                          │
[ Client C ] ──┘                    │       [ Replica 1 ]      │
                                    └──────────────────────────┘

This simple function highlights several properties of the Singleton pattern:

  • One replica: setting both replica limits to one disables horizontal scaling, so OpenFaaS does not add replicas as load increases.
  • Reuse: requests are routed to the same replica, so they can use the same process-local resource.

The example uses a two-second delay to simulate expensive setup. In a real function, the same point in the lifecycle could be used to:

  • Create a database connection pool or an SDK client.
  • Download and load a large dataset or machine-learning model.
  • Open a persistent connection whose state remains in the function process.
  • Establish a session with an upstream service that has costly setup or permits only one active client.

Create the function

Choose a language and scaffold the function:

faas-cli template store pull golang-middleware

faas-cli new --lang golang-middleware singleton \
  --prefix ttl.sh/openfaas-examples
faas-cli template store pull python3-http

faas-cli new --lang python3-http singleton \
  --prefix ttl.sh/openfaas-examples
faas-cli template store pull node24

faas-cli new --lang node24 singleton \
  --prefix ttl.sh/openfaas-examples

The example uses the public ttl.sh registry. Replace the prefix with your own registry for production use.

The full source code and stack.yaml files are available on GitHub for Go, Python, and Node.js.

Implement the function

Each implementation starts the simulated setup when the function process starts. The /ready path returns 200 only after the resource is available. Normal requests then reuse the resource.

handler.go:

package function

import (
    "fmt"
    "net/http"
    "time"
)

var expensiveResource string

func init() {
    // Initialize once when the function process starts, not on every request.
    expensiveResource = setupExpensiveResource()
}

func setupExpensiveResource() string {
    // A real function could create a database client, open a persistent
    // connection, or download a large dataset here.
    time.Sleep(2 * time.Second)
    return "The expensive resource is ready"
}

func Handle(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/ready" {
        fmt.Fprintln(w, "Ready")
        return
    }

    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    fmt.Fprintln(w, expensiveResource)
}

handler.py:

import time


def setup_expensive_resource():
    # A real function could create a database client, open a persistent
    # connection, or download a large dataset here.
    time.sleep(2)
    return "The expensive resource is ready"


# Initialize once when the function process starts, not on every request.
expensive_resource = setup_expensive_resource()


def handle(event, context):
    if event.path == "/ready":
        return {"statusCode": 200, "body": "Ready"}

    return {"statusCode": 200, "body": expensive_resource}

handler.js:

'use strict'

let expensiveResource
let resourceReady = false

async function setupExpensiveResource () {
  // A real function could create a database client, open a persistent
  // connection, or download a large dataset here.
  await new Promise(resolve => setTimeout(resolve, 2000))
  return 'The expensive resource is ready'
}

async function initialize () {
  expensiveResource = await setupExpensiveResource()
  resourceReady = true
}

// Initialize once when the function process starts, not on every request.
initialize()

module.exports = async (event, context) => {
  // The readiness probe holds traffic until initialization has completed.
  if (event.path === '/ready') {
    return respond(context, resourceReady ? 200 : 503,
      resourceReady ? 'Ready' : 'Initializing')
  }

  if (!resourceReady) {
    return respond(context, 503, 'Initializing')
  }

  return respond(context, 200, expensiveResource)
}

function respond (context, status, body) {
  return context
    .status(status)
    .headers({ 'Content-Type': 'text/plain; charset=utf-8' })
    .succeed(body)
}

Configure the singleton and readiness

stack.yaml:

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080
functions:
  singleton:
    lang: golang-middleware
    handler: ./singleton
    image: ttl.sh/openfaas-examples/singleton:latest
    labels:
      com.openfaas.scale.min: "1"
      com.openfaas.scale.max: "1"
    annotations:
      com.openfaas.ready.http.path: /ready
      com.openfaas.ready.http.initialDelaySeconds: 1
      com.openfaas.ready.http.periodSeconds: 1

stack.yaml:

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080
functions:
  singleton:
    lang: python3-http
    handler: ./singleton
    image: ttl.sh/openfaas-examples/python-singleton:latest
    labels:
      com.openfaas.scale.min: "1"
      com.openfaas.scale.max: "1"
    annotations:
      com.openfaas.ready.http.path: /ready
      com.openfaas.ready.http.initialDelaySeconds: 1
      com.openfaas.ready.http.periodSeconds: 1

stack.yaml:

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080
functions:
  singleton:
    lang: node24
    handler: ./singleton
    image: ttl.sh/openfaas-examples/node-singleton:latest
    labels:
      com.openfaas.scale.min: "1"
      com.openfaas.scale.max: "1"
    annotations:
      com.openfaas.ready.http.path: /ready
      com.openfaas.ready.http.initialDelaySeconds: 1
      com.openfaas.ready.http.periodSeconds: 1

Setting both replica limits to one disables horizontal scaling. With multiple replicas, every process would perform the setup and keep its own copy of the resource. See autoscaling for more detail.

The readiness annotations direct the platform probe to /ready. They prevent the replica from receiving traffic while initialization is still running. For more information, see Health and readiness for functions and custom HTTP health checks.

Deploy and invoke

Build and deploy the function:

faas-cli up --tag=sha

Confirm that the function is deployed and has one available replica:

faas-cli list -v

Invoke the function after it becomes ready:

curl http://127.0.0.1:8080/function/singleton

The function should return:

The expensive resource is ready

Singleton considerations

  • Process-local resources follow the replica lifecycle. Initialization runs again after a deployment, restart, or rescheduling, and local state is lost when the replica is replaced. Store durable state in external persistent storage.
  • A single replica can still process multiple requests concurrently, so shared resources must be safe for concurrent use. Set max_inflight: "1" to process one request at a time; it limits concurrency, not the replica count. See concurrent request limits.
  • Stateful connections such as WebSockets are another use of the Singleton pattern because their connection state remains in one process. See How to Integrate WebSockets with Serverless Functions and OpenFaaS.