Temporal Namespaces and Task Queues#

Namespaces and task queues are Temporal’s two primary organizational mechanisms. Namespaces provide isolation – separate history, retention, and access. Task queues route work to specific workers. Together, they determine where workflows run and how long their history is kept.

For the underlying architecture, see Introduction to Temporal.

Namespaces#

A namespace is a logical isolation boundary. Every workflow belongs to exactly one namespace. Namespaces provide history isolation (workflows cannot see across boundaries), independent retention policies, per-namespace search attributes, and scoped access control.

Creating Namespaces#

With the Temporal CLI:

temporal operator namespace create \
  --namespace order-processing \
  --retention 72h \
  --description "Order processing workflows"

temporal operator namespace create \
  --namespace compliance \
  --retention 720h \
  --description "Compliance workflows with 30-day retention"

With the Go SDK:

func createNamespace() error {
    nsClient, err := client.NewNamespaceClient(client.Options{
        HostPort: "localhost:7233",
    })
    if err != nil {
        return err
    }
    defer nsClient.Close()

    retention := 72 * time.Hour
    return nsClient.Register(context.Background(),
        &workflowservice.RegisterNamespaceRequest{
            Namespace:                        "order-processing",
            WorkflowExecutionRetentionPeriod: durationpb.New(retention),
        })
}

Retention Policies#

Retention defines how long Temporal keeps event history for completed workflows. Choose based on your needs:

ScenarioRetention
Development / testing24h
Standard production72h
Financial / regulated720h (30 days), with archival
High-volume, low-value24-48h

Namespace Best Practices#

One namespace per environment. Use order-processing-dev, order-processing-staging, order-processing-prod to prevent cross-environment accidents.

One namespace per team or domain. Independent teams get independent namespaces. This avoids workflow ID collisions and allows team-specific retention.

Avoid sprawl. Do not create a namespace per microservice unless you have a genuine isolation requirement. Multiple services can share a namespace within the same domain.

Naming conventions. Lowercase with hyphens: payment-processing, user-onboarding. Add environment suffix when sharing a cluster: payment-processing-prod.

Task Queues#

A task queue routes work to workers. When you start a workflow, you specify which task queue it runs on. Workers poll specific queues. Task queues are created implicitly – the first reference to a queue name starts tracking it.

The flow: client starts workflow on "order-queue" -> Temporal places task on "order-queue" -> worker polling "order-queue" picks it up and executes.

Task Queue Patterns#

One Queue Per Workflow Type#

Each workflow type gets its own queue with dedicated workers:

w := worker.New(c, "order-queue", worker.Options{})
w.RegisterWorkflow(OrderWorkflow)
w.RegisterActivity(orderActivities)

w2 := worker.New(c, "notification-queue", worker.Options{})
w2.RegisterWorkflow(NotificationWorkflow)
w2.RegisterActivity(notificationActivities)

Clean separation, but every new workflow type requires a new deployment.

One Queue Per Resource Pool#

Route based on resource needs, not workflow type:

// Heavy compute -- deployed on large instances
w := worker.New(c, "compute-heavy", worker.Options{
    MaxConcurrentActivityExecutionSize: 4,
})
w.RegisterWorkflow(VideoTranscodeWorkflow)
w.RegisterWorkflow(DataPipelineWorkflow)

// I/O bound -- smaller instances, many concurrent calls
w2 := worker.New(c, "io-bound", worker.Options{
    MaxConcurrentActivityExecutionSize: 50,
})
w2.RegisterActivity(apiCallActivities)

Activity-Specific Queues#

A workflow can dispatch different activities to different queues. The orchestration runs on a lightweight worker while heavy activities run on specialized ones:

func ProcessOrderWorkflow(ctx workflow.Context, order Order) error {
    // Validate on the main queue
    ctx1 := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
        TaskQueue:           "order-queue",
        StartToCloseTimeout: 10 * time.Second,
    })
    err := workflow.ExecuteActivity(ctx1, ValidateOrder, order).Get(ctx, nil)
    if err != nil {
        return err
    }

    // Charge on the payment-specific queue
    ctx2 := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
        TaskQueue:           "payment-queue",
        StartToCloseTimeout: 30 * time.Second,
        RetryPolicy:         &temporal.RetryPolicy{MaximumAttempts: 3},
    })
    err = workflow.ExecuteActivity(ctx2, ChargePayment, order.PaymentInfo).Get(ctx, nil)
    if err != nil {
        return err
    }

    // Notify on the notification queue
    ctx3 := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
        TaskQueue:           "notification-queue",
        StartToCloseTimeout: 15 * time.Second,
    })
    return workflow.ExecuteActivity(ctx3, SendConfirmation, order).Get(ctx, nil)
}

This pattern is powerful for microservice architectures. Each service runs its own worker on its own queue, exposing capabilities as activities. The workflow ties them together.

Worker Binding#

A worker registers specific workflows and activities on a specific task queue. Multiple workers can poll the same queue for horizontal scaling.

func main() {
    c, err := client.Dial(client.Options{
        HostPort:  "localhost:7233",
        Namespace: "order-processing",
    })
    if err != nil {
        log.Fatalln("Unable to create client", err)
    }
    defer c.Close()

    w := worker.New(c, "order-queue", worker.Options{
        MaxConcurrentWorkflowTaskExecutionSize: 100,
        MaxConcurrentActivityExecutionSize:     50,
    })

    w.RegisterWorkflow(OrderWorkflow)
    w.RegisterWorkflow(RefundWorkflow)

    orderActs := NewOrderActivities(paymentClient, inventoryClient)
    w.RegisterActivity(orderActs)

    err = w.Run(worker.InterruptCh())
    if err != nil {
        log.Fatalln("Worker failed", err)
    }
}

Key worker.Options:

OptionPurposeDefault
MaxConcurrentWorkflowTaskExecutionSizeMax concurrent workflow tasks1000
MaxConcurrentActivityExecutionSizeMax concurrent activity tasks1000
WorkerActivitiesPerSecondRate limit for activity dispatchUnlimited

Set these based on your worker’s resources. A 2-CPU worker should not run 1000 concurrent CPU-bound activities.

Putting It Together#

A complete topology for an e-commerce platform:

Namespace: commerce-prod (retention: 72h)

  Task Queue: "order-orchestration"
    Workers: 3 replicas (lightweight orchestration)
    Workflows: OrderWorkflow, RefundWorkflow

  Task Queue: "payment-processing"
    Workers: 2 replicas (PCI-scoped network)
    Activities: ChargeCard, RefundCard

  Task Queue: "notification-dispatch"
    Workers: 2 replicas (I/O-bound)
    Activities: SendEmail, SendSMS

Namespace: commerce-analytics (retention: 168h)

  Task Queue: "analytics-pipeline"
    Workers: 2 replicas (compute-heavy)
    Workflows: DailyAggregationWorkflow

Each queue maps to a Kubernetes Deployment. Workers scale independently based on backlog. Orchestration workers are small. Payment workers have restricted network policies.

Next Steps#

With namespaces and task queues configured, write actual workflow code with Your First Temporal Workflow in Go.