When Flowable scaling with external workers beats tuning the engine alone
When heavy or slow service calls hold engine threads and database connections, tuning the async executor alone eventually stops helping. External workers move that work out of the engine: the process reaches a service task that a separate worker process, written in any language, polls for, performs and completes over the engine's API. Consider this pattern when the slow work is an outside call, a document or image job, or a batch step whose duration varies widely, and when you need to scale those workers without scaling the engine nodes that hold process state.
The Flowable docs cover the engine side of this model: the async executor that runs timers and asynchronous jobs from a thread pool, and the lock owner and lock expiration columns in the database that let one node recover jobs left behind by another. The docs do not give you a worker sizing rule, a lock duration for your workload, or a limit on how many workers your database can serve. Confirm the external task API, its topic and lock settings, and the REST endpoints for your Flowable version in the Flowable advanced documentation before you design around them. The decision therefore rests on measurements from your own environment. Check whether engine threads spend their time waiting on downstream services, whether job queues grow during peaks, and whether database connections are held during external calls. If tuning the pool and queue settings relieves the symptom without changing the work profile, the engine alone may be enough. If not, the team must own the worker contract: how work is claimed, how long a lock lasts relative to the slowest task, how failures return to the engine, and who operates the worker pools.
How topics and job acquisition decide which worker gets which job
Topic names decide which external worker can take which job, so routing starts with how you name topics. Give each class of heavy or slow work its own topic, such as document rendering or card-network calls, and point each worker pool only at the topics it can serve. A worker asks the engine for jobs under a topic filter and receives a lock on each job it takes. The lock is recorded in the database as a lock owner and a lock expiration time, the same coordination model the async executor uses across nodes. Before you write worker code, confirm the exact acquisition call, its parameters and the default lock duration for your Flowable version in the Flowable advanced docs.
If a worker dies mid-task, nothing releases its lock explicitly. The lock stops being renewed, and once the expiration time passes, another worker or node can acquire the same job. The job may therefore run twice, so every side effect needs to be idempotent. A common approach is to record a request key in your own system before calling a payment or messaging API, then skip the call if that key already exists. Set the lock duration longer than your slowest normal execution with margin, and test the recovery path by stopping a worker during a task in a staging environment. Also confirm whether the retry counter decrements when a lock expires, because that determines when a job moves to a failed state and needs operator attention.
The same locking model shapes database load. Every acquisition query and lock renewal touches the job table, so a large pool of workers polling for small jobs creates reads and updates even when no work is waiting. Short lock durations increase this churn, while long ones delay recovery after a crash. Measure job table activity under a realistic load test before you settle on pool sizes and polling intervals. Keep worker pool sizing separate from the async executor settings in the engine, and confirm with your Flowable version that external workers do not occupy the engine's async executor threads.
Which external worker configuration fits which workload
This table compares common external worker configurations for Flowable workloads, showing when each fits and what it does to engine threads, database load and testing. Read each row against your job volume and the slowest step, then use the testing column to confirm the choice under realistic concurrency before production.
| Configuration | When it fits | Effect on engine threads and database load | What to test |
|---|---|---|---|
| One shared worker pool | Few topics with similar task duration and modest volume | Simple to operate, but one slow topic can starve fast topics of worker capacity and delay their completions | Latency for fast topics while a slow topic is saturated |
| Separate pool per topic | Topics with very different duration, volume or failure behavior | Each pool scales on its own, adding more connections and lock traffic as pools multiply | Scaling each pool alone and total connection use at peak |
| Short lock duration | Steps that finish in seconds and are safe to retry quickly | Frequent lock renewals may not be needed, but expired locks are reclaimed sooner after a worker crash | Duplicate execution rate when a worker pauses or restarts mid-task |
| Long lock duration | Steps that run for minutes, such as document generation or batch calls | Fewer acquisition calls, but a crashed worker holds work until the lock expires | Recovery time after killing a worker during a long task |
| Polling for work | Workers in other languages or networks that cannot accept callbacks | Repeated fetch requests add load on the engine REST layer and database even when idle | Idle poll rate and fetch latency at target worker count |
| Callback-style completion | Results that must reach the engine promptly after an external system responds | Lower idle load, but completion calls must be retried safely if the engine is briefly unavailable | Duplicate completion handling and retry behavior on engine errors |
| In-engine delegate for fast steps | Short, deterministic logic that needs no separate scaling or network hop | Runs on the async executor thread pool, so long delegates hold engine threads and slow other jobs | Executor queue depth and job wait time under peak load |
| Database connection budget | Any configuration with several pools or workers | Each worker and the async executor draw from the same connection pool and lock the same tables | Connection pool saturation and lock wait times at combined peak |
How scaling choices differ for CPU-bound, I/O-bound and long-running work
CPU-heavy work includes document rendering, scoring models, large calculations and parsing of big files. These steps keep a thread busy for the whole run, so they compete directly with the engine's own threads and with the async executor for CPU time. Moving them out lets you size a worker pool to the machines that have spare cores, and scale it without changing the engine nodes. Keep the engine's job execution free for orchestration.
- Separate pool: Give CPU-heavy topics their own worker deployment so a burst of scoring does not starve timers and continuations.
- Pool size follows cores: Start from the cores you can dedicate and test throughput before adding workers, since more threads than cores adds contention.
- Lock duration is set by runtime: Choose a lock time longer than the slowest normal run, or the job may be handed to another worker while the first is still computing.
- Keep trivial logic in the engine: Simple expressions and short variable mapping do not justify a network hop.
- Watch memory: Large documents held in variables inflate the database and history, so pass references to stored files instead.
Before going live, confirm the expected lock duration against measured p95 runtimes in a test environment and check that the worker host has headroom at peak.
I/O-bound steps call payment gateways, core systems, mail services or partner APIs. The worker spends most of its time waiting, so CPU is rarely the limit. The limits are the remote system's rate limits, connection pools and timeouts. A small CPU footprint can still generate a large volume of calls, so the worker pool should be sized against the downstream system's capacity as much as against your own hardware.
- Concurrency is capped downstream: Set worker concurrency so the target system's agreed limits are not exceeded, even when the queue is full.
- Timeouts must sit below the lock: Client timeouts and retries should finish before the lock expires, or two workers may call the same system for one step.
- Retries belong to the worker: Handle transient errors with bounded retries and report permanent failures back as business errors or failed jobs.
- Idempotency is required: Send an idempotency key with each external call so a retried or re-acquired task does not create a duplicate payment or record.
- Isolate failing dependencies: Use a separate pool per downstream system so one slow partner does not block unrelated topics.
Ask the target system owners for their rate limits and maintenance windows, and test a failure in that system to confirm the lock, retry and idempotency behavior.
Some steps last minutes, hours or days. Batch imports, report generation that waits on a file transfer, and approvals that wait on people are examples. Holding a database transaction or a worker lock for that long is risky. For human-paced waits, the usual pattern is a user task or a timer in the engine, which costs little while idle. For batch steps, a worker can run the job but should report progress and checkpoints so a restart does not repeat finished work.
- Human waits stay in the engine: User tasks and timers hold state in the database without occupying a worker or a thread.
- Batch work gets checkpoints: Split large batches into segments and record progress so retries resume rather than restart.
- Locks must not be extended indefinitely: Long locks hide crashed workers for a long time, so design a heartbeat or shorter segments instead.
- Plan for restarts: Confirm how the job behaves when a worker node is replaced during the run.
- Avoid large variables: Store batch results in external storage and keep only identifiers in process variables.
Review each long step with the platform team and confirm the maximum expected duration, the lock settings and how a stalled batch is detected and resumed.
How to set up external workers for a Flowable deployment
This sequence moves slow service work out of the Flowable engine and into worker services that scale independently. It ends with a tested deployment in which workers claim their jobs, the engine keeps its own thread pool for process work, and expired locks are recovered after a worker fails.
Inventory slow and blocking service tasks
Process analysts and engineers list every service task, script call and external request that waits on a remote system, and record how long each typically runs and which system it depends on. Mark tasks that call rate-limited APIs, run heavy computation or hold database connections for long periods. The step is complete when each item has an owner, an observed duration and a known failure mode, and the team agrees which tasks move to workers first.
Define topics for each worker pool
Group the inventoried tasks into topics, one per capability a worker pool serves, such as payment submission or document rendering. Name each topic in the BPMN model and keep the mapping in version control alongside the process definitions. Confirm in the Flowable release you run how external worker tasks are declared and which topic attributes the engine expects, since the exact syntax is defined by that version's documentation. Check: a test deployment shows each topic appearing as work the engine can hand to a worker.
Size the engine async executor separately
Configure the async executor that runs inside the engine so its thread pool and queue cover the work that stays in the engine, and size worker capacity as a separate number. The async executor runs timers and asynchronous jobs from a thread pool and coordinates nodes through lock owners and expiration times stored in the database. Record the current pool size, queue size and acquisition behavior, then run a representative load test to confirm engine threads remain available for process navigation. Check: engine thread use and database connection use stay within the limits you set before worker traffic is added.
Configure lock durations for worker jobs
Set the lock duration for externally acquired work so it exceeds the normal completion time of the slowest task on that topic, with margin for retries. A lock that expires too early lets a second worker take a job while the first is still running, which can repeat calls to downstream systems. A lock that expires too late delays recovery after a crash. Confirm the property names and units in the Flowable advanced documentation for your version. Check: a deliberately slow test task holds its lock for the expected period and is not reacquired.
Deploy workers as independent services
Package each worker pool as its own service with a separate deployment unit, scaling policy and credentials, so the platform team can add replicas for one topic without changing the engine. Give each worker access only to the downstream system it calls, and make handlers idempotent so a retried job does not repeat side effects. Expose health and metrics endpoints for each pool. Check: scaling one pool up and down changes throughput for its topic while engine response times and other topics stay stable.
Verify lock recovery and job handoff
Test failure paths before production traffic. Stop a worker mid-task, let its lock expire, and confirm that another worker acquires the job and completes it once. In Spring Boot deployments, use the monitoring endpoint at
/actuator/flowableand the engine job tables to follow lock owners and expiration times. Repeat the test with a crash during the downstream call to confirm the idempotency design holds. Check: expired locks are reclaimed, the process records one completion for the job, and no duplicate side effects appear in the downstream system.
Security and operational risks when work leaves the engine
Once work leaves the engine, the worker becomes a client with its own credentials, data exposure and failure modes. The cards below cover the controls your team must design before the first external worker runs, and the signals that show whether those controls are working.
Authenticate workers to the engine
Every external worker that acquires jobs or completes tasks through the engine API needs its own identity, not a shared administrator account. Decide how workers obtain credentials, how those credentials rotate, and which engine users or groups each worker may act as. Confirm with your Flowable version how the REST API authenticates clients and whether acquisition calls can be restricted by topic. Test a revoked credential to confirm the worker stops acquiring jobs.
Limit variables sent to workers
Process variables passed to a worker are copied out of the engine and may land in worker logs, queues and traces. Map each external task to the minimum set of inputs it needs, and keep personal data, account numbers and credentials out of variables that the worker does not require. Decide which outputs the worker may write back and validate them before the engine continues. Review variable mappings whenever a process definition changes.
Design idempotent side effects
A worker can crash after calling a payment, email or ticketing system but before reporting completion, so the engine may hand the same job to another worker. Give each side-effecting call a stable business key or idempotency key derived from the process instance and activity, and check for prior execution before acting. Agree retry limits with the owners of the downstream systems. Test the crash-before-completion path explicitly.
Monitor worker backlogs
Work that waits for a worker can pile up silently while the engine reports healthy processes. Track the number of open external tasks per topic, the age of the oldest unlocked task and the rate of lock expirations. Set alert thresholds with the business owners of each process, since the right limit depends on the service level of the work. Confirm which Flowable endpoints and database queries your monitoring can read without giving the monitor write access.
Log every job acquisition
When a job moves between workers, you need a record of which worker acquired it, when, and for how long the lock was held. Without that record, a duplicate side effect or a stalled task is hard to attribute. Log the worker identity, topic, process instance ID, activity ID and lock expiry on acquisition and completion, and make sure those logs carry no variable values. Keep the retention period aligned with your audit requirements.
Protect the engine from worker failures
A stuck or misbehaving worker can hold locks, flood the API with retries or exhaust database connections that the engine also needs. Set per-worker rate and concurrency limits, timeouts for calls to downstream systems, and a documented way to release or reassign locked work. Test what happens when a worker pool is paused for an extended period and confirm the engine keeps serving its own threads.
Questions teams ask before scaling Flowable with external workers
What drives the cost of scaling Flowable with external workers?
The cost comes from four parts: working out which steps move out of the engine, building and testing the worker code, deploying and operating the worker pools, and the database and infrastructure they add. Effort rises with the number of topics, the number of downstream systems each worker calls, and how much idempotency, retry and timeout handling each one needs. One-time costs include design, build and cutover. Recurring costs include compute for worker pools, monitoring, on-call and updates when Flowable changes. Ask for a quote that splits one-time build from ongoing support, lists every topic and integration in scope, and states who owns on-call. Most engagements are fixed-scope, with scope, deliverables and timelines agreed in writing before work starts. DeployOpen does not publish a price list, so the quote follows from that written scope.
How long does a worker rollout take with many topics?
Duration depends mainly on the number of topics, the number of integrations behind them, and how many of those integrations are new to your team. A phased plan works best: one pilot topic end to end first, then a group of topics that share a pool, then the rest. Each topic needs a clear contract for variables in, variables out and error outcomes, a worker, tests against a running engine, and a rollout step. Integrations with unclear APIs or no test environment add the most time. No fixed duration applies to every stack, so ask for a phased schedule in the quote with a milestone per topic group and a list of dependencies on your application, network and security teams.
What must our team provide before the engagement starts?
Your team needs to provide the following before work begins:
- A non-production Flowable environment and a database from the supported list, such as PostgreSQL, MySQL, Oracle, DB2, Microsoft SQL Server or H2 for local tests
- The process and case models, with the service tasks you want to externalize and the business owner for each
- Named owners and API documentation for every downstream system the workers will call
- A secret store or approved method for passing credentials to workers
- Monitoring, log access and on-call contacts for the engine and each worker pool
- Test data that reflects real variable shapes, including error cases
Missing items usually show up as delays in the first sprint, so confirm access and owners before the schedule is signed.
Can our in-house team build external workers without a partner?
Yes, if your team has the language skills for the workers, a Flowable engine it can test, and capacity to own the code after go-live. The polling loop is the easy part. The hard parts are idempotency, retries, timeouts and lock handling when a node fails mid-job. A team that already runs Flowable's async executor and understands its lock table will move faster than one learning both at once. A partner makes more sense when deadlines are fixed, when the team lacks Flowable experience, or when several integrations must be delivered in parallel. DeployOpen plans, implements, documents and hands over this work inside your infrastructure, so you can compare a fixed-scope quote against the cost of your own team's time.
How does lock duration affect recovery when a worker or node fails?
Lock duration sets how long a job stays claimed before another node can take it. The Flowable advanced documentation describes lock owners and lock expiration times stored in the database, so expired locks are recovered and jobs are picked up by other nodes. A short lock recovers quickly after a crash but risks a slow, healthy worker losing its claim, which can cause duplicate execution. A long lock is safer for slow calls but delays recovery. Set the lock above the longest legitimate runtime of the work, make worker-side actions idempotent, and test recovery by stopping a node mid-job. Confirm the exact property names and defaults for your Flowable version in the advanced documentation before changing them.
Will external workers increase load on the database?
Yes. Each job acquisition, lock update, completion and retry writes to the job tables, so polling frequency and the number of worker pools multiply database traffic. Moving heavy work out of the engine frees engine threads, but the database still records every state change for those jobs. Size polling intervals, pool counts and connection pools against the capacity of your database server, and check that the database type you run is on Flowable's supported list. Ask for a load test in the quote that measures lock acquisition rate, job table growth and connection pool use at your expected peak, with results reviewed before production cutover.
Who owns the worker code and the Flowable engine after go-live?
Ownership should be set in the written scope. Typically the worker code, deployment pipeline and runbooks transfer to your team at handover, while Flowable engine upgrades are handled as a separate workstream. Open-source Flowable is licensed under Apache 2.0, so the engine itself carries no license fee, but commercial Flowable products are licensed separately and are not required for this work. Ask for source code handover, runbooks for restarting pools and clearing failed jobs, and a clear statement of what post-go-live support covers. Ongoing support, upgrades and continuous testing are available as retainers if you prefer not to carry them in-house.
What to ask a partner before scoping a worker-based scaling project
Start by asking which service tasks will move out of the engine, and why each one is a candidate. A proposal should name each flowable:type or delegate expression it covers, the external worker that will handle it, and the process definitions that call it. Ask the partner to show how the work list was produced, for example from job execution history, slow service task timings or the thread dumps taken during a load test. Ask what stays in the engine. Short, transactional steps that read and write process variables are often better left where they are, and a partner who moves everything without that analysis is selling a rewrite rather than a scaling plan. Also ask how the proposal handles tasks that call systems with their own rate limits, since those decide the worker pool size as much as the engine does.
Ask for the written deliverables by name and check each one against the scope. A credible fixed-scope proposal lists the worker code and its tests, the topic and lock settings with their rationale, the deployment manifests for each worker pool, the load test plan and results, and the runbook for scaling and failure recovery. Ask how load testing will be verified: which job volume and arrival pattern the test models, which database and engine metrics are captured, and what the acceptance criteria are before go-live. Confirm that the test runs against a production-like database, because lock contention and connection pool limits do not show up on small instances. Ask who owns the test environment and the data used in it, and whether the partner will hand over the scripts so your team can rerun them.
Ask what recurs after go-live. The worker design adds running components, each with its own scaling rules, logs, credentials and health checks, so ask who monitors the worker pools, who responds to stuck or retried jobs, and who tunes lock durations when a downstream system slows down. Ask whether support is provided under a separate retainer and what it covers. Then ask how upgrades are handled. Flowable's major releases change the Java, Spring and Jakarta baseline, so the external workers and the engine may need different upgrade windows. Ask the partner to confirm which Flowable and Spring versions the worker library targets, how the workers are versioned alongside process definitions, and how a rolling upgrade keeps old and new workers compatible during the switch. Confirm these points in writing with the partner, and check the current release baseline in the Flowable 8.0.0 release notes before accepting any upgrade assumption.
How DeployOpen scopes and delivers external worker scaling for Flowable
DeployOpen plans, implements, tests, documents and supports self-hosted Flowable engines inside your own infrastructure. For external worker scaling, we start from your job volume, the slow or heavy work you want to move out of the engine, and the database and network limits you already operate under, then agree the scope in writing before any build starts.
How we scope
- We map which service tasks and delegates hold engine threads today and which should move to external workers.
- The written scope names the worker topics, pool sizes, lock duration targets and the database and monitoring changes included.
- Engagements are fixed-scope, with deliverables and timelines agreed in writing before work starts, and a quote follows from that scope.
How we build
- We configure topics, job acquisition and lock durations, then build worker services in the languages your teams use.
- We test worker pools under realistic load in a staging environment that mirrors your database and network setup.
- We check engine thread use, lock expiry behavior and database load before and after each change we make.
How we hand over
- You receive the worker code, configuration, sizing notes and runbook covering scaling, restarts and failed work.
- Documentation records the settings chosen, the test results and the conditions under which each setting should be revisited.
- Your team runs a handover session so that operators can scale worker pools and read the monitoring signals without us.
How we support
- Ongoing support, upgrades and continuous testing are available as a retainer, covering changes to job volume or worker code.
- We can review worker scaling again when Flowable releases change engine or Spring baselines that affect your deployment.
- Ask us to confirm which support hours, response commitments and upgrade coverage apply before you sign a retainer.

