diff --git a/docs/develop/go/index.mdx b/docs/develop/go/index.mdx index 422ddc32f1..51f71b6766 100644 --- a/docs/develop/go/index.mdx +++ b/docs/develop/go/index.mdx @@ -59,9 +59,9 @@ From there, you can dive deeper into any of the Temporal primitives to start bui ## [Workers](/develop/go/workers) -- [Worker processes](/develop/go/workers/run-worker-process) -- [Cloud Worker](/develop/go/workers/cloud-worker) +- [Run a Worker](/develop/go/workers/run-worker-process) - [Sessions](/develop/go/workers/sessions) +- [Serverless Workers](/develop/go/workers/serverless-workers) ## [Temporal Client](/develop/go/client) diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index 6543342254..db7785653e 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -552,7 +552,7 @@ See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree ## Make Nexus calls across Namespaces in Temporal Cloud {#nexus-calls-across-namespaces-temporal-cloud} -This section assumes you are already familiar with [how connect a Worker to Temporal Cloud](/develop/go/workers/cloud-worker). +This section assumes you are already familiar with [how to connect a Worker to Temporal Cloud](/develop/go/client/temporal-client#connect-to-temporal-cloud). The same [source code](https://github.com/temporalio/samples-go/tree/main/nexus) is used in this section, but the `tcld` CLI will be used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and handler Workers to their respective Temporal Cloud Namespaces. ### Install the latest `tcld` CLI and generate certificates diff --git a/docs/develop/go/workers/cloud-worker.mdx b/docs/develop/go/workers/cloud-worker.mdx deleted file mode 100644 index a05c06b8e7..0000000000 --- a/docs/develop/go/workers/cloud-worker.mdx +++ /dev/null @@ -1,200 +0,0 @@ ---- -id: cloud-worker -title: Cloud Worker - Go SDK -sidebar_label: Cloud Worker -description: This section explains Cloud Workers with the Go SDK -toc_max_heading_level: 4 -keywords: - - Go SDK -tags: - - Go SDK - - Temporal SDKs ---- - -## How to run a Temporal Cloud Worker {#run-a-temporal-cloud-worker} - -To run a Worker that uses [Temporal Cloud](/cloud), you need to provide additional connection and client options that include the following: - -- An address that includes your [Cloud Namespace Name](/namespaces) and a port number: `..tmprl.cloud:`. -- mTLS CA certificate. -- mTLS private key. - -For more information about managing and generating client certificates for Temporal Cloud, see [How to manage certificates in Temporal Cloud](/cloud/certificates). - -For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see [Temporal Customization Samples](https://github.com/temporalio/samples-server). - -To run a Worker that talks to Temporal Cloud, you need the following: - -- A compatible mTLS CA certificate and mTLS private key that has been added to your Namespace. - See [certificate requirements](/cloud/certificates#certificate-requirements). -- Your [Temporal Cloud Namespace Id](/cloud/namespaces#temporal-cloud-namespace-id), which includes your [Temporal Cloud Namespace Name](/cloud/namespaces#temporal-cloud-namespace-name) and the unique five- or six-digit [Temporal Cloud Account Id](/cloud/namespaces#temporal-cloud-account-id) that is appended to it. - This information can be found in the URL of your Namespace; for example, `https://cloud.temporal.io/namespaces/yournamespace.a2fx6/`. - Remember that the Namespace Id must include the Account Id: `yournamespace.a2fx6`. - -For more information about managing and generating client certificates for Temporal Cloud, see [How to manage certificates in Temporal Cloud](/cloud/certificates). - -For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see [Temporal Customization Samples](https://github.com/temporalio/samples-server). - -
- - View the source code - {' '} - in the context of the rest of the application code. -
- -```go -package main - -import ( - "crypto/tls" - "log" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" - - "documentation-samples-go/cloud" -) - -func main() { - // Get the key and cert from your env or local machine - clientKeyPath := "./secrets/yourkey.key" - clientCertPath := "./secrets/yourcert.pem" - // Specify the host and port of your Temporal Cloud Namespace - // Host and port format: namespace.unique_id.tmprl.cloud:port - hostPort := "..tmprl.cloud:7233" - namespace := "." - // Use the crypto/tls package to create a cert object - cert, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath) - if err != nil { - log.Fatalln("Unable to load cert and key pair.", err) - } - // Add the cert to the tls certificates in the ConnectionOptions of the Client - temporalClient, err := client.Dial(client.Options{ - HostPort: hostPort, - Namespace: namespace, - ConnectionOptions: client.ConnectionOptions{ - TLS: &tls.Config{Certificates: []tls.Certificate{cert}}, - }, - }) - if err != nil { - log.Fatalln("Unable to connect to Temporal Cloud.", err) - } - defer temporalClient.Close() - // Create a new Worker. - yourWorker := worker.New(temporalClient, "cloud-connection-example-go-task-queue", worker.Options{}) -// ... -} -``` - -### How to register types {#register-types} - -All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and Activity Types. - -If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. -However, the failure of the Task does not cause the associated Workflow Execution to fail. - -The `RegisterWorkflow()` and `RegisterActivity()` calls essentially create an in-memory mapping between the Workflow Types and their implementations, inside the Worker process. - -**Registering Activity `structs`** - -Per [Activity Definition](/develop/go/activities/basics#activity-definition) best practices, you might have an Activity struct that has multiple methods and fields. -When you use `RegisterActivity()` for an Activity struct, that Worker has access to all exported methods. - -**Registering multiple Types** - -To register multiple Activity Types and/or Workflow Types with the Worker Entity, just make multiple Activity registration calls, but make sure each Type name is unique: - -```go -w.RegisterActivity(ActivityA) -w.RegisterActivity(ActivityB) -w.RegisterActivity(ActivityC) -w.RegisterWorkflow(WorkflowA) -w.RegisterWorkflow(WorkflowB) -w.RegisterWorkflow(WorkflowC) -``` - -### How to set RegisterWorkflowOptions in Go {#registerworkflowoptions} - -Create an instance of [`RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/workflow#RegisterOptions) from the `go.temporal.io/sdk/workflow` package and pass it to the [`RegisterWorkflowWithOptions`](https://pkg.go.dev/go.temporal.io/sdk/worker#WorkflowRegistry) call when registering the Workflow Type with the Worker. - -- Used to set options for registering a Workflow - -| Field | Required | Type | -| ----------------------------------------------------------------- | -------- | -------- | -| [`Name`](#name) | No | `string` | -| [`DisableAlreadyRegisteredCheck`](#disablealreadyregisteredcheck) | No | `bool` | - -#### Name - -See [How to customize a Workflow Type in Go](/develop/go/workflows/basics#customize-workflow-type) - -#### DisableAlreadyRegisteredCheck - -Disables the check to see if the Workflow Type has already been registered. - -- Type: `bool` -- Default: `false` - -```go -// ... -w := worker.New(temporalClient, "your_task_queue_name", worker.Options{}) -registerOptions := workflow.RegisterOptions{ - DisableAlreadyRegisteredCheck: `false`, - // ... -} -w.RegisterWorkflowWithOptions(YourWorkflowDefinition, registerOptions) -// ... -``` - -### How to set RegisterActivityOptions in Go {#registeractivityoptions} - -Create an instance of [`RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/activity#RegisterOptions) from the `go.temporal.io/sdk/activity` package and pass it to the [`RegisterActivityWithOptions`](https://pkg.go.dev/go.temporal.io/sdk/worker#ActivityRegistry) call when registering the Activity Type with the Worker. - -Options for registering an Activity - -| Field | Required | Type | -| ----------------------------------------------------------------- | -------- | -------- | -| [`Name`](#name) | No | `string` | -| [`DisableAlreadyRegisteredCheck`](#disablealreadyregisteredcheck) | No | `bool` | -| [`SkipInvalidStructFunctions`](#skipinvalidstructfunctions) | No | `bool` | - -#### Name - -See [How to customize Activity Type in Go](/develop/go/activities/basics#customize-activity-type). - -#### DisableAlreadyRegisteredCheck - -Disables the check to see if the Activity has already been registered. - -- Type: `bool` -- Default: `false` - -```go -// ... -w := worker.New(temporalClient, "your_task_queue_name", worker.Options{}) -registerOptions := activity.RegisterOptions{ - DisableAlreadyRegisteredCheck: false, - // ... -} -w.RegisterActivityWithOptions(a.YourActivityDefinition, registerOptions) -// ... -``` - -#### SkipInvalidStructFunctions - -When registering a struct that has Activities, skip functions that are not valid. -If false, registration panics. - -- Type: `bool` -- Default: `false` - -```go -// ... -w := worker.New(temporalClient, "your_task_queue_name", worker.Options{}) -registerOptions := activity.RegisterOptions{ - SkipInvalidStructFunctions: false, - // ... -} -w.RegisterActivityWithOptions(a.YourActivityDefinition, registerOptions) -// ... -``` diff --git a/docs/develop/go/workers/index.mdx b/docs/develop/go/workers/index.mdx index 762280f3e8..06555948ac 100644 --- a/docs/develop/go/workers/index.mdx +++ b/docs/develop/go/workers/index.mdx @@ -17,6 +17,6 @@ import * as Components from '@site/src/components'; ## Workers -- [Worker processes](/develop/go/workers/run-worker-process) -- [Cloud Worker](/develop/go/workers/cloud-worker) +- [Run a Worker](/develop/go/workers/run-worker-process) - [Sessions](/develop/go/workers/sessions) +- [Serverless Workers](/develop/go/workers/serverless-workers) diff --git a/docs/develop/go/workers/run-worker-process.mdx b/docs/develop/go/workers/run-worker-process.mdx index 0ab03b28ab..b3ce5da1bd 100644 --- a/docs/develop/go/workers/run-worker-process.mdx +++ b/docs/develop/go/workers/run-worker-process.mdx @@ -1,8 +1,8 @@ --- id: run-worker-process -title: Run Worker processes - Go SDK -description: Shows how to run Worker processes with the Go SDK -sidebar_label: Worker processes +title: Run a Worker - Go SDK +description: Create and run a Temporal Worker using the Go SDK. +sidebar_label: Run a Worker slug: /develop/go/workers/run-worker-process toc_max_heading_level: 3 tags: @@ -11,37 +11,18 @@ tags: - Worker --- -## How to develop a Worker in Go {#develop-worker} +This page covers long-lived Workers that you host and run as persistent processes. +For Workers that run on serverless compute like AWS Lambda, see [Serverless Workers](/develop/go/workers/serverless-workers). -Create an instance of [`Worker`](https://pkg.go.dev/go.temporal.io/sdk/worker#Worker) by calling [`worker.New()`](https://pkg.go.dev/go.temporal.io/sdk/worker#New), available through the `go.temporal.io/sdk/worker` package, and pass it the following parameters: +## Create and run a Worker {#develop-worker} -1. An instance of the Temporal Go SDK `Client`. -1. The name of the Task Queue that it will poll. -1. An instance of `worker.Options`, which can be empty. +Create a [`Worker`](https://pkg.go.dev/go.temporal.io/sdk/worker#Worker) by calling [`worker.New()`](https://pkg.go.dev/go.temporal.io/sdk/worker#New) and passing: -Then, register the Workflow Types and the Activity Types that the Worker will be capable of executing. +1. A Temporal Client. +2. The name of the Task Queue to poll. +3. A [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) struct (can be empty for defaults). -Lastly, call either the `Start()` or the `Run()` method on the instance of the Worker. -Run accepts an interrupt channel as a parameter, so that the Worker can be stopped in the terminal. -Otherwise, the `Stop()` method must be called to stop the Worker. - -:::tip - -If you have [`gow`](https://github.com/mitranim/gow) installed, the Worker Process automatically "reloads" when you update the Worker file: - -```bash -go install github.com/mitranim/gow@latest -gow run worker/main.go # automatically reloads when file changes -``` - -::: - -
- - View the source code - {' '} - in the context of the rest of the application code. -
+Register your Workflow and Activity types, then call `Run()` to start polling. ```go package main @@ -49,475 +30,67 @@ package main import ( "log" - "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" - "go.temporal.io/sdk/workflow" - - "documentation-samples-go/yourapp" ) func main() { - // Create a Temporal Client - // A Temporal Client is a heavyweight object that should be created just once per process. - temporalClient, err := client.Dial(client.Options{}) + c, err := client.Dial(client.Options{}) if err != nil { log.Fatalln("Unable to create client", err) } - defer temporalClient.Close() - // Create a new Worker. - yourWorker := worker.New(temporalClient, "your-custom-task-queue-name", worker.Options{}) - // Register your Workflow Definitions with the Worker. - // Use the RegisterWorkflow or RegisterWorkflowWithOptions method for each Workflow registration. - yourWorker.RegisterWorkflow(yourapp.YourWorkflowDefinition) -// ... - // Register your Activity Definitions with the Worker. - // Use this technique for registering all Activities that are part of a struct and set the shared variable values. - message := "This could be a connection string or endpoint details" - number := 100 - activities := &yourapp.YourActivityObject{ - Message: &message, - Number: &number, - } - // Use the RegisterActivity or RegisterActivityWithOptions method for each Activity. - yourWorker.RegisterActivity(activities) -// ... - // Run the Worker - err = yourWorker.Run(worker.InterruptCh()) + defer c.Close() + + w := worker.New(c, "my-task-queue", worker.Options{}) + w.RegisterWorkflow(MyWorkflow) + w.RegisterActivity(MyActivity) + + err = w.Run(worker.InterruptCh()) if err != nil { log.Fatalln("Unable to start Worker", err) } } -// ... -``` - -### How to set WorkerOptions in Go {#workeroptions} - -Create an instance of [`Options`](https://pkg.go.dev/go.temporal.io/sdk/worker#Options) from the `go.temporal.io/sdk/worker` package, set any of the optional fields, and pass the instance to the [`New`](https://pkg.go.dev/go.temporal.io/sdk/worker#New) call. - -| Field | Required | Type | -| ------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- | -| [`MaxConcurrentActivityExecutionSize`](#maxconcurrentactivityexecutionsize) | No | `int` | -| [`WorkerActivitiesPerSecond`](#workeractivitiespersecond) | No | `float64` | -| [`MaxConcurrentLocalActivityExecutionSize`](#maxconcurrentlocalactivityexecutionsize) | No | `int` | -| [`WorkerLocalActivitiesPerSecond`](#workerlocalactivitiespersecond) | No | `float64` | -| [`TaskQueueActivitiesPerSecond`](#taskqueueactivitiespersecond) | No | `float64` | -| [`MaxConcurrentActivityTaskPollers`](#maxconcurrentactivitytaskpollers) | No | `int` | -| [`MaxConcurrentWorkflowTaskExecutionSize`](#maxconcurrentworkflowtaskexecutionsize) | No | `int` | -| [`MaxConcurrentWorkflowTaskPollers`](#maxconcurrentworkflowtaskpollers) | No | `int` | -| [`EnableLoggingInReplay`](#enablelogginginreplay) | No | `bool` | -| [`DisableStickyExecution`](#disablestickyexecution) | No | `bool` | -| [`StickyScheduleToStartTimeout`](#stickyscheduletostarttimeout) | No | [`time.Duration`](https://pkg.go.dev/time#Duration) | -| [`BackgroundActivityContext`](#backgroundactivitycontext) | No | [`context.Context`](https://pkg.go.dev/context#Context) | -| [`WorkflowPanicPolicy`](#workflowpanicpolicy) | No | [`WorkflowPanicPolicy`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowPanicPolicy) | -| [`WorkerStopTimeout`](#workerstoptimeout) | No | [`time.Duration`](https://pkg.go.dev/time#Duration) | -| [`EnableSessionWorker`](#enablesessionworker) | No | `bool` | -| [`MaxConcurrentSessionExecutionSize`](#maxconcurrentsessionexecutionsize) | No | `int` | -| [`WorkflowInterceptorChainFactories`](#workflowinterceptorchainfactories) | No | [`[]WorkflowInterceptor`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowInterceptor) | -| [`LocalActivityWorkerOnly`](#localactivityworkeronly) | No | `bool` | -| [`Identity`](#identity) | No | `string` | -| [`DeadlockDetectionTimeout`](#deadlockdetectiontimeout) | No | [`time.Duration`](https://pkg.go.dev/time#Duration) | - -#### MaxConcurrentActivityExecutionSize - -Sets the maximum concurrent Activity Executions for the Worker. - -- Type: `int` -- Default: `1000` - -A value of `0` sets to the default. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentActivityExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### WorkerActivitiesPerSecond - -Rate limits the number of Activity Task Executions started per second for the Worker. - -- Type: `float64` -- Default: `100000` - -A value of `0` sets to the default. - -Intended use case is to limit resources used by the Worker. - -Notice that the value type is a float so that the value can be less than 1 if needed. -For example, if set to 0.1, Activity Task Executions will happen once every ten seconds. -This can be used to protect downstream services from flooding with requests. - -```go -// ... -workerOptions := worker.Options{ - WorkerActivitiesPerSecond: 100000, - // .. -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentLocalActivityExecutionSize - -Set the maximum concurrent [Local Activity Executions](/local-activity) for the Worker. - -- Type: `int` -- Default: `1000` - -A value of `0` sets to the default value. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentLocalActivityExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### WorkerLocalActivitiesPerSecond - -Rate limits the number of Local Activity Executions per second executed for the Worker. - -- Type: `float64` -- Default: `100000` - -A value of `0` sets to the default value. - -Intended use case is to limit resources used by the Worker. - -Notice that the value type is a float so that the value can be less than 1 if needed. -For example, if set to 0.1, Local Activity Task Executions will happen once every ten seconds. -This can be used to protect downstream services from flooding with requests. - -```go -// ... -workerOptions := worker.Options{ - WorkerLocalActivitiesPerSecond: 100000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### TaskQueueActivitiesPerSecond - -Rate limits the number of Activity Executions that can be started per second. - -- Type: `float64` -- Default: `100000` - -A value of `0` sets to the default value. - -This rate is managed by the Temporal Service and limits the Activity Tasks per second for the entire Task Queue. This is in contrast to [`WorkerActivityTasksPerSecond`](#workeractivitiespersecond) controls Activities only per Worker. - -Notice that the number is represented in float, so that you can set it to less than 1 if needed. -For example, set the number to 0.1 means you want your Activity to be executed once for every 10 seconds. -This can be used to protect down stream services from flooding. - -```go -// ... -workerOptions := worker.Options{ - TaskQueueActivitiesPerSecond: 100000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentActivityTaskPollers - -Sets the maximum number of goroutines to concurrently poll the Task Queue for Activity Tasks. - -- Type: `int` -- Default: `2` - -Changing this value will affect the rate at which the Worker is able to consume Activity Tasks from the Task Queue. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentActivityTaskPollers: 2, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentWorkflowTaskExecutionSize - -Sets the maximum number of concurrent Workflow Task Executions the Worker can have. - -- Type: `int` -- Default: `1000` - -A value of `0` sets to the default value. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentWorkflowTaskExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentWorkflowTaskPollers - -Sets the maximum number of goroutines that will concurrently poll the Task Queue for Workflow Tasks. - -- Type: `int` -- Default: `2` - -Changing this value will affect the rate at which the Worker is able to consume Workflow Tasks from the Task Queue. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentWorkflowTaskPollers: 2, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### EnableLoggingInReplay - -Set to enable logging in Workflow Execution replays. - -- type: `bool` -- Default: `false` - -In Workflow Definitions you can use [`workflow.GetLogger(ctx)`](https://pkg.go.dev/go.temporal.io/sdk/workflow#GetLogger) to write logs. -By default, the logger will skip logging during replays, so you do not see duplicate logs. - -This is only really useful for debugging purposes. - -```go -// ... -workerOptions := worker.Options{ - EnableLoggingInReplay: false, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... ``` -#### DisableStickyExecution - -:::caution Deprecated - -When DisableStickyExecution is `true` it can harm performance. -It will be removed soon. -See [`SetStickyWorkflowCacheSize`](https://pkg.go.dev/go.temporal.io/sdk/worker#SetStickyWorkflowCacheSize) instead. - -::: - -Set to disable Sticky Executions - -- Type: `bool` -- Default: `false` - -Sticky Execution runs Workflow Tasks of a Workflow Execution on same host (could be a different Worker, as long as it is on the same host). -This is an optimization for Workflow Executions. -When sticky execution is enabled, Worker keeps the Workflow state in memory. -New Workflow Task contains the new history events will be dispatched to the same Worker. -If this Worker crashes, the sticky Workflow Task will timeout after `StickyScheduleToStartTimeout`, and Temporal Service will clear the stickiness for that Workflow Execution and automatically reschedule a new Workflow Task that is available for any Worker to pick up and resume the progress. - -```go -// ... -workerOptions := worker.Options{ - StickyScheduleToStartTimeout: time.Second(5), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` +`Run()` accepts an interrupt channel so the Worker shuts down on `SIGINT` or `SIGTERM`. +You can also call `Start()` and `Stop()` separately for more control over the lifecycle. -#### StickyScheduleToStartTimeout - -Sets the Sticky Execution Schedule-To-Start Timeout for Workflow Tasks. - -- Type: [`time.Duration`](https://pkg.go.dev/time#Duration) -- Default value is `5` +:::tip -The resolution is in seconds. +If you have [`gow`](https://github.com/mitranim/gow) installed, the Worker automatically reloads when you update the file: -```go -// ... -workerOptions := worker.Options{ - StickyScheduleToStartTimeout: time.Second(5), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... +```bash +go install github.com/mitranim/gow@latest +gow run worker/main.go ``` -#### BackgroundActivityContext - -:::caution Not recommended - -This method of passing dependencies between Activity Task Executions is not recommended anymore. - -Instead, we recommend using a struct with fields that contain dependencies and develop Activity Definitions as struct methods and then pass all the dependencies on the structure initialization. - -- [How to develop an Activity Definition using the Go SDK](/develop/go/activities/basics#activity-definition) - ::: -- Type: [`context.Context`](https://pkg.go.dev/context#Context) - -Sets the background `context.Context` for all Activity Types registered with the Worker. - -The context can be used to pass external dependencies such as database connections to Activity Task Executions. +## Connect to Temporal Cloud {#connect-to-temporal-cloud} -```go -// ... -ctx := context.WithValue(context.Background(), "your-key", "your-value") -workerOptions := worker.Options{ - BackgroundActivityContext: ctx, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` +To run a Worker against Temporal Cloud, configure the client connection with your Namespace address and authentication credentials. +See [Connect to Temporal Cloud](/develop/go/client/temporal-client#connect-to-temporal-cloud) for setup instructions. -#### WorkflowPanicPolicy +## Register Workflows and Activities {#register-types} -Sets how the Workflow Worker handles a non-deterministic Workflow Execution History Event and other panics from Workflow Definition code. +All Workers listening to the same Task Queue must be registered to handle the same Workflow Types and Activity Types. +If a Worker polls a Task for a type it does not know about, the Task fails. The Workflow Execution itself does not fail. -- Type: [`WorkflowPanicPolicy`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowPanicPolicy) -- Default: `BlockWorkflow` +Use `RegisterWorkflow()` and `RegisterActivity()` to register types. +To register an Activity struct with multiple methods, pass the struct. The Worker gets access to all exported methods. ```go -// ... -workerOptions := worker.Options{ - DisableStickyExecution: internal.BlockWorkflow, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... +w.RegisterWorkflow(WorkflowA) +w.RegisterWorkflow(WorkflowB) +w.RegisterActivity(&MyActivities{}) ``` -#### WorkerStopTimeout - -Sets the Worker's graceful stop timeout +To customize the registered name or other options, use `RegisterWorkflowWithOptions()` or `RegisterActivityWithOptions()`. +See [`workflow.RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/workflow#RegisterOptions) and [`activity.RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/activity#RegisterOptions). -- Type: [`time.Duration`](https://pkg.go.dev/time#Duration) -- Default: `0` +## Worker options {#worker-options} -Value resolution is in seconds. +Pass a [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) struct to `worker.New()` to configure concurrency limits, pollers, timeouts, and other Worker behavior. +An empty struct uses defaults that work for most cases. -```go -// ... -workerOptions := worker.Options{ - WorkerStopTimeout: time.Second(0), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### EnableSessionWorker - -Enables Sessions for Activity Workers. - -- Type: `bool` -- Default: `false` - -When `true` the Activity Worker creates a Session to sequentially process Activity Tasks for the given Task Queue. - -```go -// ... -workerOptions := worker.Options{ - EnableSessionWorker: true, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentSessionExecutionSize - -Sets the maximum number of concurrent Sessions that the Worker can support. - -- Type: `int` -- Default: 1000 - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentSessionExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### WorkflowInterceptorChainFactories - -Specifies the factories used to instantiate the Workflow interceptor chain. - -- Type: [`[]WorkflowInterceptor`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowInterceptor) - -The chain is instantiated for each replay of a Workflow Execution. - -#### LocalActivityWorkerOnly - -Sets the Worker to only handle Workflow Tasks and local Activity Tasks. - -- Type: `bool` -- Default: `false` - -```go -// ... -workerOptions := worker.Options{ - LocalActivityWorkerOnly: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### Identity - -Sets the Temporal Client-level Identity value, overwriting the existing one. - -- Type: string -- Default: client identity - -```go -// ... -workerOptions := worker.Options{ - Identity: "your_custom_identity", - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### DeadlockDetectionTimeout - -Sets the maximum time that a Workflow Task can execute for. - -- Type: [`time.Duration`](https://pkg.go.dev/time#Duration) -- Default: 1 - -Resolution is in seconds. - -```go -// ... -workerOptions := worker.Options{ - DeadlockDetectionTimeout: time.Second(1), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` +For the full list of options and their defaults, see the [Go SDK reference](https://pkg.go.dev/go.temporal.io/sdk@v1.42.0/internal#WorkerOptions). diff --git a/docs/develop/go/workers/serverless-workers/aws-lambda.mdx b/docs/develop/go/workers/serverless-workers/aws-lambda.mdx new file mode 100644 index 0000000000..447b7ce9c4 --- /dev/null +++ b/docs/develop/go/workers/serverless-workers/aws-lambda.mdx @@ -0,0 +1,240 @@ +--- +id: aws-lambda +title: Serverless Workers on AWS Lambda - Go SDK +sidebar_label: Serverless Workers on AWS Lambda +description: Write a Temporal Worker that runs on AWS Lambda using the Go SDK lambdaworker package. +slug: /develop/go/workers/serverless-workers/aws-lambda +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - go sdk + - worker + - serverless worker +tags: + - Workers + - Go SDK + - Serverless + - AWS Lambda +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +The `lambdaworker` package lets you run a Temporal Serverless Worker on AWS Lambda. +Deploy your Worker code as a Lambda function, and Temporal Cloud invokes it when Tasks arrive. +Each invocation starts a Worker, polls for Tasks, then gracefully shuts down before a configurable invocation deadline. +You register Workflows and Activities the same way you would with a standard Worker. + +For a full end-to-end deployment guide covering AWS IAM setup, compute configuration, and verification, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Create and run a Worker in Lambda {#create-and-run} + +Use the `RunWorker` function to start a Lambda-based Worker. +Pass a `WorkerDeploymentVersion` and a callback that registers your Workflows and Activities. + + +[samples-go/lambda-worker/worker/main.go](https://github.com/temporalio/samples-go/blob/lambda-worker/samples-go/lambda-worker/worker/main.go) +```go +package main + +import ( + greeting "github.com/temporalio/samples-go/lambda-worker/greeting" + + lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" +// ... + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +func main() { + lambdaworker.RunWorker(worker.WorkerDeploymentVersion{ + DeploymentName: "my-app", + BuildID: "build-1", + }, func(opts *lambdaworker.Options) error { + opts.TaskQueue = "serverless-task-queue-1" + +// ... + + opts.RegisterWorkflowWithOptions(greeting.SampleWorkflow, workflow.RegisterOptions{ + VersioningBehavior: workflow.VersioningBehaviorPinned, + }) + opts.RegisterActivity(greeting.HelloActivity) + + return nil + }) +} +``` + + +The `WorkerDeploymentVersion` is required. +Worker Deployment Versioning is always enabled for Serverless Workers. +Each Workflow must have a [versioning behavior](/worker-versioning#versioning-behaviors), either `AutoUpgrade` or `Pinned`. +Set it per-Workflow at registration time, or set a worker-level default with `DefaultVersioningBehavior` in `DeploymentOptions`. + +The `Options` callback gives you access to the same registration methods you use with a traditional Worker: `RegisterWorkflow`, `RegisterWorkflowWithOptions`, `RegisterActivity`, `RegisterActivityWithOptions`, and `RegisterNexusService`. + +## Configure the Temporal connection {#configure-connection} + +The `lambdaworker` package automatically loads Temporal client configuration from a TOML config file and environment variables. Refer to [Environment Configuration](/develop/environment-configuration) for more details. + +The config file is resolved in order: + +1. `TEMPORAL_CONFIG_FILE` environment variable, if set. +2. `temporal.toml` in `$LAMBDA_TASK_ROOT` (typically `/var/task`). +3. `temporal.toml` in the current working directory. + +The file is optional. If absent, only environment variables are used. + +Encrypt sensitive values like TLS keys or API keys. Refer to [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) for options. + +## Adjust Worker defaults for Lambda {#lambda-tuned-defaults} + +The `lambdaworker` package applies conservative defaults suited to short-lived Lambda invocations. +These differ from standard Worker defaults to avoid overcommitting resources in a constrained environment. +Except for `ShutdownDeadlineBuffer`, these are the same [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk@v1.42.0/internal#WorkerOptions) available to any Temporal Worker, just with lower values for Lambda's constrained environment. + +| Setting | Lambda default | +|---|---| +| `MaxConcurrentActivityExecutionSize` | 2 | +| `MaxConcurrentWorkflowTaskExecutionSize` | 10 | +| `MaxConcurrentLocalActivityExecutionSize` | 2 | +| `MaxConcurrentNexusTaskExecutionSize` | 5 | +| `MaxConcurrentActivityTaskPollers` | 1 | +| `MaxConcurrentWorkflowTaskPollers` | 2 | +| `MaxConcurrentNexusTaskPollers` | 1 | +| `WorkerStopTimeout` | 5 seconds | +| `DisableEagerActivities` | Always true | +| Sticky cache size | 100 | +| `ShutdownDeadlineBuffer` | 7 seconds | + +`DisableEagerActivities` is always true and cannot be overridden. +Eager Activities require a persistent connection, which Lambda invocations don't maintain. + +`ShutdownDeadlineBuffer` is specific to the `lambdaworker` package. +It controls how much time before the Lambda deadline the Worker begins its graceful shutdown. +The default is `WorkerStopTimeout` + 2 seconds. + +If your Worker handles long-running Activities, increase `WorkerStopTimeout`, `ShutdownDeadlineBuffer`, and the Lambda invocation deadline (`--timeout`) together. +For guidance on how these values relate, see [Tuning for long-running Activities](/serverless-workers#tuning-for-long-running-activities). + +## Add observability with OpenTelemetry {#add-observability} + +The `lambdaworker/otel` sub-package provides OpenTelemetry integration with defaults configured for the [AWS Distro for OpenTelemetry (ADOT)](https://aws-otel.github.io/docs/getting-started/lambda) Lambda layer. +With this enabled, the Worker emits SDK metrics and distributed traces for Workflow and Activity executions. +The ADOT Lambda layer collects this telemetry and can forward traces to AWS X-Ray and metrics to Amazon CloudWatch. + +The underlying metrics and traces are the same ones the Go SDK emits in any environment. +For general observability concepts and the full list of available metrics, see [Observability - Go SDK](/develop/go/platform/observability) and the [SDK metrics reference](/references/sdk-metrics). + + +[samples-go/lambda-worker/worker/main.go](https://github.com/temporalio/samples-go/blob/lambda-worker/samples-go/lambda-worker/worker/main.go) +```go +package main + +import ( + greeting "github.com/temporalio/samples-go/lambda-worker/greeting" + + lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" + otel "go.temporal.io/sdk/contrib/aws/lambdaworker/otel" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +func main() { + lambdaworker.RunWorker(worker.WorkerDeploymentVersion{ + DeploymentName: "my-app", + BuildID: "build-1", + }, func(opts *lambdaworker.Options) error { + opts.TaskQueue = "serverless-task-queue-1" + + if err := otel.ApplyDefaults(opts, &opts.ClientOptions, otel.Options{}); err != nil { + return err + } + + opts.RegisterWorkflowWithOptions(greeting.SampleWorkflow, workflow.RegisterOptions{ + VersioningBehavior: workflow.VersioningBehaviorPinned, + }) + opts.RegisterActivity(greeting.HelloActivity) + + return nil + }) +} +``` + + +`ApplyDefaults` configures both metrics and tracing. +By default, telemetry is sent to `localhost:4317`, which is the ADOT Lambda layer's default collector endpoint. + +To collect this telemetry, attach the [ADOT Collector layer](https://aws-otel.github.io/docs/getting-started/lambda) to your Lambda function. +The layer runs a collector sidecar that receives telemetry on `localhost:4317` and forwards traces to X-Ray and metrics to CloudWatch. +Go does not need a language-specific ADOT layer because the OTel SDK is compiled into the binary. + +The default Collector configuration does not route OpenTelemetry Protocol (OTLP) data to the traces pipeline. +You must provide a custom Collector configuration that wires the OTLP receiver to both the traces and metrics pipelines. +Bundle the following `otel-collector-config.yaml` in your Lambda deployment package: + + +[samples-go/lambda-worker/otel-collector-config.yaml](https://github.com/temporalio/samples-go/blob/lambda-worker/samples-go/lambda-worker/otel-collector-config.yaml) +```yaml +receivers: + otlp: + protocols: + grpc: + endpoint: "localhost:4317" + http: + endpoint: "localhost:4318" + +exporters: + debug: + awsxray: + region: us-west-2 + awsemf: + # AWS EMF exporter for metrics + # These are example configurations + namespace: TemporalWorkerMetrics + log_group_name: /aws/lambda/ + region: us-west-2 + dimension_rollup_option: NoDimensionRollup + resource_to_telemetry_conversion: + enabled: true + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [awsxray, debug] + metrics: + receivers: [otlp] + exporters: [awsemf] + telemetry: + logs: + level: debug + metrics: + address: localhost:8888 +``` + + +Set the following environment variable on the Lambda function to point the Collector at the bundled config: + +- `OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml` + +Enable X-Ray active tracing on the Lambda function: + +```bash +aws lambda update-function-configuration \ + --function-name \ + --tracing-config Mode=Active +``` + +The Lambda execution role must have permissions to write to X-Ray and CloudWatch. +Add `xray:PutTraceSegments`, `xray:PutTelemetryRecords`, and `cloudwatch:PutMetricData` permissions to the execution role. +Without these permissions, the Collector fails silently and no telemetry appears. + +If you only need metrics or tracing, use `otel.ApplyMetrics` or `otel.ApplyTracing` individually. diff --git a/docs/develop/go/workers/serverless-workers/index.mdx b/docs/develop/go/workers/serverless-workers/index.mdx new file mode 100644 index 0000000000..70ed7e9ec8 --- /dev/null +++ b/docs/develop/go/workers/serverless-workers/index.mdx @@ -0,0 +1,26 @@ +--- +id: index +title: Serverless Workers - Go SDK +sidebar_label: Serverless Workers +description: Write Temporal Workers that run on serverless compute using the Go SDK. +slug: /develop/go/workers/serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - go sdk + - worker +tags: + - Workers + - Go SDK + - Serverless +--- + +Serverless Workers run on ephemeral, on-demand compute rather than long-lived processes. +Temporal invokes the Worker when Tasks arrive, and the Worker shuts down when the work is done. + +For a general overview of how Serverless Workers work, see [Serverless Workers](/serverless-workers). +For the end-to-end deployment guide, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Supported providers + +- [**AWS Lambda**](/develop/go/workers/serverless-workers/aws-lambda) - Use the `lambdaworker` package to run a Worker as a Lambda function. Covers setup, configuration, Lambda-tuned defaults, observability, and the invocation lifecycle. diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index afad0f58d9..442e212079 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -221,7 +221,7 @@ Workflow IDs should typically be business-meaningful IDs and are used to dedupe A Nexus Operation can only take one input parameter. If you want a Nexus Operation to start a Workflow that takes multiple arguments use the `ctx.start_workflow` method. -[nexus_multiple_args/handler/service_handler.py](https://github.com/temporalio/samples-python/blob/main/nexus_multiple_args/handler/service_handler.py) +[nexus_multiple_args/handler/service_handler.py](https://github.com/temporalio/samples-python/blob/lambda-worker/nexus_multiple_args/handler/service_handler.py) ```py @nexusrpc.handler.service_handler(service=MyNexusService) class MyNexusServiceHandler: diff --git a/docs/develop/python/workers/serverless-workers/aws-lambda.mdx b/docs/develop/python/workers/serverless-workers/aws-lambda.mdx new file mode 100644 index 0000000000..0d376c2b88 --- /dev/null +++ b/docs/develop/python/workers/serverless-workers/aws-lambda.mdx @@ -0,0 +1,232 @@ +--- +id: aws-lambda +title: Serverless Workers on AWS Lambda - Python SDK +sidebar_label: Serverless Workers on AWS Lambda +description: Write a Temporal Worker that runs on AWS Lambda using the Python SDK lambda_worker package. +slug: /develop/python/workers/serverless-workers/aws-lambda +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - python sdk + - worker + - serverless worker +tags: + - Workers + - Python SDK + - Serverless + - AWS Lambda +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +The `lambda_worker` contrib package lets you run a Temporal Serverless Worker on AWS Lambda. +Deploy your Worker code as a Lambda function, and Temporal Cloud invokes it when Tasks arrive. +Each invocation starts a Worker, polls for Tasks, then gracefully shuts down before a configurable invocation deadline. +You register Workflows and Activities the same way you would with a standard Worker. + +For a full end-to-end deployment guide covering AWS IAM setup, compute configuration, and verification, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Create and run a Worker in Lambda {#create-and-run} + +Use the `run_worker` function to create a Lambda handler that runs a Temporal Worker. +Pass a `WorkerDeploymentVersion` and a configure callback that registers your Workflows and Activities. + + +[lambda_worker/lambda_function.py](https://github.com/temporalio/samples-python/blob/lambda-worker/lambda_worker/lambda_function.py) +```py {15-18} +from activities import hello_activity +from temporalio.common import WorkerDeploymentVersion +from temporalio.contrib.aws.lambda_worker import LambdaWorkerConfig, run_worker +# ... +from workflows import TASK_QUEUE, SampleWorkflow + + +def configure(config: LambdaWorkerConfig) -> None: + config.worker_config["task_queue"] = TASK_QUEUE + config.worker_config["workflows"] = [SampleWorkflow] + config.worker_config["activities"] = [hello_activity] +# ... + + +lambda_handler = run_worker( + WorkerDeploymentVersion(deployment_name="my-app", build_id="build-1"), + configure, +) +``` + + +`run_worker` takes a `WorkerDeploymentVersion` and a configure callback, and returns a Lambda handler. +The `WorkerDeploymentVersion` identifies the [Worker Deployment](/worker-versioning#deployments) and [Build ID](/worker-versioning#deployment-versions) for this Worker. +The deployment name groups related Workers across versions, and the Build Id identifies a specific release of your Worker code. +Worker Versioning is required for Serverless Workers. + +The `configure` callback receives a `LambdaWorkerConfig` dataclass with fields pre-populated with Lambda-appropriate defaults. +Set the Task Queue, Workflows, and Activities through `worker_config`, which accepts the same keyword arguments as the `Worker` constructor. + +Each Workflow must have a [versioning behavior](/worker-versioning#versioning-behaviors), either `PINNED` or `AUTO_UPGRADE`. +Set it per-Workflow in the `@workflow.defn` decorator, or set a worker-level default with `default_versioning_behavior` in the worker config. + +```python {5} +from temporalio import workflow +from temporalio.common import VersioningBehavior + + +@workflow.defn(versioning_behavior=VersioningBehavior.PINNED) +class MyWorkflow: + @workflow.run + async def run(self, input: str) -> str: + ... +``` + +## Configure the Temporal connection {#configure-connection} + +The `lambda_worker` package automatically loads Temporal client configuration from a TOML config file and environment variables. Refer to [Environment Configuration](/develop/environment-configuration) for more details. + +The config file is resolved in order: + +1. `TEMPORAL_CONFIG_FILE` environment variable, if set. +2. `temporal.toml` in `$LAMBDA_TASK_ROOT` (typically `/var/task`). +3. `temporal.toml` in the current working directory. + +The file is optional. If absent, only environment variables are used. + +Encrypt sensitive values like TLS keys or API keys. Refer to [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) for options. + +## Adjust Worker defaults for Lambda {#lambda-tuned-defaults} + +The `lambda_worker` package applies conservative defaults suited to short-lived Lambda invocations. +These differ from standard Worker defaults to avoid overcommitting resources in a constrained environment. + +| Setting | Lambda default | +|---|---| +| `max_concurrent_activities` | 2 | +| `max_concurrent_workflow_tasks` | 10 | +| `max_concurrent_local_activities` | 2 | +| `max_concurrent_nexus_tasks` | 5 | +| `workflow_task_poller_behavior` | `SimpleMaximum(2)` | +| `activity_task_poller_behavior` | `SimpleMaximum(1)` | +| `nexus_task_poller_behavior` | `SimpleMaximum(1)` | +| `graceful_shutdown_timeout` | 5 seconds | +| `max_cached_workflows` | 30 | +| `disable_eager_activity_execution` | Always `True` | +| `shutdown_deadline_buffer` | 7 seconds | + +`disable_eager_activity_execution` is always `True` and cannot be overridden. +Eager Activities require a persistent connection, which Lambda invocations don't maintain. + +`shutdown_deadline_buffer` is specific to the `lambda_worker` package. +It controls how much time before the Lambda deadline the Worker begins its graceful shutdown. +The default is `graceful_shutdown_timeout` + 2 seconds. + +If your Worker handles long-running Activities, increase `graceful_shutdown_timeout`, `shutdown_deadline_buffer`, and the Lambda invocation deadline (`--timeout`) together. +For guidance on how these values relate, see [Tuning for long-running Activities](/serverless-workers#tuning-for-long-running-activities). + +## Add observability with OpenTelemetry {#add-observability} + +The `lambda_worker.otel` module provides OpenTelemetry integration with defaults configured for the [AWS Distro for OpenTelemetry (ADOT)](https://aws-otel.github.io/docs/getting-started/lambda) Lambda layer. +With this enabled, the Worker emits SDK metrics and distributed traces for Workflow and Activity executions. +The ADOT Lambda layer collects this telemetry and can forward traces to AWS X-Ray and metrics to Amazon CloudWatch. + +The underlying metrics and traces are the same ones the Python SDK emits in any environment. +For general observability concepts and the full list of available metrics, see [Observability - Python SDK](/develop/python/platform/observability) and the [SDK metrics reference](/references/sdk-metrics). + + +[lambda_worker/lambda_function.py](https://github.com/temporalio/samples-python/blob/lambda-worker/lambda_worker/lambda_function.py) +```py +from activities import hello_activity +from temporalio.common import WorkerDeploymentVersion +from temporalio.contrib.aws.lambda_worker import LambdaWorkerConfig, run_worker +from temporalio.contrib.aws.lambda_worker.otel import apply_defaults +from workflows import TASK_QUEUE, SampleWorkflow + + +def configure(config: LambdaWorkerConfig) -> None: + config.worker_config["task_queue"] = TASK_QUEUE + config.worker_config["workflows"] = [SampleWorkflow] + config.worker_config["activities"] = [hello_activity] + apply_defaults(config) + + +lambda_handler = run_worker( + WorkerDeploymentVersion(deployment_name="my-app", build_id="build-1"), + configure, +) +``` + + +`apply_defaults` configures both metrics and tracing. +By default, telemetry is sent to `localhost:4317`, which is the ADOT Lambda layer's default collector endpoint. + +To collect this telemetry, attach the [ADOT Python Lambda layer](https://aws-otel.github.io/docs/getting-started/lambda/lambda-python) to your Lambda function. +The layer includes both auto-instrumentation and an OpenTelemetry Collector that receives telemetry on `localhost:4317` and forwards traces to AWS X-Ray and metrics to Amazon CloudWatch. + +The default Collector configuration does not route OpenTelemetry Protocol (OTLP) data to the traces pipeline. +You must provide a custom Collector configuration that wires the OTLP receiver to both the traces and metrics pipelines. +Bundle the following `otel-collector-config.yaml` in your Lambda deployment package: + + +[lambda_worker/otel-collector-config.yaml](https://github.com/temporalio/samples-python/blob/lambda-worker/lambda_worker/otel-collector-config.yaml) +```yaml +receivers: + otlp: + protocols: + grpc: + endpoint: "localhost:4317" + http: + endpoint: "localhost:4318" + +exporters: + debug: + awsxray: + region: us-west-2 + awsemf: + # AWS EMF exporter for metrics + # These are example configurations + namespace: TemporalWorkerMetrics + log_group_name: /aws/lambda/ + region: us-west-2 + dimension_rollup_option: NoDimensionRollup + resource_to_telemetry_conversion: + enabled: true + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [awsxray, debug] + metrics: + receivers: [otlp] + exporters: [awsemf] + telemetry: + logs: + level: debug + metrics: + address: localhost:8888 +``` + + +Set the following environment variable on the Lambda function: + +- `OPENTELEMETRY_COLLECTOR_CONFIG_FILE=/var/task/otel-collector-config.yaml` + +Enable X-Ray active tracing on the Lambda function: + +```bash +aws lambda update-function-configuration \ + --function-name \ + --tracing-config Mode=Active +``` + +The Lambda execution role must have permissions to write to X-Ray and CloudWatch. +Attach the [`AWSXRayDaemonWriteAccess`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSXRayDaemonWriteAccess.html) managed policy, or add `xray:PutTraceSegments`, `xray:PutTelemetryRecords`, and `cloudwatch:PutMetricData` permissions. +Without these permissions, the Collector fails silently and no telemetry appears. + +If you only need metrics or tracing, use `build_metrics_telemetry_config` or `apply_tracing` individually. diff --git a/docs/develop/python/workers/serverless-workers/index.mdx b/docs/develop/python/workers/serverless-workers/index.mdx new file mode 100644 index 0000000000..eaa30b5c12 --- /dev/null +++ b/docs/develop/python/workers/serverless-workers/index.mdx @@ -0,0 +1,26 @@ +--- +id: index +title: Serverless Workers - Python SDK +sidebar_label: Serverless Workers +description: Write Temporal Workers that run on serverless compute using the Python SDK. +slug: /develop/python/workers/serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - python sdk + - worker +tags: + - Workers + - Python SDK + - Serverless +--- + +Serverless Workers run on ephemeral, on-demand compute rather than long-lived processes. +Temporal invokes the Worker when Tasks arrive, and the Worker shuts down when the work is done. + +For a general overview of how Serverless Workers work, see [Serverless Workers](/serverless-workers). +For the end-to-end deployment guide, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Supported providers + +- [**AWS Lambda**](/develop/python/workers/serverless-workers/aws-lambda) - Use the `lambda_worker` contrib package to run a Worker as a Lambda function. Covers setup, configuration, Lambda-tuned defaults, and observability. diff --git a/docs/develop/typescript/integrations/ai-sdk.mdx b/docs/develop/typescript/integrations/ai-sdk.mdx index 2315d0f8f8..9ad7acde0a 100644 --- a/docs/develop/typescript/integrations/ai-sdk.mdx +++ b/docs/develop/typescript/integrations/ai-sdk.mdx @@ -164,7 +164,6 @@ from the tool function. The following is an example of an Activity that gets the export async function getWeather(input: { location: string; }): Promise<{ city: string; temperatureRange: string; conditions: string }> { - console.log('Activity execution'); return { city: input.location, temperatureRange: '14-20C', diff --git a/docs/develop/typescript/workers/serverless-workers/aws-lambda.mdx b/docs/develop/typescript/workers/serverless-workers/aws-lambda.mdx new file mode 100644 index 0000000000..bdcd301989 --- /dev/null +++ b/docs/develop/typescript/workers/serverless-workers/aws-lambda.mdx @@ -0,0 +1,234 @@ +--- +id: aws-lambda +title: Serverless Workers on AWS Lambda - TypeScript SDK +sidebar_label: Serverless Workers on AWS Lambda +description: Write a Temporal Worker that runs on AWS Lambda using the TypeScript SDK @temporalio/lambda-worker package. +slug: /develop/typescript/workers/serverless-workers/aws-lambda +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - typescript sdk + - worker + - serverless worker +tags: + - Workers + - TypeScript SDK + - Serverless + - AWS Lambda +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +The `@temporalio/lambda-worker` package lets you run a Temporal Serverless Worker on AWS Lambda. +Deploy your Worker code as a Lambda function, and Temporal Cloud invokes it when Tasks arrive. +Each invocation starts a Worker, polls for Tasks, then gracefully shuts down before a configurable invocation deadline. +You register Workflows and Activities the same way you would with a standard Worker. + +For a full end-to-end deployment guide covering AWS IAM setup, compute configuration, and verification, see [Deploy a Serverless Worker on AWS Lambda](/production-deployment/worker-deployments/serverless-workers/aws-lambda). + +## Create and run a Worker in Lambda {#create-and-run} + +Use the `runWorker` function to create a Lambda handler that runs a Temporal Worker. +Pass a deployment version and a configure callback that sets up your Workflows and Activities. + + +[samples-typescript/lambda-worker/src/index.ts](https://github.com/temporalio/samples-typescript/blob/lambda-worker/samples-typescript/lambda-worker/src/index.ts) +```ts +import { runWorker } from '@temporalio/lambda-worker'; +// ... +import * as activities from './activities'; +import { TASK_QUEUE } from './workflows'; + +export const handler = runWorker({ deploymentName: 'sdk-demo', buildId: 'v1' }, (config) => { + config.workerOptions.taskQueue = TASK_QUEUE; + config.workerOptions.workflowBundle = { + codePath: require.resolve('./workflow-bundle.js'), + }; + config.workerOptions.activities = activities; +// ... +}); +``` + + +The deployment version is required. +Worker Deployment Versioning is always enabled for Serverless Workers. +Each Workflow must declare a [versioning behavior](/worker-versioning#versioning-behaviors), either `AutoUpgrade` or `Pinned`. +The default versioning behavior is `PINNED`. To change it, set `workerDeploymentOptions.defaultVersioningBehavior` in the configure callback. + +### Pre-bundle Workflow code {#pre-bundle} + +Use `workflowBundle` with pre-bundled code instead of `workflowsPath`. +Pre-bundling avoids webpack bundling overhead on every Lambda cold start. + +Build the bundle as a separate build step: + +```typescript +import { bundleWorkflowCode } from '@temporalio/worker'; +import { writeFile } from 'fs/promises'; + +const { code } = await bundleWorkflowCode({ + workflowsPath: require.resolve('./workflows'), +}); +await writeFile('./workflow-bundle.js', code); +``` + +Then reference the bundle in your handler with `workflowBundle: { codePath: require.resolve('./workflow-bundle.js') }`. + +## Configure the Temporal connection {#configure-connection} + +The `@temporalio/lambda-worker` package automatically loads Temporal client configuration from a TOML config file and environment variables. Refer to [Environment Configuration](/develop/environment-configuration) for more details. + +The config file is resolved in order: + +1. `TEMPORAL_CONFIG_FILE` environment variable, if set. +2. `temporal.toml` in `$LAMBDA_TASK_ROOT` (typically `/var/task`). +3. `temporal.toml` in the current working directory. + +The file is optional. If absent, only environment variables are used. + +Encrypt sensitive values like TLS keys or API keys. Refer to [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) for options. + +## Adjust Worker defaults for Lambda {#lambda-tuned-defaults} + +The `@temporalio/lambda-worker` package applies conservative defaults suited to short-lived Lambda invocations. +These differ from standard Worker defaults to avoid overcommitting resources in a constrained environment. + +| Setting | Lambda default | +|---|---| +| `maxConcurrentActivityTaskExecutions` | 2 | +| `maxConcurrentWorkflowTaskExecutions` | 10 | +| `maxConcurrentLocalActivityExecutions` | 2 | +| `maxConcurrentNexusTaskExecutions` | 5 | +| `workflowTaskPollerBehavior` | `SimpleMaximum(2)` | +| `activityTaskPollerBehavior` | `SimpleMaximum(1)` | +| `nexusTaskPollerBehavior` | `SimpleMaximum(1)` | +| `shutdownGraceTime` | 5 seconds | +| `maxCachedWorkflows` | 30 | +| `shutdownDeadlineBufferMs` | 7000 | + +Eager Activities are not supported. Lambda invocations don't maintain persistent connections. + +`shutdownDeadlineBufferMs` is specific to the `@temporalio/lambda-worker` package. +It controls how much time before the Lambda deadline the Worker begins its graceful shutdown. +The default is `shutdownGraceTime` (5s) + 2s. + +If your Worker handles long-running Activities, increase `shutdownGraceTime`, `shutdownDeadlineBufferMs`, and the Lambda invocation deadline (`--timeout`) together. +For guidance on how these values relate, see [Tuning for long-running Activities](/serverless-workers#tuning-for-long-running-activities). + +## Add observability with OpenTelemetry {#add-observability} + +The `@temporalio/lambda-worker/otel` module provides OpenTelemetry integration with defaults configured for the [AWS Distro for OpenTelemetry (ADOT)](https://aws-otel.github.io/docs/getting-started/lambda) Lambda layers. +With this enabled, the Worker emits SDK metrics and distributed traces for Workflow and Activity executions. + +The underlying metrics and traces are the same ones the TypeScript SDK emits in any environment. +For general observability concepts and the full list of available metrics, see [Observability - TypeScript SDK](/develop/typescript/platform/observability) and the [SDK metrics reference](/references/sdk-metrics). + + +[samples-typescript/lambda-worker/src/index.ts](https://github.com/temporalio/samples-typescript/blob/lambda-worker/samples-typescript/lambda-worker/src/index.ts) +```ts +import { runWorker } from '@temporalio/lambda-worker'; +import { applyDefaults } from '@temporalio/lambda-worker/otel'; +import * as activities from './activities'; +import { TASK_QUEUE } from './workflows'; + +export const handler = runWorker({ deploymentName: 'sdk-demo', buildId: 'v1' }, (config) => { + config.workerOptions.taskQueue = TASK_QUEUE; + config.workerOptions.workflowBundle = { + codePath: require.resolve('./workflow-bundle.js'), + }; + config.workerOptions.activities = activities; + applyDefaults(config); +}); +``` + + +`applyDefaults` registers Temporal SDK interceptors for tracing and configures the Core SDK to export metrics via OpenTelemetry Protocol (OTLP). +By default, telemetry is sent to `localhost:4317`, which is the ADOT Lambda layer's default collector endpoint. + +To collect this telemetry, attach two ADOT Lambda layers: + +1. The [ADOT JavaScript layer](https://aws-otel.github.io/docs/getting-started/lambda/lambda-js) for Node.js-side auto-instrumentation and trace export. +2. The [ADOT Collector layer](https://aws-otel.github.io/docs/getting-started/lambda) (`aws-otel-collector-amd64`) to run the OTel Collector as a Lambda extension, receiving telemetry via OTLP on `localhost:4317` and forwarding traces to X-Ray and metrics to CloudWatch. + +The default Collector configuration does not route OTLP data to the traces pipeline. +You must provide a custom Collector configuration that wires the OTLP receiver to both the traces and metrics pipelines. +Bundle the following `otel-collector-config.yaml` in your Lambda deployment package: + + +[samples-typescript/lambda-worker/otel-collector-config.yaml](https://github.com/temporalio/samples-typescript/blob/lambda-worker/samples-typescript/lambda-worker/otel-collector-config.yaml) +```yaml +receivers: + otlp: + protocols: + grpc: + endpoint: 'localhost:4317' + http: + endpoint: 'localhost:4318' + +exporters: + debug: + awsxray: + region: us-west-2 + awsemf: + # AWS EMF exporter for metrics + # These are example configurations + namespace: TemporalWorkerMetrics + # log_group_name: /aws/lambda/ + log_group_name: /aws/lambda/sdk-worker-typescript + region: us-west-2 + dimension_rollup_option: NoDimensionRollup + resource_to_telemetry_conversion: + enabled: true + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [awsxray, debug] + metrics: + receivers: [otlp] + exporters: [awsemf] + telemetry: + logs: + level: debug + metrics: + address: localhost:8888 +``` + + +Set the following environment variable on the Lambda function: + +- `OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml` + +Enable X-Ray active tracing on the Lambda function: + +```bash +aws lambda update-function-configuration \ + --function-name \ + --tracing-config Mode=Active +``` + +The Lambda execution role must have permissions to write to X-Ray and CloudWatch. +Add `xray:PutTraceSegments`, `xray:PutTelemetryRecords`, and `cloudwatch:PutMetricData` permissions to the execution role. +Without these permissions, the Collector fails silently and no telemetry appears. + +When pre-bundling Workflow code, pass the plugin from `makeOtelPlugin()` so that Workflow interceptor modules are included in the bundle: + +```typescript +import { bundleWorkflowCode } from '@temporalio/worker'; +import { makeOtelPlugin } from '@temporalio/lambda-worker/otel'; + +const { plugin } = makeOtelPlugin(); +const { code } = await bundleWorkflowCode({ + workflowsPath: require.resolve('./workflows'), + plugins: [plugin], +}); +``` diff --git a/docs/develop/typescript/workers/serverless-workers/index.mdx b/docs/develop/typescript/workers/serverless-workers/index.mdx new file mode 100644 index 0000000000..58f3e71046 --- /dev/null +++ b/docs/develop/typescript/workers/serverless-workers/index.mdx @@ -0,0 +1,26 @@ +--- +id: index +title: Serverless Workers - TypeScript SDK +sidebar_label: Serverless Workers +description: Write Temporal Workers that run on serverless compute using the TypeScript SDK. +slug: /develop/typescript/workers/serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - typescript sdk + - worker +tags: + - Workers + - TypeScript SDK + - Serverless +--- + +Serverless Workers run on ephemeral, on-demand compute rather than long-lived processes. +Temporal invokes the Worker when Tasks arrive, and the Worker shuts down when the work is done. + +For a general overview of how Serverless Workers work, see [Serverless Workers](/serverless-workers). +For the end-to-end deployment guide, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Supported providers + +- [**AWS Lambda**](/develop/typescript/workers/serverless-workers/aws-lambda) - Use the `@temporalio/lambda-worker` package to run a Worker as a Lambda function. Covers setup, configuration, Lambda-tuned defaults, and observability. diff --git a/docs/develop/worker-performance.mdx b/docs/develop/worker-performance.mdx index 37a31a6ba7..29841332e4 100644 --- a/docs/develop/worker-performance.mdx +++ b/docs/develop/worker-performance.mdx @@ -909,8 +909,8 @@ Then consider increasing the number of pollers by adjusting `maxConcurrentWorkfl If, after adjusting the poller and executors count as specified earlier, you still observe an elevated `schedule_to_start`, underutilized Worker hosts, or high `worker_task_slots_available`, you might want to check the following: -- If server-side rate limiting per Task Queue is set by `WorkerOptions#maxTaskQueueActivitiesPerSecond`, remove the limit or adjust the value up. (See [Go](/develop/go/workers/run-worker-process#taskqueueactivitiespersecond) and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) -- If Worker-side rate limiting per Worker is set by `WorkerOptions#maxWorkerActivitiesPerSecond`, remove the limit. (See [Go](/develop/go/workers/run-worker-process#workeractivitiespersecond), [TypeScript](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#maxconcurrentactivitytaskexecutions), and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) +- If server-side rate limiting per Task Queue is set by `WorkerOptions#maxTaskQueueActivitiesPerSecond`, remove the limit or adjust the value up. (See [Go](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) +- If Worker-side rate limiting per Worker is set by `WorkerOptions#maxWorkerActivitiesPerSecond`, remove the limit. (See [Go](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions), [TypeScript](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#maxconcurrentactivitytaskexecutions), and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) ## Related reading diff --git a/docs/encyclopedia/workers/serverless-workers.mdx b/docs/encyclopedia/workers/serverless-workers.mdx new file mode 100644 index 0000000000..f76ad600f6 --- /dev/null +++ b/docs/encyclopedia/workers/serverless-workers.mdx @@ -0,0 +1,263 @@ +--- +id: serverless-workers +title: Serverless Workers +sidebar_label: Serverless Workers +description: + Learn how Serverless Workers work, how Temporal invokes them, and how they differ from traditional long-lived Workers. +slug: /serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - workers + - lambda + - compute provider +tags: + - Workers + - Concepts + - Serverless +--- + +import CaptionedImage from '@site/src/components/images/CaptionedImage'; + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +This page covers the following: + +- [What is a Serverless Worker?](#serverless-worker) +- [How Serverless invocation works](#how-invocation-works) +- [Autoscaling](#autoscaling) +- [Scaling with long-lived Workers](#scaling-with-long-lived-workers) +- [Worker lifecycle](#worker-lifecycle) +- [Failure handling](#failure-handling) +- [Constraints](#constraints) +- [Compute providers](#compute-providers) + +## What is a Serverless Worker? {#serverless-worker} + +A Serverless Worker is a Temporal Worker that runs on serverless compute instead of a long-lived process. There is no +always-on infrastructure to provision or scale. Temporal invokes the Worker when Tasks arrive on a Task Queue, and the +Worker shuts down when the work is done. + +A Serverless Worker uses the same Temporal SDKs as a traditional long-lived Worker. It registers Workflows and +Activities the same way. The difference is in the lifecycle: instead of the Worker starting and polling continuously, +Temporal invokes the Serverless Worker on demand, the Worker starts, processes available Tasks, and then shuts down. + +Serverless Workers require [Worker Versioning](/worker-versioning). Each Serverless Worker must be associated with a +[Worker Deployment Version](/worker-versioning#deployment-versions) that has a compute provider configured. + +To deploy a Serverless Worker, see +[Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## How Serverless invocation works {#how-invocation-works} + +With long-lived Workers, you start the Worker process, which connects to Temporal and polls a Task Queue for work. +Temporal does not need to know anything about the Worker's infrastructure. + +With Serverless Workers, Temporal starts the Worker. + +### Worker Controller Instance {#worker-controller-instance} + +The Worker Controller Instance (WCI) is a system Workflow that scales Serverless Workers based on Task Queue conditions. +One WCI Workflow runs per Worker Deployment Version that has a compute provider configured. The WCI runs in the same +Namespace as your Worker Deployment. + +The WCI responds to two triggers: [sync match failures](#sync-match-failure) and +[Task Queue backlog](#task-queue-backlog). When either trigger fires, the WCI produces a scaling action, such as +invoking the configured compute provider (for example, calling AWS Lambda's `InvokeFunction` API) to start new Workers. +For details on how scaling works, see [Autoscaling](#autoscaling). + +You can list WCI Workflows in your Namespace: + +```bash +temporal workflow list \ + --namespace \ + --query 'TemporalNamespaceDivision = "TemporalWorkerControllerInstance"' +``` + +WCI Workflow IDs follow the pattern `temporal-sys-worker-controller-instance::`. You can +inspect a WCI Workflow's history to see its recent Activity results: + +```bash +temporal workflow show \ + --namespace \ + --workflow-id 'temporal-sys-worker-controller-instance::' +``` + +The following diagram illustrates the invocation flow of a Serverless Worker. + + + +The invocation flow works as follows: + +1. A Task is submitted (for example, `StartWorkflow` or `ScheduleActivity`). +2. The [Matching Service](/temporal-service/temporal-server#matching-service) attempts to route the Task directly to an + available Worker (a sync match). +3. If a Worker is available, the Task is routed to that Worker. +4. If no Worker is available (sync match fails), the Matching Service pushes a signal to the WCI, and the WCI invokes + the configured compute provider. +5. The Serverless Worker starts, creates a Temporal Client, and begins polling the Task Queue. +6. The Worker processes available Tasks until it exits (see [Worker lifecycle](#worker-lifecycle)). + +Each invocation is independent. The Worker creates a fresh client connection on every invocation. There is no connection +reuse or shared state across invocations. + +## Autoscaling {#autoscaling} + +The [WCI](#worker-controller-instance) automatically scales Serverless Workers based on Task Queue signals. When Tasks +arrive and no Worker is available, the WCI invokes new Workers. When the Tasks are done, Workers exit and scale to zero. + +The WCI uses two signals to decide when to invoke new Workers: + +### Sync match failure {#sync-match-failure} + +When a Task is submitted, the [Matching Service](/temporal-service/temporal-server#matching-service) attempts to route +it directly to an available Worker. If no Worker is available, the sync match fails, and the Matching Service pushes a +signal to the WCI. The WCI then invokes a new Worker. This is the primary scaling path. Because the Matching Service +pushes match failures to the WCI as they happen rather than the WCI polling on a timer, latency stays low and scaling is +responsive. + +### Task Queue backlog {#task-queue-backlog} + +The WCI monitors Task Queue metadata to determine whether pending Tasks exist without enough Workers to process them. If +there are Tasks on the queue and not enough Workers, the WCI invokes additional Workers. + +## Scaling with long-lived Workers {#scaling-with-long-lived-workers} + +Serverless Workers can share a Task Queue with long-lived Workers. Because Serverless Workers are only invoked on +[sync match failure](#sync-match-failure), Serverless Workers only pick up Tasks that no long-lived Worker was available +to handle. In practice, the Serverless Workers act as spillover capacity for the long-lived fleet. + +:::caution + +If you configure Serverless and long-lived Workers on the same Task Queue, do not enable dynamic scaling on the +long-lived Workers. The two groups cannot coordinate their scaling behavior. If both scale dynamically, the long-lived +Workers may scale up to handle the same Tasks that Temporal is simultaneously invoking Serverless Workers for, leading +to unnecessary invocations and unpredictable scaling. + +::: + +## Worker lifecycle {#worker-lifecycle} + +A single Serverless Worker invocation has three phases: init, work, and shutdown. + + + +During the **init** phase, the Worker initializes and establishes a client connection to Temporal. + +During the **work** phase, the Worker polls the Task Queue and processes Tasks. + +During the **shutdown** phase, the Worker stops polling, waits for in-flight Tasks to finish, and runs any shutdown +hooks (for example, OpenTelemetry telemetry flushes). Shutdown begins before the invocation deadline so the Worker can +exit cleanly before the compute provider forcibly terminates the execution environment. + +### Tuning for long-running Activities + +If your Worker handles long-running Activities, set these three values together: + +- **Worker stop timeout > longest Activity runtime.** Gives in-flight Activities enough time to finish after polling + stops. +- **Shutdown deadline buffer > Worker stop timeout + shutdown hook time.** Ensures the drain and any shutdown hooks + complete before the compute provider terminates the environment. +- **Invocation deadline > longest Activity runtime + shutdown deadline buffer.** Set on the compute provider to give + each invocation enough total runtime. + + :::tip + + If your longest-running Activity runs longer than half the maximum invocation deadline, this constraint may be + difficult or impossible to meet. In this case, use + [Activity Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat) to record the state of the + Activity execution so that the next retry can pick up where it left off. + + ::: + +For example, if your longest Activity runtime is 5 minutes, and your shutdown hooks take 3 seconds to run, set the +Worker stop timeout to more than 5 minutes, and the shutdown deadline buffer to more than 303 seconds (5 minutes + 3 +seconds). Set your invocation deadline to at least 10 minutes and 3 seconds (5 minutes + 303 seconds). + +The Worker stop timeout controls how long the Worker waits for in-flight Tasks to finish after it stops polling. The +shutdown deadline buffer controls how much time before the invocation deadline the Worker stops polling for Tasks. + +Raising only the shutdown deadline buffer makes the Worker stop polling earlier, but does not give in-flight Tasks any +more time to complete. + +Raising only the Worker stop timeout does not make the Worker stop polling earlier, which means the compute provider +might terminate the Worker before the full stop timeout completes. In-flight Activities then do not get the full stop +timeout to finish, and the shutdown hooks may not run. + +## Failure handling {#failure-handling} + +Serverless Workers rely on Temporal's standard retry and timeout semantics to recover from failures. The following +sections describe common failure scenarios and how they are handled. + +### Worker crash {#worker-crash} + +If a Worker invocation crashes (out of memory, unhandled exception, etc.), the behavior follows standard Temporal retry +semantics: + +- The Activity Timeout fires after the configured duration. +- Temporal retries the Activity on a different Worker invocation. +- No manual intervention is required. + +### Provider concurrency limit {#provider-concurrency-limit} + +If the compute provider's concurrency limit is reached (for example, AWS Lambda account concurrency): + +- Further invocations from the WCI fail. +- Tasks remain in the Task Queue backlog. No data loss occurs. +- Processing slows until concurrency frees up. + +### Resource exhaustion across Activity slots {#resource-exhaustion} + +By default, a single Worker invocation may run multiple Activity slots. A crash or resource exhaustion in one Activity +(for example, out-of-memory from a memory-intensive operation) can affect other Activities running in the same +invocation. + +To isolate Activities from each other: + +- Split Workflow and Activity Workers into separate compute functions. +- Set Activity slots to 1 per invocation. + +With single-slot configuration, each Activity gets a dedicated execution environment. + +## Constraints {#constraints} + +| Constraint | Detail | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Activity duration | Must complete within the compute provider's invocation limit (minus shutdown deadline buffer). For AWS Lambda, the maximum is 15 minutes. | +| Workflow duration | No limit. Workflows of any duration work, regardless of the invocation timeout. A Workflow runs across as many invocations as needed. | +| Worker code | Same Temporal SDK Worker code, using the serverless Worker package for your SDK. | +| Versioning | [Worker Versioning](/worker-versioning) is required. Each Workflow must have an `AutoUpgrade` or `Pinned` behavior, set per-Workflow or as a Worker-level default. | + +## Compute providers {#compute-providers} + +A compute provider is the configuration that tells Temporal how to invoke a Serverless Worker. The compute provider is +set on a [Worker Deployment Version](/worker-versioning#deployment-versions) and specifies the provider type, the +invocation target, and the credentials Temporal needs to trigger the invocation. + +For example, an AWS Lambda compute provider includes the Lambda function ARN and the IAM role that Temporal assumes to +invoke the function. + +Compute providers are only needed for Serverless Workers. Traditional long-lived Workers do not require a compute +provider because the Worker process manages its own lifecycle. + +### Supported providers + +| Provider | Description | +| ---------- | ----------------------------------------------------------------------------- | +| AWS Lambda | Temporal assumes an IAM role in your AWS account to invoke a Lambda function. | diff --git a/docs/encyclopedia/workers/sticky-execution.mdx b/docs/encyclopedia/workers/sticky-execution.mdx index e56caa419b..9e89b2f68a 100644 --- a/docs/encyclopedia/workers/sticky-execution.mdx +++ b/docs/encyclopedia/workers/sticky-execution.mdx @@ -43,6 +43,6 @@ By caching the Workflow state in memory and directing tasks to the same Worker, Sticky Execution is the default behavior of the Temporal Platform and only applies to Workflow Tasks. Since Event History is associated with a Workflow, the concept of Sticky Execution is not relevant to Activity Tasks. -- [How to set a `StickyScheduleToStartTimeout` on a individual Worker in Go](/develop/go/workers/run-worker-process#stickyscheduletostarttimeout) +- [How to set `StickyScheduleToStartTimeout` on a Worker in Go](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) Sticky Executions are the default behavior of the Temporal Platform. diff --git a/docs/encyclopedia/workers/task-queues.mdx b/docs/encyclopedia/workers/task-queues.mdx index ce8d564802..67cdec4863 100644 --- a/docs/encyclopedia/workers/task-queues.mdx +++ b/docs/encyclopedia/workers/task-queues.mdx @@ -94,7 +94,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to run a development Worker using the Python SDK](/develop/python/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the .NET SDK](/develop/dotnet/workers/run-worker-process)

