Skip to content

Multithreading in Action Kits ​

Java ​

Each action runs as a single instance with one consumer thread that pulls work from the queue. That consumer can process up to N items concurrently, where N is the action's configured concurrency. This is decoupled from the number of instances and pods: raising concurrency adds in-memory workers, not threads or Valkey connections, so you can scale an action's throughput without scaling the deployment to extra pods.

Concurrency is bounded per action by a gate; in-flight work for an action never exceeds its limit. The default concurrency, when none is configured, is 1.

Actions must be thread-safe. A single action instance is shared across all concurrent invocations, so raising concurrency above 1 runs multiple invocations against the same object simultaneously. Actions must not keep per-invocation state in instance fields — see Actions Must Be Thread-Safe. Default concurrency is 1, so an action is only invoked concurrently once you raise its limit.

Configuring Concurrency at Runtime ​

Per-action concurrency can be changed at runtime with no restart. A change takes effect on running plugins within a few seconds — growing takes effect immediately, shrinking drains in-flight work first.

bash
deltafi action concurrency list                                          # all actions and current concurrency
deltafi action concurrency set org.deltafi.core.action.egress.RestEgress 5
deltafi action concurrency reset org.deltafi.core.action.egress.RestEgress

list reports each action's effective concurrency, a Source column showing where the value comes from — default (the system default of 1), config (the plugin's declared actions.actionThreads value), or override (a runtime value set with set) — and a Tunable column. Only actions whose kit honors runtime changes (this Java action kit) are tunable; actions on older kits or other languages appear in list but set rejects them, and they are omitted from set/reset shell completion.

The same list, set, and reset controls are available in the GUI under Configuration → Actions (viewing requires the PluginsView permission; setting and resetting require ActionConcurrencyUpdate).

reset reverts an action to its declared bootstrap value — the plugin's actions.actionThreads setting if it has one, otherwise 1. It does not force the value to 1; to pin an action below its declared default, use set with the value you want.

Runtime overrides persist across restarts and are captured in system snapshots.

Bootstrap Default (application.yaml) ​

To set an action's starting concurrency before any runtime override is applied, add an entry under the actions namespace in your application.yaml:

yaml
actions:
  actionThreads:
    org.deltafi.core.action.FilterEgressAction: 2

The effective concurrency is the runtime override if one is set, otherwise this bootstrap value, otherwise 1. Any value is clamped to actions.maxConcurrency (default 64).

Threading inside an action ​

Actions must be thread-safe (see Actions Must Be Thread-Safe for the why). The framework already runs many DeltaFiles through an action in parallel, so you rarely need to manage threads yourself — raising the action's concurrency is the supported way to scale throughput.

Do

  • Keep per-DeltaFile state in local variables and method arguments — never in shared/instance state.
  • Make any collaborator you store and reuse thread-safe. Most HTTP clients and connection pools are; objects like a non-thread-safe date formatter or XML parser are not — create those per invocation or use a thread-safe variant.
  • Prefer raising the action's configured concurrency over hand-rolling parallelism inside a single invocation.

Don't

  • Don't spawn unbounded threads or a fresh thread pool per invocation. Your threads multiply by the action's concurrency (N concurrent invocations × your threads) and can exhaust the pod. If one DeltaFile genuinely needs internal parallelism, use a small bounded executor and shut it down before the method returns.
  • Don't stash a value in shared state "to pass it between methods" — pass it as a parameter instead.
  • Don't assume invocations run in order or that one can see another's state; they are independent and may overlap. Note that internal threads you start also bypass the per-action concurrency limit and its backpressure, making it easy to overwhelm a downstream system.

Go ​

The Go action kit supports configurable worker counts per action. Set the worker count globally or per action.

Global Worker Count ​

Set the ACTION_THREADS environment variable in your Dockerfile:

dockerfile
ENV ACTION_THREADS=2

Per-Action Worker Count ​

Set an environment variable using the pattern ACTIONS_ACTIONTHREADS_{UPPERCASED_ACTION_NAME}, where dots and hyphens in the action name are replaced with underscores:

dockerfile
ENV ACTIONS_ACTIONTHREADS_ORG_DELTAFI_MY_PLUGIN_MYTRANSFORMACTION=4

Or set it programmatically before calling RunPlugin:

go
plugin.SetWorkerCount("org.myorg.my-plugin.MyTransformAction", 4)

By default, if an action does not have a specific thread count configuration, it will use 1 worker goroutine.

C++ ​

The C++ action kit supports the same configurable worker counts as Go.

Global Worker Count ​

Set the ACTION_THREADS environment variable in your Dockerfile:

dockerfile
ENV ACTION_THREADS=2

Per-Action Worker Count ​

Set an environment variable using the pattern ACTIONS_ACTIONTHREADS_{UPPERCASED_ACTION_NAME}, where dots and hyphens in the action name are replaced with underscores:

dockerfile
ENV ACTIONS_ACTIONTHREADS_ORG_DELTAFI_MY_PLUGIN_MYTRANSFORMACTION=4

Or set it programmatically:

cpp
plugin.set_worker_count("org.myorg.my-plugin.MyTransformAction", 4);

By default, if an action does not have a specific thread count configuration, it will use 1 worker thread.

Contact US