Temporal Latency

I’m investigating moving to Temporal from AWS SWF. One interest is latency. I’m seeing a 300ms 99th percentile latency from sending a request to complete one activity, the workflow scheduling a second activity and the activity worker picking up the second activity.

Latency was mentioned here but I’m wondering what I might expect from Temporal.

1 Like

I don’t fully understand the information in you question. Is the 300ms number you’re referencing SWF or Temporal latency? Also it’s incredibly difficult to understand the latency without more context. For example, does this include round trip latency? What Region/AZ is your infra running in and where are you?

Generally speaking, Temporal is low latency enough to make synchronous flows possible (click a button and get a response back in reasonable time). That being said, Temporal is not low latency enough to be used for something realtime like VR. If you can provide some more specifics I am happy to help, but I also recommend just deploying a local cluster to get a rough idea.

That 300 ms latency is running on Amazon SWF, in the same region (us-east-1). Time from starting the request to complete one activity ( RespondActivityTaskCompleted) until the next activity is triggered ( PollForActivityTask receives new activity).

This is basically for a front end UI that will be performing activities and integrating with the workflow.

This is basically the same as if in the temporal-samples project we changed HelloActivity to

/*
 *  Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
 *
 *  Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 *  Modifications copyright (C) 2017 Uber Technologies, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"). You may not
 *  use this file except in compliance with the License. A copy of the License is
 *  located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 *  or in the "license" file accompanying this file. This file is distributed on
 *  an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 *  express or implied. See the License for the specific language governing
 *  permissions and limitations under the License.
 */

package io.temporal.samples.hello;

import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
import io.temporal.activity.ActivityOptions;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
import io.temporal.workflow.Workflow;
import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
import java.time.Duration;

/**
 * Hello World Temporal workflow that executes a single activity. Requires a local instance the
 * Temporal service to be running.
 */
public class HelloActivity {

  static final String TASK_QUEUE = "HelloActivity";

  public static void main(String[] args) {
    // gRPC stubs wrapper that talks to the local docker instance of temporal service.
    WorkflowServiceStubs service = WorkflowServiceStubs.newInstance();
    // client that can be used to start and signal workflows
    WorkflowClient client = WorkflowClient.newInstance(service);

    // worker factory that can be used to create workers for specific task queues
    WorkerFactory factory = WorkerFactory.newInstance(client);
    // Worker that listens on a task queue and hosts both workflow and activity implementations.
    Worker worker = factory.newWorker(TASK_QUEUE);
    // Workflows are stateful. So you need a type to create instances.
    worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class);
    // Activities are stateless and thread safe. So a shared instance is used.
    worker.registerActivitiesImplementations(new GreetingActivitiesImpl());
    // Start listening to the workflow and activity task queues.
    factory.start();

    // Start a workflow execution. Usually this is done from another program.
    // Uses task queue from the GreetingWorkflow @WorkflowMethod annotation.
    GreetingWorkflow workflow =
        client.newWorkflowStub(
            GreetingWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
    // Execute a workflow waiting for it to complete. See {@link
    // io.temporal.samples.hello.HelloSignal}
    // for an example of starting workflow without waiting synchronously for its result.
    String greeting = workflow.getGreeting("World");
    System.out.println(greeting);
    System.exit(0);
  }

  /** Workflow interface has to have at least one method annotated with @WorkflowMethod. */
  @WorkflowInterface
  public interface GreetingWorkflow {
    @WorkflowMethod
    String getGreeting(String name);
  }

  /** Activity interface is just a POJI. */
  @ActivityInterface
  public interface GreetingActivities {
    @ActivityMethod
    String composeGreeting(String greeting, String name);
  }

  /** GreetingWorkflow implementation that calls GreetingsActivities#composeGreeting. */
  public static class GreetingWorkflowImpl implements GreetingWorkflow {

    /**
     * Activity stub implements activity interface and proxies calls to it to Temporal activity
     * invocations. Because activities are reentrant, only a single stub can be used for multiple
     * activity invocations.
     */
    private final GreetingActivities activities =
        Workflow.newActivityStub(
            GreetingActivities.class,
            ActivityOptions.newBuilder().setScheduleToCloseTimeout(Duration.ofSeconds(2)).build());

    @Override
    public String getGreeting(String name) {
      // This is a blocking call that returns only after the activity has completed.
      activities.composeGreeting("Hello", name);
      activities.composeGreeting("Hello", name);
      activities.composeGreeting("Hello", name);
      activities.composeGreeting("Hello", name);
      return activities.composeGreeting("Hello", name);
    }
  }

  static class GreetingActivitiesImpl implements HelloActivityRetry.GreetingActivities {
    private long lastInvocationTime;

    @Override
    public synchronized String composeGreeting(String greeting, String name) {
      if (lastInvocationTime != 0) {
        long timeSinceLastInvocation = System.currentTimeMillis() - lastInvocationTime;
        System.out.print(timeSinceLastInvocation + " milliseconds since last invocation. ");
      }
      lastInvocationTime = System.currentTimeMillis();
      System.out.println("composeGreeting activity is going to complete");
      return greeting + " " + name + "!";
    }
  }
}

Running locally, that prints out

43 milliseconds since last invocation. composeGreeting activity is going to complete
38 milliseconds since last invocation. composeGreeting activity is going to complete
37 milliseconds since last invocation. composeGreeting activity is going to complete
32 milliseconds since last invocation. composeGreeting activity is going to complete

How might that change using a Postgres/Cassandra DB within the same Region/AZ? With deciders/activity workers on different machines?

The local deployment is not tailored for any performance testing. I wouldn’t be surprised if you get lower latency in a real production environment.