- - [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/workers/cloud-worker) + - [How to connect a Go SDK Worker to Temporal Cloud](/develop/go/workers/run-worker-process#connect-to-temporal-cloud) - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-temporal-cloud-worker) Note that all Worker Entities listening to the same Task Queue name must be registered to handle the exact same Workflows Types, Activity Types, and Nexus Operations. diff --git a/docs/encyclopedia/workers/worker-versioning.mdx b/docs/encyclopedia/workers/worker-versioning.mdx index f536a9a313..f62ea2a5e1 100644 --- a/docs/encyclopedia/workers/worker-versioning.mdx +++ b/docs/encyclopedia/workers/worker-versioning.mdx @@ -34,6 +34,8 @@ Each Deployment has a name (such as your service name) and supports versioning t ## Worker Deployment Versions {#deployment-versions} A Worker Deployment Version represents an iteration of a Worker Deployment. +Each Deployment Version is identified by a deployment name and a Build ID. +The deployment name groups related Workers across versions, and the Build ID identifies a specific release of your Worker code. Each Deployment Version consists of Workers that share the same code build and environment. When a Worker starts polling for Workflow and Activity Tasks, it reports its Deployment Version to the Temporal Server. diff --git a/docs/encyclopedia/workers/workers.mdx b/docs/encyclopedia/workers/workers.mdx index c152971628..03728f07f4 100644 --- a/docs/encyclopedia/workers/workers.mdx +++ b/docs/encyclopedia/workers/workers.mdx @@ -41,7 +41,7 @@ A Worker Program is the static code that defines the constraints of the Worker P - [How to run a development Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the .NET SDK](/develop/dotnet/workers/run-worker-process) -- [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/workers/cloud-worker) +- [How to connect a Go SDK Worker to Temporal Cloud](/develop/go/workers/run-worker-process#connect-to-temporal-cloud) - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-temporal-cloud-worker) ::: diff --git a/docs/evaluate/development-production-features/serverless-workers/demo.mdx b/docs/evaluate/development-production-features/serverless-workers/demo.mdx new file mode 100644 index 0000000000..040702657c --- /dev/null +++ b/docs/evaluate/development-production-features/serverless-workers/demo.mdx @@ -0,0 +1,49 @@ +--- +id: demo +title: Serverless Workers Interactive Demo +sidebar_label: Interactive Demo +slug: /evaluate/serverless-workers/demo +toc_max_heading_level: 3 +keywords: + - serverless + - lambda + - aws + - worker + - interactive demo + - serverless worker +tags: + - Workers + - Serverless + - AWS Lambda +description: An interactive demo for Temporal Serverless Workers. Explore the configuration, generated code, and execution flow for running Workers on AWS Lambda. +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +Serverless Workers let you run Temporal Workers on serverless compute like AWS Lambda. +There are no long-lived processes to provision or scale. +Temporal Cloud invokes your Worker when Tasks arrive, and the Worker shuts down when the work is done. + +Use the interactive demo below to explore how the configuration options affect the generated +Worker code, deployment script, and CLI commands. Click "Start Workflow" to simulate the +end-to-end Serverless Worker invocation flow. + + + +import { ServerlessWorkerDemo } from '@site/src/components'; + + + + +--- + +## Next steps + +- [Serverless Workers](/serverless-workers) for concepts, autoscaling, and lifecycle details. +- [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers) for the full end-to-end deployment guide. diff --git a/docs/evaluate/development-production-features/serverless-workers/index.mdx b/docs/evaluate/development-production-features/serverless-workers/index.mdx new file mode 100644 index 0000000000..87b0e82519 --- /dev/null +++ b/docs/evaluate/development-production-features/serverless-workers/index.mdx @@ -0,0 +1,121 @@ +--- +id: index +title: Serverless Workers +sidebar_label: Serverless Workers +slug: /evaluate/serverless-workers +description: + Understand the benefits of Serverless Workers and when to use them. Run Temporal Workers on serverless compute with no + infrastructure to manage. +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - worker + - serverless worker + - evaluate +tags: + - Workers + - Serverless +--- + +Serverless Workers let you run Temporal Workers on serverless compute platforms like AWS Lambda. There are no servers to +provision, no clusters to scale, and no idle compute to pay for. Temporal invokes the Worker when Tasks arrive, and the +Worker shuts down when the Tasks are done. + +Serverless Workers use the same Temporal SDKs as traditional long-lived Workers. You register Workflows and Activities +the same way. The difference is in the lifecycle: instead of running a long-lived process, Temporal invokes the +Serverless Worker on demand when Tasks arrive. The Worker starts, polls for available Tasks, processes them, and exits +when the Task is done. + +For a deeper look at how Serverless invocation works under the hood, see [Serverless Workers](/serverless-workers) in +the encyclopedia. + +## Why use Serverless Workers? + +Serverless Workers are a good fit for many workloads. They offer several advantages compared to long-lived Workers on +dedicated compute. + +### Reduce operational overhead + +Long-lived Workers require you to provision infrastructure, configure scaling policies, manage deployments, and monitor +host-level health. Serverless Workers reduce this burden by offloading invocation and scaling to Temporal and the +compute provider. You still deploy the function and configure the compute provider, but there is no always-on +infrastructure to manage and no autoscaling policies to tune. + +Worker management is one of the most common sources of support questions for Temporal users. Serverless Workers offer a +prescriptive deployment path that reduces the operational surface area and lets you focus on writing Workflows instead +of managing infrastructure. + +### Get started faster + +Running a long-lived Worker requires choosing a hosting strategy, configuring compute resources, and setting up +deployment pipelines before you can execute your first Workflow in production. + +With Serverless Workers, deploying a Worker is as simple as deploying a function. Package your Worker code, deploy it to +your serverless provider, and configure the connection to Temporal. There is no need to set up Kubernetes, manage +container orchestration, or design a scaling strategy. + +### Scale automatically + +Serverless compute providers handle scaling natively. When Task volume increases, the provider spins up additional +function instances. When traffic drops, instances scale down. When there is no work, there is no compute running. + +This automatic scaling is especially useful for bursty, event-driven workloads where traffic patterns are unpredictable +or highly variable. + +### Pay only for what you use + +Long-lived Workers run continuously, whether or not there is work to process. Serverless Workers run only when Tasks are +available. For workloads with low or intermittent volume, this pay-per-invocation model can significantly reduce compute +costs. + +## When to use Serverless Workers + +Serverless Workers are a good fit when: + +- **Workloads are bursty or event-driven.** Order processing, notifications, webhook handlers, and similar workloads + that experience spiky traffic benefit from automatic scaling without over-provisioning. +- **Traffic is low or intermittent.** If Workers spend most of their time idle, Serverless Workers eliminate the cost of + always-on compute. +- **You want a simpler getting-started path.** Deploying a function is simpler than setting up a container orchestration + platform. Serverless Workers reduce the steps between writing Worker code and running your first Workflow. +- **Your organization has standardized on serverless.** Teams that already run services on Lambda, Cloud Run, or similar + platforms can run Temporal Workers using the same deployment patterns and tooling. +- **You serve multiple tenants with infrequent workloads.** Platforms that run Workflows on behalf of many users or + customers can avoid running dedicated Workers per tenant. + +Serverless Workers may not be ideal when: + +- **Activities are long-running and cannot be interrupted.** Some serverless platforms enforce execution time limits. + For example, AWS Lambda has a 15-minute execution limit. Activities that run longer than the provider's timeout and + cannot be broken into smaller steps need a different hosting strategy or a provider with longer limits (such as Cloud + Run). Long-running Workflows are not affected because Workflows can span multiple invocations. +- **Workloads require sustained high throughput.** For consistently high-volume Task Queues, long-lived Workers on + dedicated compute may be more cost-effective and performant. +- **You need persistent connections.** Some features require a persistent connection between the Worker and Temporal, + which serverless invocations do not maintain. + +## How Serverless Workers compare to long-lived Workers + +| | Long-lived Worker | Serverless Worker | +| -------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| **Lifecycle** | Long-lived process that runs continuously. | Invoked on demand. Starts and stops per invocation. | +| **Scaling** | You manage scaling (Kubernetes HPA, instance count, etc.). | Temporal invokes additional instances as needed, within the compute provider's concurrency limits. | +| **Connection** | Persistent connection to Temporal. | Fresh connection on each invocation. | + +## Supported providers + +| Provider | Status | +| ---------- | --------- | +| AWS Lambda | Available | + +## Next steps + +- [Interactive demo](/evaluate/serverless-workers/demo) to explore the configuration and invocation flow. +- [How Serverless Workers work](/serverless-workers) for a deeper look at the invocation lifecycle, compute providers, + and architecture. +- [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers) for the end-to-end + deployment guide. +- [Serverless Workers - Go SDK](/develop/go/workers/serverless-workers/aws-lambda) for SDK-specific configuration and + defaults. diff --git a/docs/production-deployment/worker-deployments/index.mdx b/docs/production-deployment/worker-deployments/index.mdx index cd8f20f6ff..97960c8015 100644 --- a/docs/production-deployment/worker-deployments/index.mdx +++ b/docs/production-deployment/worker-deployments/index.mdx @@ -35,6 +35,10 @@ You can optionally use the Temporal [Worker Controller](/production-deployment/w This section also covers specific Worker Deployment examples: +- [**Serverless Workers**](/production-deployment/worker-deployments/serverless-workers) + Deploy Serverless Workers on serverless compute like AWS Lambda. + Temporal invokes your Worker when Tasks arrive, with no long-lived processes to manage. + - [**Deploy Workers to Amazon EKS**](/production-deployment/worker-deployments/deploy-workers-to-aws-eks) Containerize your Worker, publish it to Amazon Elastic Container Registry (ECR), and deploy it to Amazon Elastic Kubernetes Service (EKS) using the Temporal Python SDK. This guide covers the full deployment lifecycle and shows how to configure your Worker to connect to Temporal Cloud using Kubernetes-native tools like ConfigMaps and Secrets. diff --git a/docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx b/docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx new file mode 100644 index 0000000000..18b34c4b3f --- /dev/null +++ b/docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx @@ -0,0 +1,600 @@ +--- +id: aws-lambda +title: Deploy a Serverless Worker on AWS Lambda +sidebar_label: AWS Lambda +description: Deploy a Temporal Serverless Worker on AWS Lambda. +slug: /production-deployment/worker-deployments/serverless-workers/aws-lambda +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - worker + - deployment +tags: + - Workers + - Deploy + - Serverless + - AWS Lambda +--- + +import SdkTabs from '@site/src/components/elements/SdkTabs'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This guide walks through deploying a Temporal [Serverless Worker](/serverless-workers) on AWS Lambda. + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +## Prerequisites {#prerequisites} + +- A Temporal Cloud account with an AWS-hosted Namespace, or a self-hosted Temporal Service v1.31.0 or later. The + Namespace's cloud provider must match the serverless compute provider. +- For self-hosted deployments, complete the + [self-hosted setup](/production-deployment/worker-deployments/serverless-workers/self-hosted-setup) before following + this guide. +- Every Workflow must declare a [versioning behavior](/worker-versioning#versioning-behaviors), or the Worker must set a + default versioning behavior. +- An AWS account with permissions to create and invoke Lambda functions and create IAM roles. +- The AWS-specific steps in this guide require the [`aws` CLI](https://aws.amazon.com/cli/) installed and configured + with your AWS credentials. You may use other tools to perform the steps, such as the AWS Console or the AWS SDKs. + +- The [Go SDK](/develop/go), [Python SDK](/develop/python), or [TypeScript SDK](/develop/typescript), depending on which + language you are using. Use the tabs to select your language and the rest of the page will update accordingly. + + :::tip + + If you are exploring the Serverless Worker feature and don't have a Workflow ready, you can use a sample from + the [Go Lambda Worker sample](https://github.com/temporalio/samples-go/tree/main/lambda-worker), + [Python Lambda Worker sample](https://github.com/temporalio/samples-python/tree/main/lambda_worker), or + [TypeScript Lambda Worker sample](https://github.com/temporalio/samples-typescript/tree/main/lambda-worker). + + ::: + +## 1. Write Worker code {#write-worker-code} + +Write a Worker that runs inside a Lambda function. The Worker handles the per-invocation lifecycle: connecting to +Temporal, polling for tasks, and gracefully shutting down before the invocation deadline. + + + + +Use the Go SDK's `lambdaworker` package. + +```go +package main + +import ( + lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +func main() { + lambdaworker.RunWorker(worker.WorkerDeploymentVersion{ + DeploymentName: "my-app", + BuildID: "build-1", + }, func(opts *lambdaworker.Options) error { + opts.TaskQueue = "my-task-queue" + + opts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{ + VersioningBehavior: workflow.VersioningBehaviorPinned, + }) + opts.RegisterActivity(MyActivity) + + return nil + }) +} +``` + +Each Workflow must have a [versioning behavior](/worker-versioning#versioning-behaviors), either `AutoUpgrade` or +`Pinned`. Set it per-Workflow at registration time, or set a Worker-level default with `DefaultVersioningBehavior` in +`DeploymentOptions`. + +For details on configuration options, Lambda-tuned defaults, and the invocation lifecycle, see +[Serverless Workers - Go SDK](/develop/go/workers/serverless-workers/aws-lambda). + + + + +Use the Python SDK's `lambda_worker` contrib package. + +```python +from temporalio.common import WorkerDeploymentVersion +from temporalio.contrib.aws.lambda_worker import LambdaWorkerConfig, run_worker + +from my_workflows import MyWorkflow +from my_activities import my_activity + + +def configure(config: LambdaWorkerConfig) -> None: + config.worker_config["task_queue"] = "my-task-queue" + config.worker_config["workflows"] = [MyWorkflow] + config.worker_config["activities"] = [my_activity] + + +lambda_handler = run_worker( + WorkerDeploymentVersion( + deployment_name="my-app", + build_id="build-1", + ), + configure, +) +``` + +Each Workflow must have a [versioning behavior](/worker-versioning#versioning-behaviors), either `PINNED` or +`AUTO_UPGRADE`. Set it per-Workflow in the `@workflow.defn` decorator, or set a Worker-level default with +`default_versioning_behavior` in the worker config. + +```python +from temporalio import workflow +from temporalio.common import VersioningBehavior + + +@workflow.defn(versioning_behavior=VersioningBehavior.PINNED) +class MyWorkflow: + @workflow.run + async def run(self, input: str) -> str: + ... +``` + +For details on configuration options, Lambda-tuned defaults, and observability, see +[Serverless Workers - Python SDK](/develop/python/workers/serverless-workers/aws-lambda). + + + + +Use the `@temporalio/lambda-worker` package. + +```typescript +import { runWorker } from '@temporalio/lambda-worker'; +import * as activities from './activities'; + +export const handler = runWorker({ deploymentName: 'my-app', buildId: 'build-1' }, (config) => { + config.workerOptions.taskQueue = 'my-task-queue'; + config.workerOptions.workflowBundle = { + codePath: require.resolve('./workflow-bundle.js'), + }; + config.workerOptions.activities = activities; + config.workerOptions.workerDeploymentOptions!.defaultVersioningBehavior = 'PINNED'; +}); +``` + +Use `workflowBundle` with pre-bundled code instead of `workflowsPath` to avoid webpack bundling overhead on Lambda cold +starts. + +Each Workflow must declare a [versioning behavior](/worker-versioning#versioning-behaviors), either `AUTO_UPGRADE` or +`PINNED`. Set it per-Workflow with `setWorkflowOptions` in the Workflow file, or set a default for all Workflows with +`defaultVersioningBehavior` in the configure callback. + +For details on configuration options, Lambda-tuned defaults, and observability, see +[Serverless Workers - TypeScript SDK](/develop/typescript/workers/serverless-workers/aws-lambda). + + + + +## 2. Deploy Lambda function {#deploy-lambda-function} + +Build your Worker for the Lambda runtime, package it as a zip, and deploy it to AWS Lambda. + +### i. Build and package {#build-and-package} + + + + +Cross-compile for Lambda's Linux runtime: + +```bash +GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap ./worker +``` + +Package the binary into a zip file: + +```bash +zip function.zip bootstrap +``` + + + + +Install dependencies into a local directory for packaging. Use `--platform` to fetch Linux-compatible binaries for the +Lambda runtime: + +```bash +pip install --target ./package --platform manylinux2014_x86_64 --only-binary=:all: temporalio +``` + +To include [OpenTelemetry support](/develop/python/workers/serverless-workers/aws-lambda#add-observability), install +`temporalio[lambda-worker-otel]` instead. + +Package the dependencies and your application code into a zip file: + +```bash +cd package && zip -r ../function.zip . && cd .. +zip function.zip lambda_function.py my_workflows.py my_activities.py +``` + + + + +Build the Workflow bundle and compile the project: + +```bash +npx ts-node src/scripts/build-workflow-bundle.ts +npx tsc +``` + +Install production dependencies and package everything into a zip: + +```bash +npm install --omit=dev +zip -r function.zip lib/ node_modules/ workflow-bundle.js +``` + + + + +### ii. Deploy Lambda function {#deploy-lambda-function-step} + +Replace the placeholder values and run the following command to create the Lambda function in your AWS environment. + + + + +```bash +aws lambda create-function \ + --function-name my-temporal-worker \ + --runtime provided.al2023 \ + --handler bootstrap \ + --role \ + --zip-file fileb://function.zip \ + --timeout 600 \ + --memory-size 256 \ + --environment '{"Variables":{"HOME":"/tmp","TEMPORAL_ADDRESS":":7233","TEMPORAL_NAMESPACE":"","TEMPORAL_API_KEY":""}}' +``` + +| Parameter | Description | +| ----------------- | --------------------------------------------------------------------------------------------- | +| `--function-name` | Name of the Lambda function. | +| `--runtime` | Lambda runtime. Use `provided.al2023` for custom Go binaries. | +| `--handler` | Entry point binary name. Must be `bootstrap` when using the `provided.al2023` custom runtime. | + + + + +```bash +aws lambda create-function \ + --function-name my-temporal-worker \ + --runtime python3.13 \ + --handler lambda_function.lambda_handler \ + --role \ + --zip-file fileb://function.zip \ + --timeout 600 \ + --memory-size 256 \ + --environment '{"Variables":{"TEMPORAL_ADDRESS":":7233","TEMPORAL_NAMESPACE":"","TEMPORAL_API_KEY":""}}' +``` + +| Parameter | Description | +| ----------------- | -------------------------------------------------------------------------------------------- | +| `--function-name` | Name of the Lambda function. | +| `--runtime` | Lambda runtime. Use `python3.13` or another supported Python version. | +| `--handler` | Entry point in `module.function` format. Must point to the handler returned by `run_worker`. | + + + + +```bash +aws lambda create-function \ + --function-name my-temporal-worker \ + --runtime nodejs22.x \ + --handler lib/index.handler \ + --role \ + --zip-file fileb://function.zip \ + --timeout 600 \ + --memory-size 256 \ + --environment '{"Variables":{"HOME":"/tmp","TEMPORAL_ADDRESS":":7233","TEMPORAL_NAMESPACE":"","TEMPORAL_API_KEY":""}}' +``` + +| Parameter | Description | +| ----------------- | ----------------------------------------------------------------------------------------- | +| `--function-name` | Name of the Lambda function. | +| `--runtime` | Lambda runtime. Use `nodejs22.x` or another supported Node.js version (20+). | +| `--handler` | Entry point in `module.export` format. Must point to the handler exported by `runWorker`. | + + + + +The following parameters apply to all SDKs: + +| Parameter | Description | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--role` | ARN of the Lambda [execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html), which grants the function permission to run. Trusted principal must be `lambda.amazonaws.com`. This is separate from the role Temporal uses to invoke the function in [Step 3](#configure-iam). The role must have at least the [`AWSLambdaBasicExecutionRole`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSLambdaBasicExecutionRole.html) managed policy attached. | +| `--zip-file` | Path to your packaged deployment zip. | +| `--timeout` | Invocation deadline in seconds. This is the maximum time each Lambda invocation can run before AWS terminates it. Set this high enough for the Worker to start, process Tasks, and [shut down gracefully](/serverless-workers#worker-lifecycle). | +| `--memory-size` | Memory in MB allocated to each invocation. | +| `TEMPORAL_ADDRESS` | Temporal frontend address (e.g., `..tmprl.cloud:7233`). | +| `TEMPORAL_NAMESPACE` | Temporal Namespace. | +| `TEMPORAL_TASK_QUEUE` | Task Queue name. Overrides the value set in code. | +| `TEMPORAL_TLS_CLIENT_CERT_PATH` | Path to the TLS client certificate file for mTLS authentication. | +| `TEMPORAL_TLS_CLIENT_KEY_PATH` | Path to the TLS client key file for mTLS authentication. | +| `TEMPORAL_API_KEY` | API key for API key authentication. | + +The serverless Worker packages read environment variables and configuration files automatically at startup. +For the full list of supported environment variables, config file format, and profiles, see +[Environment configuration](/develop/environment-configuration). + +Sensitive values like TLS keys and API keys should be encrypted at rest. See +[AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) for options. + +To update an existing function with new code: + +```bash +aws lambda update-function-code \ + --function-name my-temporal-worker \ + --zip-file fileb://function.zip +``` + +:::caution Lambda versioning best practices + +Create a 1-to-1 mapping between each build ID in your Worker code and a +[Lambda function version](https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html). If you use an +unversioned Lambda, do not change the Build Id in your Worker code without also creating a new Worker Deployment +Version. + +::: + +## 3. Configure IAM for Temporal invocation (Cloud only) {#configure-iam} + +This section applies to Temporal Cloud. For self-hosted Temporal Service deployments, see +[Self-hosted setup](/production-deployment/worker-deployments/serverless-workers/self-hosted-setup#create-invocation-role) +for IAM configuration with a different CloudFormation template. + +Temporal needs permission to invoke your Lambda function. The Temporal server assumes an IAM role in your AWS account to +call `lambda:InvokeFunction`. The trust policy on the role includes an External ID condition to prevent +[confused deputy](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) attacks. + +Deploy the following CloudFormation template to create the invocation role and its permissions. +[Download the template](/files/temporal-cloud-serverless-worker-role.yaml). + +| Parameter | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `AssumeRoleExternalId` | A string you choose to prevent [confused deputy](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) attacks. Can be any value. Use the same value when creating the Worker Deployment Version. | +| `LambdaFunctionARNs` | Comma-separated list of Lambda function ARNs that Temporal may invoke. One role can authorize multiple Worker Lambdas. | +| `RoleName` | Base name for the created IAM role. Defaults to `Temporal-Cloud-Serverless-Worker`. | + +
+CloudFormation template + +```yaml +# CloudFormation template for creating an IAM role that Temporal Cloud can assume to invoke Lambda functions. +AWSTemplateFormatVersion: '2010-09-09' +Description: + Creates an IAM role that Temporal Cloud can assume to invoke multiple Lambda functions for Serverless Workers. + +Parameters: + AssumeRoleExternalId: + Type: String + Description: A string you choose. Can be any value. + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of Lambda function ARNs to invoke (e.g., + arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2) + + RoleName: + Type: String + Default: 'Temporal-Cloud-Serverless-Worker' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: 'Temporal Cloud Configuration' + Parameters: + - AssumeRoleExternalId + - Label: + default: 'Lambda Configuration' + Parameters: + - LambdaFunctionARNs + - RoleName + ParameterLabels: + AssumeRoleExternalId: + default: 'External ID' + LambdaFunctionARNs: + default: 'Lambda Function ARNs (comma-separated list)' + RoleName: + default: 'IAM Role Name' + +Resources: + TemporalCloudServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: + [ + arn:aws:iam::902542641901:role/wci-lambda-invoke, + arn:aws:iam::160190466495:role/wci-lambda-invoke, + arn:aws:iam::819232936619:role/wci-lambda-invoke, + arn:aws:iam::829909441867:role/wci-lambda-invoke, + arn:aws:iam::354116250941:role/wci-lambda-invoke, + ] + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': [!Ref AssumeRoleExternalId] + Description: 'The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers' + MaxSessionDuration: 3600 # 1 hour + + TemporalCloudLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalCloudServerlessWorker + Properties: + PolicyName: 'Temporal-Cloud-Lambda-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub '${RoleName}-${AWS::StackName}' + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for Temporal Cloud + Value: !GetAtt TemporalCloudServerlessWorker.Arn + Export: + Name: !Sub '${AWS::StackName}-RoleARN' + + RoleName: + Description: The name of the IAM role + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked + Value: !Join [', ', !Ref LambdaFunctionARNs] +``` + +
+ +Deploy the template: + +```bash +aws cloudformation create-stack \ + --stack-name \ + --template-body file://temporal-cloud-serverless-worker-role.yaml \ + --parameters \ + ParameterKey=AssumeRoleExternalId,ParameterValue= \ + ParameterKey=LambdaFunctionARNs,ParameterValue='""' \ + --capabilities CAPABILITY_NAMED_IAM \ + --region +``` + +After the stack finishes creating, retrieve the IAM role ARN from the stack outputs: + +```bash +aws cloudformation describe-stacks --stack-name --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' --output text --region +``` + +Use this role ARN in your Worker Deployment Version's compute configuration. + +## 4. Create Worker Deployment Version {#create-worker-deployment-version} + +Create a [Worker Deployment Version](/production-deployment/worker-deployments/worker-versioning) with a compute +provider that points to your Lambda function. The compute configuration tells Temporal how to invoke your Worker: the +provider type (`aws-lambda`), the Lambda function ARN, and the IAM role to assume. The deployment name and build ID must +match the values in your Worker code. + +You can create the version using the Temporal UI or the Temporal CLI. + + + + +1. In the Temporal UI, open your Namespace. +2. In the left pane, select **Workers**. +3. Click **Create Worker Deployment** in the upper right corner. +4. Under **Configuration**, enter a **Name** and **Build ID**. These must match the `DeploymentName` and `BuildID` in + your Worker code. +5. Under **Compute**, select **AWS Lambda** and provide: + - **Lambda ARN**: the ARN of your Lambda function. + - **IAM Role ARN**: the ARN of the role Temporal assumes to invoke your Lambda function. This is the role ARN from + [Step 3](#configure-iam) (output of the CloudFormation stack). This is not the Lambda execution role from + [Step 2](#deploy-lambda-function) or your own IAM user/role. + - **External ID**: the same value you passed to the CloudFormation template. +6. Click **Save**. + +When you create a version through the UI, the version is automatically set as current. Skip to +[Verify the deployment](#verify-deployment). + + + + +Use the CLI for manual setup, shell scripts, and CI/CD pipelines. When you create a version through the CLI, you must +[set the version as current](#set-current-version) as a separate step. + +First, create the Worker Deployment if it does not already exist: + +```bash +temporal worker deployment create \ + --namespace \ + --name my-app +``` + +Then create the version with the compute provider configuration: + +```bash +temporal worker deployment create-version \ + --namespace \ + --deployment-name my-app \ + --build-id build-1 \ + --aws-lambda-function-arn \ + --aws-lambda-assume-role-arn \ + --aws-lambda-assume-role-external-id +``` + +| Flag | Description | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--deployment-name` | Worker Deployment name. Must match `DeploymentName` in your Worker code. | +| `--build-id` | Worker Deployment Version build ID. Must match `BuildID` in your Worker code. | +| `--aws-lambda-function-arn` | ARN of the Lambda function Temporal invokes for this version. | +| `--aws-lambda-assume-role-arn` | IAM role Temporal assumes to invoke the function. This is the `RoleARN` output from the CloudFormation stack in [Step 3](#configure-iam). This is not the Lambda execution role from [Step 2](#deploy-lambda-function) or your own IAM user/role. | +| `--aws-lambda-assume-role-external-id` | External ID configured in the IAM role trust policy. | + + + + +To verify that Temporal can reach your Lambda function, go to **Workers** > **Deployments** > select your deployment > +open the **Actions** menu on the version and click **Validate Connection**. This checks that Temporal can assume the IAM +role and invoke the function. + +## 5. Set version as current {#set-current-version} + +If you created the version through the Temporal UI, the version is already current and you can skip this step. + +If you used the CLI, set the version as current. Without this step, tasks on the Task Queue will not route to the +version, and Temporal will not invoke the Lambda function. + +```bash +temporal worker deployment set-current-version \ + --deployment-name my-app \ + --build-id build-1 +``` + +## 6. Verify deployment {#verify-deployment} + +Start a Workflow on the same Task Queue to confirm that Temporal invokes your Lambda Worker. + +```bash +temporal workflow start \ + --task-queue my-task-queue \ + --type MyWorkflow \ + --input '"Hello, serverless!"' +``` + +When the task lands on the Task Queue with no active pollers, Temporal detects the compute provider configuration and +invokes your Lambda function. The Worker starts, connects to Temporal, picks up the task, and processes it. + +You can verify the invocation by checking: + +- **Temporal UI:** The Workflow execution should show task completions in the event history. +- **AWS CloudWatch Logs:** The Lambda function's log group (`/aws/lambda/my-temporal-worker`) should show invocation + logs with the Worker startup, task processing, and graceful shutdown. + +If the Workflow does not progress or the Lambda is not invoked, see [Troubleshoot Serverless Workers](/troubleshooting/serverless-workers). diff --git a/docs/production-deployment/worker-deployments/serverless-workers/index.mdx b/docs/production-deployment/worker-deployments/serverless-workers/index.mdx new file mode 100644 index 0000000000..bbf5f957bd --- /dev/null +++ b/docs/production-deployment/worker-deployments/serverless-workers/index.mdx @@ -0,0 +1,32 @@ +--- +id: index +title: Serverless Workers +sidebar_label: Serverless Workers +description: + Deploy Temporal Workers on serverless compute providers. Temporal invokes your Worker when Tasks arrive, with no + long-lived processes to manage. +slug: /production-deployment/worker-deployments/serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - worker + - deployment + - lambda +tags: + - Workers + - Deploy + - Serverless +--- + +Serverless Workers let you run Temporal Workers on serverless compute like AWS Lambda. Deploy your Worker code to a +serverless provider, configure a compute provider for the Worker Deployment Version, and Temporal invokes the Worker +when Tasks arrive. There are no long-lived processes to provision or scale. + +Temporal monitors Task Queues that have a compute provider configured. When a Task arrives and no Worker is polling, +Temporal invokes the configured compute target. The Worker starts, processes available Tasks, and shuts down when the +invocation window ends. + +## Supported providers + +- [**AWS Lambda**](/production-deployment/worker-deployments/serverless-workers/aws-lambda) - Deploy a Serverless Worker + as a Lambda function. Temporal assumes an IAM role in your AWS account to invoke the function when Tasks arrive. diff --git a/docs/production-deployment/worker-deployments/serverless-workers/self-hosted-setup.mdx b/docs/production-deployment/worker-deployments/serverless-workers/self-hosted-setup.mdx new file mode 100644 index 0000000000..850cee499c --- /dev/null +++ b/docs/production-deployment/worker-deployments/serverless-workers/self-hosted-setup.mdx @@ -0,0 +1,232 @@ +--- +id: self-hosted-setup +title: Self-hosted setup for Serverless Workers +sidebar_label: Self-hosted setup +description: Configure a self-hosted Temporal Service to use Serverless Workers with AWS Lambda. +slug: /production-deployment/worker-deployments/serverless-workers/self-hosted-setup +toc_max_heading_level: 4 +keywords: + - serverless + - self-hosted + - lambda + - aws + - worker + - deployment +tags: + - Workers + - Deploy + - Serverless + - Self-hosted +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +Serverless Workers require Temporal Service v1.31.0 or later. + +This page covers the prerequisites for running [Serverless Workers](/serverless-workers) on a self-hosted Temporal +Service with AWS Lambda: + +1. Ensure Lambda can reach the Temporal Service. +2. Enable the Worker Controller Instance (WCI) on the server through dynamic configuration. +3. Provide the server with AWS credentials to assume IAM roles. +4. Create an IAM role in your AWS account that grants Temporal permission to invoke Lambda functions. + +Once setup is complete, follow the +[AWS Lambda deployment guide](/production-deployment/worker-deployments/serverless-workers/aws-lambda) to deploy your +Worker. + +## Ensure Lambda can reach the Temporal Service {#ensure-network-reachability} + +The [Temporal Service frontend](/temporal-service/temporal-server#frontend-service) must be reachable from the Lambda execution +environment. How to achieve this depends on your network setup. If the Temporal Service runs on a private network, you +may need [VPC access for Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html), VPC peering, or a +similar mechanism to allow the Lambda function to connect to the Temporal frontend. + +## Enable the Worker Controller Instance {#enable-worker-controller} + +[WCI](/serverless-workers#how-invocation-works) is the server component that monitors Task Queues and invokes compute +providers. It is disabled by default and must be enabled through +[dynamic configuration](/references/dynamic-configuration). + +Add the following keys to your dynamic config file: + +```yaml +workercontroller.enabled: + - value: true + +workercontroller.compute_providers.enabled: + - value: + - aws-lambda + +workercontroller.scaling_algorithms.enabled: + - value: + - no-sync +``` + +To enable WCI for specific Namespaces instead of globally, add a `constraints` section with the +Namespace name under `workercontroller.enabled`. For example, to enable WCI only for `your-namespace`: + +```yaml +workercontroller.enabled: + - value: true + constraints: + namespace: 'your-namespace' +``` + +The Temporal Service watches the dynamic config file for changes and applies updates without a restart. + +## Configure AWS credentials {#configure-aws-credentials} + +The Temporal Service needs AWS credentials to assume an IAM role that invokes Lambda functions. How you provide +credentials depends on where the Temporal Service runs. + +**On AWS infrastructure (EC2, ECS, EKS):** The server uses the attached instance role, task role, or pod role +automatically. No additional credential configuration is needed. The attached role must have `sts:AssumeRole` permission +for the Lambda invocation role created in the next step. + +**Outside AWS:** Use [IAM Roles Anywhere](https://aws.amazon.com/iam/roles-anywhere/), or configure static AWS +credentials in the server's environment (not recommended): + +``` +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_REGION= +``` + +These credentials must belong to an IAM user or role that has `sts:AssumeRole` permission for the Lambda invocation +role. + +## Create the Lambda invocation role {#create-invocation-role} + +Temporal invokes Lambda functions by assuming an IAM role in your AWS account. This role needs `lambda:GetFunction` and +`lambda:InvokeFunction` permission on your Worker Lambda functions, and a trust policy that allows the Temporal server's +identity to assume it. + +Deploy the following CloudFormation template to create the role. +[Download the template](/files/temporal-self-hosted-serverless-worker-role.yaml). Replace the parameter values in the +command below and run it in your terminal: + +```bash +aws cloudformation create-stack \ + --stack-name temporal-serverless-worker \ + --template-body file://temporal-self-hosted-serverless-worker-role.yaml \ + --parameters \ + ParameterKey=TemporalIamRoleArn,ParameterValue= \ + ParameterKey=AssumeRoleExternalId,ParameterValue= \ + ParameterKey=LambdaFunctionARNs,ParameterValue='""' \ + --capabilities CAPABILITY_NAMED_IAM \ + --region +``` + +| Parameter | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `TemporalIamRoleArn` | The ARN of the IAM role or user that the Temporal Service runs as. This is the identity the server uses to call `sts:AssumeRole`. To find the ARN, run `aws sts get-caller-identity` in the server's environment. | +| `AssumeRoleExternalId` | A unique string to prevent [confused deputy](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) attacks. Choose any value and pass the same value when creating the Worker Deployment Version. | +| `LambdaFunctionARNs` | Comma-separated list of Lambda function ARNs that Temporal may invoke. | +| `RoleName` | Base name for the created IAM role. Defaults to `Temporal-Serverless-Worker`. | + +
+CloudFormation template + +```yaml +AWSTemplateFormatVersion: '2010-09-09' +Description: + Creates an IAM role that a self-hosted Temporal Service can assume to invoke Lambda functions for Serverless Workers. + +Parameters: + TemporalIamRoleArn: + Type: String + Description: The ARN of the IAM role or user that the Temporal Service runs as. + + AssumeRoleExternalId: + Type: String + Description: A unique identifier to prevent confused deputy attacks. + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of Lambda function ARNs to invoke (e.g., + arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2) + + RoleName: + Type: String + Default: 'Temporal-Serverless-Worker' + +Resources: + TemporalServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: [!Ref TemporalIamRoleArn] + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': [!Ref AssumeRoleExternalId] + Description: 'The role the Temporal Service uses to invoke Lambda functions for Serverless Workers' + MaxSessionDuration: 3600 + + TemporalLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalServerlessWorker + Properties: + PolicyName: 'Temporal-Lambda-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub '${RoleName}-${AWS::StackName}' + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for the Temporal Service + Value: !GetAtt TemporalServerlessWorker.Arn + Export: + Name: !Sub '${AWS::StackName}-RoleARN' + + RoleName: + Description: The name of the IAM role + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked + Value: !Join [', ', !Ref LambdaFunctionARNs] +``` + +
+ +After the stack finishes creating, retrieve the IAM role ARN from the stack outputs: + +```bash +aws cloudformation describe-stacks \ + --stack-name temporal-serverless-worker \ + --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' \ + --output text \ + --region +``` + +Use this role ARN when creating the Worker Deployment Version. + +## Next steps {#next-steps} + +Follow the [AWS Lambda deployment guide](/production-deployment/worker-deployments/serverless-workers/aws-lambda) to +write your Worker code, deploy it to Lambda, and create a Worker Deployment Version with the IAM role from the previous +step. diff --git a/docs/troubleshooting/index.mdx b/docs/troubleshooting/index.mdx index a13a20107e..3abb21f96a 100644 --- a/docs/troubleshooting/index.mdx +++ b/docs/troubleshooting/index.mdx @@ -22,3 +22,4 @@ Our troubleshooting guides are designed to help you quickly identify and resolve The "Context: deadline exceeded" error occurs when requests to the Temporal Service by the Client or Worker cannot be completed. This can be due to network issues, timeouts, server overload, or Query errors. - [Troubleshoot the Failed Reaching Server Error](/troubleshooting/last-connection-error): The message "Failed reaching server: last connection error" often happens due to an expired TLS certificate or during the Server startup process when Client requests reach the Server before roles are fully initialized. +- [Troubleshoot Serverless Workers](/troubleshooting/serverless-workers): Diagnose issues with Serverless Workers on AWS Lambda by tracing the invocation flow from Task Queue to Worker execution. diff --git a/docs/troubleshooting/serverless-workers.mdx b/docs/troubleshooting/serverless-workers.mdx new file mode 100644 index 0000000000..046eddc4ec --- /dev/null +++ b/docs/troubleshooting/serverless-workers.mdx @@ -0,0 +1,167 @@ +--- +id: serverless-workers +title: Troubleshoot Serverless Workers +sidebar_label: Serverless Workers +description: + Diagnose and fix issues with Temporal Serverless Workers on AWS Lambda by tracing the invocation flow from Task Queue + to Worker execution. +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - troubleshooting + - worker + - invocation +tags: + - Workers + - Serverless + - Troubleshooting + - AWS Lambda +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Serverless Workers are in [Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This page walks through the Serverless Worker invocation flow and helps you identify where a failure is occurring. + +When a Serverless Worker invocation works correctly, the following sequence happens: + +1. You deploy the Worker function on Lambda. +2. You configure a [Worker Deployment Version](/worker-versioning#deployment-versions) with a compute provider. This starts a [Worker Controller Instance (WCI)](/serverless-workers#how-invocation-works) Workflow and a validation invocation of the Lambda function. +3. The Lambda polls the Temporal Service successfully, binding the [Task Queue](/task-queue) configured on the Worker to the Worker Deployment Version. +4. The WCI continuously monitors the associated Task Queue on a schedule. The [Matching Service](/temporal-service/temporal-server#matching-service) also notifies the WCI Workflow of sync match failures immediately as they happen. +5. A Task arrives on the Task Queue and the WCI detects the backlog. +6. The WCI invokes the Lambda function. +7. The Lambda function starts, the Worker connects to Temporal and polls the Task Queue. +8. The Worker processes Tasks and shuts down gracefully. + +Start by determining whether the Lambda function is being invoked at all, then narrow down from there. + +## Is the Lambda function being invoked? {#is-lambda-invoked} + +Check the Lambda function's CloudWatch metrics or invocation logs. + +In the AWS Console, go to **Lambda > Functions > your function > Monitor**. Look for recent invocations in the +**Invocations** graph. You can also check **CloudWatch > Log groups > /aws/lambda/your-function-name** for execution +logs. + +If there are no invocations, continue to [Lambda is not being invoked](#lambda-not-invoked). + +If the Lambda is being invoked but Workflows are not progressing, skip to +[Lambda is invoked but Tasks are not completing](#lambda-invoked-not-completing). + +## Lambda is not being invoked {#lambda-not-invoked} + +Work through the following checks in order. + +### Validate the connection to Lambda {#validate-connection} + +Start by verifying that Temporal can reach the Lambda function. Go to **Workers > Deployments > select your +deployment**, open the **Actions** menu on the version, and click **Validate Connection**. A successful validation +confirms that the Worker Deployment Version has a compute provider configured, that Temporal can assume the invocation +role, and that the Lambda function can be invoked. + +If validation fails, verify that the Lambda function ARN and invocation role ARN in the Worker Deployment Version +configuration are correct. Verify the invocation role was created using the +[CloudFormation template](/production-deployment/worker-deployments/serverless-workers/aws-lambda#configure-iam) +and that the External ID matches the value in the Worker Deployment Version configuration. + +If the Worker Deployment Version does not have a compute provider configured, no +[Worker Controller Instance (WCI)](/serverless-workers#how-invocation-works) Workflow exists and the Lambda is never +automatically invoked. A common cause is manually invoking the Lambda function before creating the Worker Deployment +Version in the UI or CLI. When the Lambda runs, the Worker connects to Temporal and polls the Task Queue. That polling +registers the Worker Deployment Version and binds the Task Queue on the server, but the version has no compute provider. +To fix the issue, create or update the Worker Deployment Version with the compute provider flags as described in the +[deploy guide](/production-deployment/worker-deployments/serverless-workers/aws-lambda#create-worker-deployment-version). + +### Check that the version is set as current {#check-version-current} + +The Worker Deployment Version must be set as the current version for new Tasks to route to it. If you created the +version through the CLI, you need to +[set it as current](/production-deployment/worker-deployments/serverless-workers/aws-lambda#set-current-version). + +You can verify the current version with `temporal worker deployment describe`. + +### Check that the WCI is detecting Tasks {#check-wci-detecting-tasks} + +If the connection validates successfully but the Lambda is still not being invoked, the +[Worker Controller Instance (WCI)](/serverless-workers#worker-controller-instance) may not be detecting Tasks on the +Task Queue. + +Check which Task Queues are bound to the Worker Deployment Version and whether there is a backlog: + +```bash +temporal worker deployment describe-version \ + --namespace \ + --deployment-name \ + --build-id \ + --report-task-queue-stats +``` + +If no Task Queues are listed, the binding has not been established. The server binds a Task Queue to a Worker Deployment +Version when a Worker with that deployment version successfully connects and polls the Task Queue. + +A common cause is a failed first invocation. When you create a Worker Deployment Version, the WCI invokes the Lambda to +validate the configuration. If that first invocation fails (for example, due to missing environment variables, incorrect +TLS configuration, or missing dependencies), the Worker never connects to Temporal and never polls. Without a successful +poll, the Task Queue binding is never created. + +To diagnose a failed first invocation, invoke the Lambda function manually from the AWS Console. The console displays +the execution result and any errors directly, making it easier to identify configuration issues than searching through +CloudWatch logs. Once the Lambda runs successfully and the Worker connects to Temporal, the Task Queue binding is +established. + +## Lambda is invoked but Tasks are not completing {#lambda-invoked-not-completing} + +If CloudWatch shows Lambda invocations but Workflows are not progressing, the problem is in the Worker's execution +within the Lambda function. + +### Check Lambda execution logs {#check-execution-logs} + +Check CloudWatch logs for errors during Worker startup. In the AWS Console, go to **CloudWatch > Log groups > +/aws/lambda/your-function-name** and look for recent error messages. + +Common errors include: + +- **Connection failures**: The Worker cannot reach the Temporal Service. Check that the `TEMPORAL_ADDRESS` and + `TEMPORAL_API_KEY` environment variables (or `temporal.toml` config file) are correctly set on the Lambda function. + For self-hosted deployments, verify + [network reachability](/production-deployment/worker-deployments/serverless-workers/self-hosted-setup#ensure-network-reachability). +- **TLS errors**: The TLS certificate or key is missing, expired, or does not match the Namespace. +- **Authentication errors**: The API key is invalid or does not have access to the Namespace. + +### Check for Lambda timeout {#check-lambda-timeout} + +If the Lambda function reaches its configured timeout before the Worker finishes processing, AWS terminates the +invocation. + +The Worker begins graceful shutdown before the Lambda deadline. If Activities take longer than the available execution +window, the Activities are abandoned mid-execution and retried on the next invocation. + +For long-running Activities, increase the Lambda timeout and the Worker's shutdown buffer together. See +[Tuning for long-running Activities](/serverless-workers#tuning-for-long-running-activities) for guidance on how these +values relate. + +### Check that the deployment name and build ID match {#check-deployment-match} + +If CloudWatch shows rapid, repeated invocations with no Workflow progress, the deployment name or build ID in the Worker +code may not match the Worker Deployment Version configuration. + +The deployment name and build ID in your Lambda function code must exactly match the values you used when creating the +Worker Deployment Version. Compare the values in your code against the WCI Workflow ID +(`temporal-sys-worker-controller-instance::`) and the output of +`temporal worker deployment describe`. + +A mismatch causes an invocation loop: the WCI invokes the Lambda, the Worker starts and polls with a different +deployment version than the WCI expects, the Task is not processed, and the WCI invokes the Lambda again. + +To fix the loop, update the deployment name and build ID in the Worker code to match the Worker Deployment Version, then +redeploy the Lambda function. diff --git a/sidebars.js b/sidebars.js index c43cbbf0aa..bd572dfb11 100644 --- a/sidebars.js +++ b/sidebars.js @@ -37,6 +37,14 @@ module.exports = { 'evaluate/development-production-features/low-latency', 'evaluate/development-production-features/multi-tenancy', 'evaluate/development-production-features/job-queue', + { + type: 'category', + label: 'Serverless Workers', + link: { type: 'doc', id: 'evaluate/development-production-features/serverless-workers/index' }, + items: [ + 'evaluate/development-production-features/serverless-workers/demo', + ], + }, { type: 'category', label: 'Product release stages', @@ -148,8 +156,19 @@ module.exports = { }, items: [ 'develop/go/workers/run-worker-process', - 'develop/go/workers/cloud-worker', 'develop/go/workers/sessions', + { + type: 'category', + label: 'Serverless Workers', + collapsed: true, + link: { + type: 'doc', + id: 'develop/go/workers/serverless-workers/index', + }, + items: [ + 'develop/go/workers/serverless-workers/aws-lambda', + ], + }, ], }, { @@ -507,6 +526,18 @@ module.exports = { items: [ 'develop/python/workers/run-worker-process', 'develop/python/workers/interceptors', + { + type: 'category', + label: 'Serverless Workers', + collapsed: true, + link: { + type: 'doc', + id: 'develop/python/workers/serverless-workers/index', + }, + items: [ + 'develop/python/workers/serverless-workers/aws-lambda', + ], + }, ], }, { @@ -647,6 +678,18 @@ module.exports = { items: [ 'develop/typescript/workers/run-worker-process', 'develop/typescript/workers/interceptors', + { + type: 'category', + label: 'Serverless Workers', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/workers/serverless-workers/index', + }, + items: [ + 'develop/typescript/workers/serverless-workers/aws-lambda', + ], + }, ], }, { @@ -1281,6 +1324,19 @@ module.exports = { 'production-deployment/worker-deployments/worker-versioning', 'production-deployment/worker-deployments/kubernetes-controller', 'production-deployment/worker-deployments/deploy-workers-to-aws-eks', + { + type: 'category', + label: 'Serverless Workers', + collapsed: true, + link: { + type: 'doc', + id: 'production-deployment/worker-deployments/serverless-workers/index', + }, + items: [ + 'production-deployment/worker-deployments/serverless-workers/aws-lambda', + 'production-deployment/worker-deployments/serverless-workers/self-hosted-setup', + ], + }, ], }, 'production-deployment/data-encryption', @@ -1345,6 +1401,7 @@ module.exports = { 'troubleshooting/deadline-exceeded-error', 'troubleshooting/last-connection-error', 'troubleshooting/performance-bottlenecks', + 'troubleshooting/serverless-workers', ], }, { @@ -1456,6 +1513,7 @@ module.exports = { 'encyclopedia/workers/sticky-execution', 'encyclopedia/workers/worker-shutdown', 'encyclopedia/workers/worker-versioning', + 'encyclopedia/workers/serverless-workers', ], }, { diff --git a/src/components/elements/ServerlessWorkerDemo.js b/src/components/elements/ServerlessWorkerDemo.js new file mode 100644 index 0000000000..34b7fd4044 --- /dev/null +++ b/src/components/elements/ServerlessWorkerDemo.js @@ -0,0 +1,527 @@ +import Admonition from '@theme/Admonition'; +import CodeBlock from '@theme/CodeBlock'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import styles from './serverless-worker-demo.module.css'; + +// --------------------------------------------------------------------------- +// Code generation +// --------------------------------------------------------------------------- + +function generateWorkerCode(config) { + const { deploymentName, buildId, taskQueue } = config; + return `package main + +import ( +\tlambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" +\t"go.temporal.io/sdk/worker" +\t"go.temporal.io/sdk/workflow" +) + +func main() { +\tlambdaworker.RunWorker(worker.WorkerDeploymentVersion{ +\t\tDeploymentName: "${deploymentName}", +\t\tBuildID: "${buildId}", +\t}, func(opts *lambdaworker.Options) error { +\t\topts.TaskQueue = "${taskQueue}" + +\t\topts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{ +\t\t\tVersioningBehavior: workflow.VersioningBehaviorPinned, +\t\t}) +\t\topts.RegisterActivity(MyActivity) + +\t\treturn nil +\t}) +}`; +} + +function generateDeployScript(config) { + const { namespace, lambdaFunctionName } = config; + return `# Build for Lambda +GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap ./worker + +# Package the binary +zip function.zip bootstrap + +# Create the Lambda function with Temporal connection env vars +aws lambda create-function \\ + --function-name ${lambdaFunctionName} \\ + --runtime provided.al2023 \\ + --handler bootstrap \\ + --role \\ + --zip-file fileb://function.zip \\ + --timeout 600 \\ + --memory-size 256 \\ + --environment '{"Variables":{"HOME":"/tmp","TEMPORAL_ADDRESS":"${namespace}.tmprl.cloud:7233","TEMPORAL_NAMESPACE":"${namespace}","TEMPORAL_API_KEY":""}}'`; +} + +function generateIamScript(config) { + const { lambdaArn } = config; + return `# Deploy the CloudFormation template to create the invocation role +aws cloudformation create-stack \\ + --stack-name my-temporal-invoke-role \\ + --template-body file://temporal-invoke-role.yaml \\ + --parameters \\ + ParameterKey=TemporalPrincipalArn,ParameterValue= \\ + ParameterKey=ExternalId,ParameterValue= \\ + ParameterKey=LambdaFunctionArn,ParameterValue=${lambdaArn} \\ + --capabilities CAPABILITY_IAM + +# TemporalPrincipalArn and ExternalId are provided by Temporal +# in your Namespace configuration.`; +} + +function generateCliCode(config) { + const { namespace, deploymentName, buildId, lambdaArn } = config; + return `temporal worker deployment create-version \\ + --namespace ${namespace} \\ + --deployment-name ${deploymentName} \\ + --build-id ${buildId} \\ + --aws-lambda-invoke ${lambdaArn}`; +} + +function generateSetCurrentVersion(config) { + const { namespace, deploymentName, buildId } = config; + return `temporal worker deployment set-current-version \\ + --namespace ${namespace} \\ + --deployment-name ${deploymentName} \\ + --build-id ${buildId}`; +} + +function generateStartWorkflow(config) { + const { namespace, taskQueue } = config; + return `temporal workflow start \\ + --namespace ${namespace} \\ + --task-queue ${taskQueue} \\ + --type MyWorkflow \\ + --input '"Hello, serverless!"'`; +} + +// --------------------------------------------------------------------------- +// Steps configuration — ties steps to code tabs +// --------------------------------------------------------------------------- + +const STEPS = [ + { + id: 'worker', + number: '1', + title: 'Write Worker code', + description: + 'Use the lambdaworker package to write a Worker that runs inside a Lambda function. Register Workflows and Activities the same way you would with a standard Worker.', + codeLabel: 'Worker Code', + language: 'go', + generate: generateWorkerCode, + }, + { + id: 'deploy', + number: '2', + title: 'Deploy to Lambda', + description: + 'Cross-compile for Linux, package the binary, and create the Lambda function. Configure the Temporal connection using environment variables.', + codeLabel: 'Deploy Script', + language: 'bash', + generate: generateDeployScript, + }, + { + id: 'iam', + number: '3', + title: 'Configure IAM for Temporal invocation', + description: + 'Deploy a CloudFormation template that creates an IAM role allowing Temporal to invoke your Lambda function. The principal ARN and External ID are provided in your Temporal Namespace configuration.', + codeLabel: 'IAM Setup', + language: 'bash', + generate: generateIamScript, + }, + { + id: 'cli', + number: '4', + title: 'Create a Worker Deployment Version', + description: + 'Use the CLI to create a Worker Deployment Version with your Lambda ARN as the compute provider. The deployment name and Build Id must match your Worker code.', + codeLabel: 'CLI Command', + language: 'bash', + generate: generateCliCode, + }, + { + id: 'set-current', + number: '5', + title: 'Set the current version', + description: + 'Promote the version to current so Temporal routes Tasks to it. New Workflow Executions and auto-upgrade Workflows will use this version.', + codeLabel: 'CLI Command', + language: 'bash', + generate: generateSetCurrentVersion, + }, + { + id: 'start', + number: '6', + title: 'Start a Workflow', + description: + 'Start a Workflow on the same Task Queue. When the Task arrives with no active pollers, Temporal invokes your Lambda function. The Worker starts, processes the Task, and shuts down.', + codeLabel: 'Start Workflow', + language: 'bash', + generate: generateStartWorkflow, + }, +]; + +// --------------------------------------------------------------------------- +// Flow diagram nodes +// --------------------------------------------------------------------------- + +const FLOW_NODES = [ + { label: 'Start', sub: 'Client' }, + { label: 'Task Queue', sub: 'No pollers' }, + { label: 'Temporal', sub: 'Invokes Worker' }, + { label: 'Worker', sub: 'Processing' }, + { label: 'Done', sub: 'Result' }, +]; + +const IDLE_NODES = Array(FLOW_NODES.length).fill('pending'); + +const DEFAULT_CONFIG = { + deploymentName: 'my-app', + buildId: 'build-1', + taskQueue: 'serverless-task-queue', + namespace: 'your-namespace.your-account', + lambdaFunctionName: 'my-temporal-worker', + lambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-temporal-worker', +}; + +// --------------------------------------------------------------------------- +// Main component +// --------------------------------------------------------------------------- + +export default function ServerlessWorkerDemo() { + const [activeStep, setActiveStep] = useState(0); + const [config, setConfig] = useState({ ...DEFAULT_CONFIG }); + + const [sim, setSim] = useState({ + running: false, + nodeStates: [...IDLE_NODES], + log: [], + status: 'idle', + result: null, + }); + + const runIdRef = useRef(0); + const logScrollRef = useRef(null); + const codeRef = useRef(null); + + useEffect(() => { + if (logScrollRef.current) { + logScrollRef.current.scrollTop = logScrollRef.current.scrollHeight; + } + }, [sim.log]); + + const updateConfig = useCallback((key, value) => { + setConfig((prev) => ({ ...prev, [key]: value })); + }, []); + + const handleFunctionNameChange = useCallback((value) => { + setConfig((prev) => ({ + ...prev, + lambdaFunctionName: value, + lambdaArn: `arn:aws:lambda:us-east-1:123456789012:function:${value}`, + })); + }, []); + + const handleStepClick = useCallback((index) => { + setActiveStep(index); + }, []); + + const currentStep = STEPS[activeStep]; + const code = currentStep.generate(config); + + const handleSimulate = useCallback(() => { + const runId = ++runIdRef.current; + const isCancelled = () => runIdRef.current !== runId; + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + const startTime = Date.now(); + const logEntries = []; + + const elapsed = () => ((Date.now() - startTime) / 1000).toFixed(2); + + const update = (nodeStates, msg, type = 'info') => { + if (isCancelled()) return; + if (msg) logEntries.push({ time: elapsed(), msg, type }); + setSim((prev) => ({ + ...prev, + running: true, + nodeStates, + log: [...logEntries], + status: 'running', + })); + }; + + setSim({ + running: true, + nodeStates: [...IDLE_NODES], + log: [], + status: 'running', + result: null, + }); + + (async () => { + update( + ['active', 'pending', 'pending', 'pending', 'pending'], + `Workflow started on task queue "${config.taskQueue}"...` + ); + await sleep(600); + if (isCancelled()) return; + + update( + ['completed', 'active', 'pending', 'pending', 'pending'], + 'Task enqueued. No active pollers detected on task queue.' + ); + await sleep(800); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'active', 'pending', 'pending'], + 'Temporal Service detected unpolled task. Invoking compute provider...' + ); + await sleep(600); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'active', 'pending', 'pending'], + 'Assuming IAM role via cross-account trust. Validating External ID.', + 'info' + ); + await sleep(500); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `Lambda function "${config.lambdaFunctionName}" invoked.` + ); + await sleep(500); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `Worker started. Deployment: "${config.deploymentName}", Build: "${config.buildId}".` + ); + await sleep(400); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `Worker polling task queue "${config.taskQueue}" for tasks...` + ); + await sleep(500); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + 'Picked up Workflow Task. Executing MyWorkflow...' + ); + await sleep(600); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + 'Executing Activity: MyActivity...' + ); + await sleep(500); + if (isCancelled()) return; + + const result = 'Workflow completed successfully'; + logEntries.push({ + time: elapsed(), + msg: 'Activity completed. Workflow result returned to caller.', + type: 'success', + }); + logEntries.push({ + time: elapsed(), + msg: 'Worker draining. Graceful shutdown before Lambda deadline.', + type: 'info', + }); + + setSim({ + running: false, + nodeStates: ['completed', 'completed', 'completed', 'completed', 'completed'], + log: [...logEntries], + status: 'completed', + result, + }); + })(); + }, [config]); + + return ( +
+ {/* ── Simulation: the main interactive area ── */} +
+

Serverless Worker Flow

+
+ +
+
+ {FLOW_NODES.map((node, i) => ( + +
+
{node.label}
+
{node.sub}
+
+ {i < FLOW_NODES.length - 1 && ( +
+ › +
+ )} +
+ ))} +
+
+ +
+ +
+ +

Execution Log

+

Configuration

+ +
+
+ {sim.log.length === 0 ? ( +
+ Click “Simulate Workflow” to see the serverless worker flow in action +
+ ) : ( + sim.log.map((entry, i) => ( +
+ [{entry.time}s] + {entry.msg} +
+ )) + )} + {sim.status === 'completed' && ( +
Workflow completed successfully.
+ )} +
+
+ +
+
+ updateConfig('deploymentName', v)} + /> + updateConfig('buildId', v)} + /> + updateConfig('taskQueue', v)} + /> + updateConfig('namespace', v)} + /> + +
+
+
+ + + The execution log above is a simplified, combined view for educational purposes. + In a real Serverless Worker execution, logs are distributed across different services + (Temporal, AWS Lambda, your application) with varying visibility. + + + {/* ── Step-by-step walkthrough: steps left, code right ── */} +
+
+

Step-by-step walkthrough

+
+ {STEPS.map((step, i) => ( + + ))} +
+
+ +
+

+ Step {currentStep.number}:{' '} + {currentStep.codeLabel} +

+ {code} +
+
+
+ ); +} + +// --------------------------------------------------------------------------- +// Sub-components +// --------------------------------------------------------------------------- + +function ConfigField({ label, value, onChange, type = 'text', min, max }) { + return ( +
+ + onChange(e.target.value)} + /> +
+ ); +} diff --git a/src/components/elements/serverless-worker-demo.module.css b/src/components/elements/serverless-worker-demo.module.css new file mode 100644 index 0000000000..d58eb91d90 --- /dev/null +++ b/src/components/elements/serverless-worker-demo.module.css @@ -0,0 +1,517 @@ +/* ── Root ───────────────────────────────────────────────────────────────── */ + +.demo { + font-family: var(--ifm-font-family-base); +} + +/* ── Steps + Code side-by-side ─────────────────────────────────────────── */ + +.stepsAndCode { + display: flex; + gap: 24px; + align-items: flex-start; + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid var(--ifm-color-emphasis-200); +} + +.stepsCol { + flex: 0 0 280px; + min-width: 0; +} + +.codeCol { + flex: 1; + min-width: 0; + position: sticky; + top: 80px; +} + +@media (max-width: 900px) { + .stepsAndCode { + flex-direction: column; + } + .stepsCol { + flex: none; + width: 100%; + } +} + +/* ── Two-column layout (simulation + config) ──────────────────────────── */ + +.columns { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto 1fr auto; + gap: 8px 24px; +} + +.row1Left { + grid-column: 1; + grid-row: 1; + align-self: end; +} + +.row1Right { + grid-column: 2; + grid-row: 1; + align-self: end; +} + +.row2Left { + grid-column: 1; + grid-row: 2; + align-self: center; +} + +.row2Right { + grid-column: 2; + grid-row: 2; + align-self: center; +} + +.row3Left { + grid-column: 1; + grid-row: 3; + align-self: end; + padding-top: 16px; +} + +.row3Right { + grid-column: 2; + grid-row: 3; + align-self: end; + padding-top: 16px; +} + +.row4Left { + grid-column: 1; + grid-row: 4; + margin-bottom: 20px; +} + +.row4Right { + grid-column: 2; + grid-row: 4; + margin-bottom: 20px; +} + +@media (max-width: 900px) { + .columns { + grid-template-columns: 1fr; + grid-template-rows: auto; + } + .row1Left, .row1Right, + .row2Left, .row2Right, + .row3Left, .row3Right, + .row4Left, .row4Right { + grid-column: 1; + grid-row: auto; + } +} + +/* ── Section ────────────────────────────────────────────────────────────── */ + +.section { + margin-bottom: 20px; +} + +.sectionTitle { + font-size: 0.95rem; + font-weight: 600; + margin: 0 0 8px; + color: var(--ifm-font-color-base); +} + +/* ── Config form ────────────────────────────────────────────────────────── */ + +.configGrid { + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + overflow: hidden; +} + +.configRow { + display: flex; + align-items: center; + padding: 7px 12px; + border-bottom: 1px solid var(--ifm-color-emphasis-200); + background: var(--ifm-background-surface-color); +} + +.configRow:last-child { + border-bottom: none; +} + +.configLabel { + flex: 1; + font-size: 0.83rem; + color: var(--ifm-font-color-secondary); + user-select: none; +} + +.configInput { + width: 200px; + padding: 4px 8px; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 4px; + background: var(--ifm-background-color); + color: var(--ifm-font-color-base); + font-size: 0.83rem; +} + +/* ── Code tabs ──────────────────────────────────────────────────────────── */ + +.codeTabs { + display: flex; + gap: 4px; + margin-bottom: 8px; + flex-wrap: wrap; +} + +.codeTab { + padding: 4px 12px; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 4px; + background: var(--ifm-background-color); + color: var(--ifm-font-color-base); + cursor: pointer; + font-size: 0.8rem; + font-weight: 500; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} + +.codeTab:hover { + background: var(--ifm-color-emphasis-100); +} + +.codeTabActive { + background: var(--ifm-color-primary); + color: #fff; + border-color: var(--ifm-color-primary); +} + +:global(html[data-theme='dark']) .codeTabActive { + background: #2563eb; + border-color: #2563eb; +} + +/* ── Execute button ─────────────────────────────────────────────────────── */ + +.executeBtn { + width: 100%; + padding: 12px 24px; + background: var(--ifm-color-primary); + color: #fff; + border: none; + border-radius: 6px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: background 0.2s; +} + +:global(html[data-theme='dark']) .executeBtn { + background: #2563eb; +} + +.executeBtn:hover:not(.executeBtnDisabled) { + background: var(--ifm-color-primary-dark); +} + +:global(html[data-theme='dark']) .executeBtn:hover:not(.executeBtnDisabled) { + background: #1d4ed8; +} + +.executeBtnDisabled { + opacity: 0.65; + cursor: not-allowed; +} + +/* ── Spinner ────────────────────────────────────────────────────────────── */ + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.35); + border-top-color: #fff; + border-radius: 50%; + animation: spin 0.75s linear infinite; + flex-shrink: 0; +} + +/* ── Flow diagram ───────────────────────────────────────────────────────── */ + +.flowDiagram { + display: flex; + align-items: center; + justify-content: space-between; + gap: 2px; + padding: 12px 4px; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + overflow-x: auto; +} + +.flowNode { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 44px; + flex: 1; + padding: 6px 3px; + border-radius: 6px; + border: 2px solid var(--ifm-color-emphasis-300); + background: var(--ifm-background-color); + transition: border-color 0.3s, background 0.3s, box-shadow 0.3s; +} + +.flowNodeLabel { + font-size: 0.68rem; + font-weight: 600; + text-align: center; + line-height: 1.2; +} + +.flowNodeSub { + font-size: 0.58rem; + color: var(--ifm-font-color-secondary); + text-align: center; + margin-top: 2px; +} + +/* Node states */ +.flowNode_pending { + border-color: var(--ifm-color-emphasis-300); + opacity: 0.5; +} + +.flowNode_active { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-primary-lightest, rgba(55, 125, 255, 0.08)); + box-shadow: 0 0 8px rgba(55, 125, 255, 0.25); + opacity: 1; +} + +.flowNode_completed { + border-color: var(--ifm-color-success); + opacity: 1; +} + +.flowNode_failed { + border-color: var(--ifm-color-danger); + opacity: 1; +} + +.flowArrow { + font-size: 1.1rem; + color: var(--ifm-color-emphasis-300); + flex-shrink: 0; + transition: color 0.3s; + user-select: none; +} + +.flowArrowLit { + color: var(--ifm-color-success); +} + +/* ── Log ────────────────────────────────────────────────────────────────── */ + +.log { + background: var(--ifm-pre-background); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + padding: 12px; + min-height: 100%; + max-height: 300px; + overflow-y: auto; + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + line-height: 1.6; + display: flex; + flex-direction: column; +} + +.logPlaceholder { + color: var(--ifm-font-color-secondary); + font-style: italic; + text-align: center; + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.logLine { + display: flex; + gap: 8px; +} + +.logTime { + color: var(--ifm-font-color-secondary); + flex-shrink: 0; + min-width: 52px; +} + +.logMsg { + word-break: break-word; +} + +.logLine_info .logMsg { + color: var(--ifm-font-color-base); +} + +.logLine_success .logMsg { + color: var(--ifm-color-success-darkest, #2e7d32); +} + +[data-theme='dark'] .logLine_success .logMsg { + color: var(--ifm-color-success-lightest, #81c784); +} + +.logLine_error .logMsg { + color: var(--ifm-color-danger); +} + +.logLine_warn .logMsg { + color: var(--ifm-color-warning-darkest, #e65100); +} + +[data-theme='dark'] .logLine_warn .logMsg { + color: var(--ifm-color-warning-lightest, #ffb74d); +} + +/* ── Result banner ──────────────────────────────────────────────────────── */ + +.resultSuccess { + margin-top: 10px; + padding: 10px 14px; + background: var(--ifm-color-success-contrast-background, rgba(46, 125, 50, 0.08)); + border: 1px solid var(--ifm-color-success); + border-radius: 6px; + font-size: 0.85rem; + font-weight: 600; + color: var(--ifm-color-success-darkest, #2e7d32); +} + +[data-theme='dark'] .resultSuccess { + color: var(--ifm-color-success-lightest, #81c784); + background: rgba(76, 175, 80, 0.15); +} + +/* ── Steps (clickable) ──────────────────────────────────────────────────── */ + +.stepsContainer { + display: flex; + flex-direction: column; + gap: 4px; +} + +.step { + display: flex; + gap: 10px; + align-items: flex-start; + padding: 8px 12px; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + background: var(--ifm-background-surface-color); + cursor: pointer; + transition: border-color 0.2s, background 0.2s, box-shadow 0.2s; + text-align: left; + width: 100%; + font-family: inherit; + color: inherit; +} + +.step:hover { + border-color: var(--ifm-color-emphasis-400); + background: var(--ifm-color-emphasis-100); +} + +.stepActive { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-primary-lightest, rgba(55, 125, 255, 0.06)); + box-shadow: 0 0 0 1px var(--ifm-color-primary); +} + +:global(html[data-theme='dark']) .stepActive { + border-color: #2563eb; + background: rgba(37, 99, 235, 0.12); + box-shadow: 0 0 0 1px #2563eb; +} + +.stepActive:hover { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-primary-lightest, rgba(55, 125, 255, 0.06)); +} + +:global(html[data-theme='dark']) .stepActive:hover { + border-color: #2563eb; + background: rgba(37, 99, 235, 0.18); +} + +.stepNumber { + flex-shrink: 0; + width: 24px; + height: 24px; + border-radius: 50%; + background: var(--ifm-color-emphasis-300); + color: #fff; + font-size: 0.75rem; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + margin-top: 1px; + transition: background 0.2s; +} + +.stepNumberActive { + background: var(--ifm-color-primary); +} + +:global(html[data-theme='dark']) .stepNumberActive { + background: #2563eb; +} + +.stepContent { + flex: 1; + min-width: 0; +} + +.stepTitle { + font-size: 0.83rem; + font-weight: 600; + line-height: 1.3; +} + +.stepDesc { + font-size: 0.78rem; + color: var(--ifm-font-color-base); + line-height: 1.4; + margin-top: 4px; +} + +/* ── Code step label ────────────────────────────────────────────────────── */ + +.codeStepLabel { + color: var(--ifm-color-primary); + font-weight: 700; +} + +:global(html[data-theme='dark']) .codeStepLabel { + color: #60a5fa; +} diff --git a/src/components/index.js b/src/components/index.js index 07b4ad9ef6..9c82476efc 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -5,6 +5,7 @@ export { SdkLogos } from './elements/SdkLogos'; export { SdkLogosAsBlocks } from './elements/SdkLogosAsBlocks'; export { default as PhotoCarousel } from './elements/PhotoCarousel'; export { default as SdkTabs } from './elements/SdkTabs'; +export { default as ServerlessWorkerDemo } from './elements/ServerlessWorkerDemo'; // Formatting components export { default as DocsTable, NewDocsCell, DocsTableRow } from './formatting/DocsTable'; diff --git a/static/diagrams/serverless-worker-flow-dark.svg b/static/diagrams/serverless-worker-flow-dark.svg new file mode 100644 index 0000000000..a13c90f1e8 --- /dev/null +++ b/static/diagrams/serverless-worker-flow-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/diagrams/serverless-worker-flow.svg b/static/diagrams/serverless-worker-flow.svg new file mode 100644 index 0000000000..aeea36b1f4 --- /dev/null +++ b/static/diagrams/serverless-worker-flow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/diagrams/serverless-worker-lifecycle-dark.svg b/static/diagrams/serverless-worker-lifecycle-dark.svg new file mode 100644 index 0000000000..4ed19ca017 --- /dev/null +++ b/static/diagrams/serverless-worker-lifecycle-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/diagrams/serverless-worker-lifecycle.svg b/static/diagrams/serverless-worker-lifecycle.svg new file mode 100644 index 0000000000..6587b0f5d2 --- /dev/null +++ b/static/diagrams/serverless-worker-lifecycle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/files/temporal-cloud-serverless-worker-role.yaml b/static/files/temporal-cloud-serverless-worker-role.yaml new file mode 100644 index 0000000000..f3cfd18aea --- /dev/null +++ b/static/files/temporal-cloud-serverless-worker-role.yaml @@ -0,0 +1,97 @@ +# CloudFormation template for creating an IAM role that Temporal Cloud can assume to invoke Lambda functions. +AWSTemplateFormatVersion: '2010-09-09' +Description: Creates an IAM role that Temporal Cloud can assume to invoke multiple Lambda functions for Serverless Workers. + +Parameters: + AssumeRoleExternalId: + Type: String + Description: A string you choose. Can be any value. + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of Lambda function ARNs to invoke + (e.g., arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2) + + RoleName: + Type: String + Default: 'Temporal-Cloud-Serverless-Worker' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Temporal Cloud Configuration" + Parameters: + - AssumeRoleExternalId + - Label: + default: "Lambda Configuration" + Parameters: + - LambdaFunctionARNs + - RoleName + ParameterLabels: + AssumeRoleExternalId: + default: "External ID" + LambdaFunctionARNs: + default: "Lambda Function ARNs (comma-separated list)" + RoleName: + default: "IAM Role Name" + +Resources: + TemporalCloudServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: + [ + arn:aws:iam::902542641901:role/wci-lambda-invoke, + arn:aws:iam::160190466495:role/wci-lambda-invoke, + arn:aws:iam::819232936619:role/wci-lambda-invoke, + arn:aws:iam::829909441867:role/wci-lambda-invoke, + arn:aws:iam::354116250941:role/wci-lambda-invoke + ] + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': [!Ref AssumeRoleExternalId] + Description: "The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers" + MaxSessionDuration: 3600 # 1 hour + + TemporalCloudLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalCloudServerlessWorker + Properties: + PolicyName: 'Temporal-Cloud-Lambda-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub '${RoleName}-${AWS::StackName}' + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for Temporal Cloud + Value: !GetAtt TemporalCloudServerlessWorker.Arn + Export: + Name: !Sub "${AWS::StackName}-RoleARN" + + RoleName: + Description: The name of the IAM role + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked + Value: !Join [", ", !Ref LambdaFunctionARNs] diff --git a/static/files/temporal-self-hosted-serverless-worker-role.yaml b/static/files/temporal-self-hosted-serverless-worker-role.yaml new file mode 100644 index 0000000000..488fa1ad96 --- /dev/null +++ b/static/files/temporal-self-hosted-serverless-worker-role.yaml @@ -0,0 +1,74 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Creates an IAM role that a self-hosted Temporal Service can assume to invoke Lambda functions for Serverless Workers. + +Parameters: + TemporalIamRoleArn: + Type: String + Description: The ARN of the IAM role or user that the Temporal Service runs as. + + AssumeRoleExternalId: + Type: String + Description: A unique identifier to prevent confused deputy attacks. + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of Lambda function ARNs to invoke + (e.g., arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2) + + RoleName: + Type: String + Default: 'Temporal-Serverless-Worker' + +Resources: + TemporalServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: + [!Ref TemporalIamRoleArn] + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': [!Ref AssumeRoleExternalId] + Description: "The role the Temporal Service uses to invoke Lambda functions for Serverless Workers" + MaxSessionDuration: 3600 + + TemporalLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalServerlessWorker + Properties: + PolicyName: 'Temporal-Lambda-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub '${RoleName}-${AWS::StackName}' + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for the Temporal Service + Value: !GetAtt TemporalServerlessWorker.Arn + Export: + Name: !Sub "${AWS::StackName}-RoleARN" + + RoleName: + Description: The name of the IAM role + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked + Value: !Join [", ", !Ref LambdaFunctionARNs] diff --git a/vale/styles/Temporal/terms.yml b/vale/styles/Temporal/terms.yml index 0504678fd2..ecf3921314 100644 --- a/vale/styles/Temporal/terms.yml +++ b/vale/styles/Temporal/terms.yml @@ -102,6 +102,8 @@ swap: temporal sdks: Temporal SDKs temporal server: Temporal Server '\bworker\b': Worker + serverless worker: Serverless Worker + serverless workers: Serverless Workers worker controller: Worker Controller '\bworkflow\b': Workflow timer: Timer diff --git a/vale/styles/config/vocabularies/Temporal/accept.txt b/vale/styles/config/vocabularies/Temporal/accept.txt index 264999c357..7982118437 100644 --- a/vale/styles/config/vocabularies/Temporal/accept.txt +++ b/vale/styles/config/vocabularies/Temporal/accept.txt @@ -7,7 +7,7 @@ sample samples attempt multiple -serverless +[Ss]erverless Namespace gRPC tctl diff --git a/vercel.json b/vercel.json index cf17b54fd2..68698a992c 100644 --- a/vercel.json +++ b/vercel.json @@ -6,6 +6,8 @@ }, "redirects": [ { + "source": "/evaluate/serverless-workers-demo", + "destination": "/evaluate/serverless-workers/demo", "source": "/develop/go/data-handling/large-payload-storage", "destination": "/develop/go/data-handling/external-storage", "permanent": true @@ -2033,6 +2035,11 @@ "source": "/develop/typescript/versioning", "destination": "/develop/typescript/workflows/versioning", "permanent": true + }, + { + "source": "/develop/go/workers/cloud-worker", + "destination": "/develop/go/workers/run-worker-process#connect-to-temporal-cloud", + "permanent": true } ] }