Tuning the Flowable async executor

Size the Flowable async executor for your job volume by setting thread pools, queues and acquisition behavior against the database load they create. Confirm each setting under load before production.

On this page

Tuning the Flowable async executor for your job volume

When timers and async continuations pile up or run late on a Flowable engine, the async executor is the first place to look, but it is rarely the only one. The async executor runs timers and asynchronous jobs from a thread pool. In a clustered deployment it coordinates nodes through lock owners and lock expiration times stored in the database, so a job locked by a node that stops is recovered once its lock expires and another node picks it up, as described in the Flowable advanced documentation. Tuning is the right next step when the queue of due jobs keeps growing while the database and application threads have headroom, and when the number of jobs your process models create per hour is known and stable. Before changing any pool or queue setting, confirm that the jobs themselves are sized correctly. Check job design first when a single process creates many small async steps, when a loop or parallel branch fans out into hundreds of jobs per instance, or when a long delegate call holds a thread for seconds at a time.

Start by measuring rather than guessing. Compare the number of jobs created with the number completed per interval, and separate timer jobs from async continuations, since they fire on different schedules and fail differently. Record the database wait time for job acquisition and lock updates, because each acquisition cycle reads the job tables and writes lock fields. Raising the thread pool or acquisition batch size increases that load, so a larger pool can slow the database and make lock contention worse. Confirm the pool, queue and acquisition settings with your Flowable version's configuration reference before you change them, because property names and defaults differ between releases and between the Spring Boot starter and the plain engine configuration. Make one change at a time and rerun the same load profile so the effect is attributable.

Plan for the case where the queue fills. When no thread is free and the queue is full, jobs stay in the database and are acquired later, so backlog shows up as delay rather than as errors, which makes it easy to miss. Decide in advance who watches the backlog, what delay is acceptable for timers that carry business deadlines, and how a node failure affects in-flight jobs, which depends on how long lock expiration is set. Ask your team to test a node restart under load and confirm that jobs resume within the window you accept. If the workload is heavy or slow, moving that work to external workers may remove the pressure from engine threads entirely, which is covered in the scaling guidance for external workers.

Async executor settings and when each fits

This table lists the async executor settings that the Flowable configuration and advanced docs describe, with what each one controls and when it is worth changing. Setting names here are descriptive, so confirm exact property names and defaults against the docs for your Flowable version before editing any configuration. The main takeaway: settings that add throughput also add database load or lock contention, so change one setting at a time and measure the result.

SettingWhat it controlsWhen to change it
Thread pool sizeThreads on each node that run due timers and async jobsRaise when jobs wait while database and CPU have headroom; lower when threads contend for database connections
Queue capacityAcquired jobs waiting in memory for a free threadSize for short bursts; if the queue stays full, check monitoring and confirm in the docs what happens to rejected jobs
Acquisition batch sizeHow many jobs a node loads from the job tables in one queryIncrease to cut query count; watch for more locks held at once and more contention between nodes
Lock durationHow long a node holds a job before another node may take it overSet longer than your slowest job; too short risks duplicate execution, too long delays recovery after a node failure
Acquisition wait timeHow long the acquisition loop pauses when it finds no jobsShorten for faster timer and async pickup; lengthen to reduce empty polling queries on the database
Timer handlingHow due timers in the timer job table are picked up and runReview when timers fire late or in bursts at scheduled times, since timers share the same thread pool
Node count and connection poolEach node runs its own executor against shared job tablesAdd nodes only after checking that the database connection pool and polling load can absorb them

How to size and verify the executor before production

This sequence turns a guess about Flowable job load into tested pool, queue and database settings, along with a documented baseline and a way back if production behaves differently than the test predicted.

  1. Measure job volume and due-time spikes

    Start by counting what the executor will actually have to handle. Pull the number of timers and asynchronous jobs created per hour from your process models, and identify the due-time spikes: month-end batch timers, SLA reminders that all fire at the same minute, or retry storms after a downstream outage. Ask the process owners which timers share a fire time and how many instances they cover. The output is a table of peak jobs per minute by job type, with the source of each estimate written next to it. You know this step is done when the peak figure comes from measured history or a documented model, not from a single developer's estimate.

  2. Check the job tables and indexes

    Review the timer and async job tables in your database, along with their indexes, before changing any executor setting. Confirm that the lock owner and lock expiration columns and the due-date column are indexed in the way the Flowable documentation for your version describes, and check table size and row churn after a test run. Compare the table design against your database vendor's guidance for the query plans the executor uses. The check is complete when you can explain, with query plans or statistics from your environment, why acquisition queries stay fast as the job table grows. If the plans show full scans, fix the indexing before tuning threads.

  3. Set initial pool and queue values

    Choose a starting thread pool size, queue capacity and acquisition batch size for each node, using your measured peak and the number of nodes that will share the job tables. Keep the starting values conservative so the database is not flooded on the first run. Record which settings are exposed through your Flowable configuration or Spring Boot properties and which require a custom executor bean. Write down the expected connection use per node, since every executor thread that works a job also holds database work. The output is a configuration file under version control with the values and the reasoning behind each one. The step is done when a colleague can rebuild the same configuration from the repository alone.

  4. Load test with realistic timers and async continuations

    Run a load test that reproduces your real mix of timers, asynchronous continuations, retries and failure paths, not a synthetic stream of identical jobs. Use production-like data volumes, the same number of nodes and the same database type and version. Include the due-time spike from step one and a scenario where a downstream service is slow so that jobs back up in the queue. Capture jobs waiting, jobs executed per second, failed jobs and the time between due time and execution. The check is complete when the test shows whether the queue drains after the spike and whether late jobs stay within the delay your process owners accepted.

  5. Watch database connections and lock contention

    During the load test, monitor the database side of the executor: open connections per node, connection pool wait times, lock waits on the job tables and deadlocks or optimistic locking errors reported by the engine. Compare connection use with the pool sizes configured in your application server or Spring Boot datasource, because executor threads compete with API traffic for the same connections. Look for nodes that repeatedly acquire and release the same jobs, which points to lock expiration times that are too short for the work. The step is done when you have a baseline for connections and lock waits at peak and you know which setting caused the largest share of contention.

  6. Document the chosen values and the rollback plan

    Write down the final values for pool size, queue capacity, acquisition settings, lock durations and database connection limits, together with the load test results that justified them. Record the metrics you will watch in production, the thresholds that trigger a change, and who is authorized to make it. Define the rollback: the previous configuration, how it is redeployed, and what to check after the revert, such as queue depth returning to baseline and no new lock timeouts. Ask your partner or internal team to confirm the document is reviewed alongside the database and infrastructure runbooks. The step is complete when the document is approved and the rollback has been rehearsed in a non-production environment.

What happens when the executor queue fills

When the async executor cannot keep up with incoming jobs, the work does not disappear. It stays in the database, waits for a thread, and can be picked up late or twice. The cards below describe each failure mode the Flowable docs point to and how to spot it before users do.

Queue saturation

The executor holds a bounded in-memory queue of jobs it has acquired and is ready to run. When every worker thread is busy and the queue is full, new jobs are not dropped silently. They remain in the job tables until the executor has room. Check the executor's configured queue size and thread count against your peak job rate, and watch whether the queue stays at its limit during business hours. Confirm the exact rejection and back-off behavior for your Flowable version in the docs.

Jobs waiting in the database

Jobs that are due but not yet executed sit in the async job table with a due date and no lock owner. A growing backlog of due jobs means the executor is slower than the arrival rate, and every acquisition cycle adds read load to the database. Query the job table for due, unlocked rows and track the count over time. A backlog that drains overnight is usually a sizing issue. A backlog that keeps growing points to slow delegates or a database bottleneck.

Lock expiry and re-acquisition

Each job is locked by the node that acquired it, with a lock owner and a lock expiration time stored in the database. If a job runs longer than its lock duration, the lock expires and another node can acquire the same job, which can produce duplicate execution of side effects. Compare your longest-running job against the configured lock time. Watch for jobs that show repeated lock acquisition in the logs, and make delegates that call external systems idempotent.

Retries and failed jobs

When a job throws an exception, the engine records the failure and schedules a retry according to the retry settings for that job. Jobs that exhaust their retries stop running until someone intervenes, so a full queue can hide a growing set of failed jobs behind normal-looking throughput. Check the count of failed jobs and their retry counts after every tuning change. Confirm the default retry count and the management APIs for reviewing and resetting failed jobs in your version.

Lag in timer firing

Timer events and boundary timers are stored as timer jobs and fire only when the executor acquires them. When the queue is saturated, a timer due at 09:00 may fire at 09:07 or later, which matters for deadlines, escalations and SLA clocks. Compare each timer's due date with its actual execution time in the job history. If the gap grows under load, add executor capacity or move slow work out of the engine before changing timer logic.

The database load each executor setting creates

The async executor is a database workload before it is a thread pool, so every sizing decision shows up as load on the tables that store jobs and locks. Larger batches, more threads, shorter wait intervals and shorter lock durations each raise a different part of that load. Before changing any of them, decide which kind of pressure matters most for your system: frequent polling when the queue is mostly empty, row updates and lock contention when many nodes compete for the same jobs, or connection use when job execution holds connections for long periods.

Polling load grows as the executor checks for due timers and jobs more often, and it is paid even when no work exists. Each acquisition writes lock owner and lock expiry values to the job rows it claims, so a larger batch means more rows updated in one transaction, and more nodes competing for the same rows means more optimistic conflicts and retries. Shorter lock durations make expired locks recover faster, but they also mean a slow job can be handed to another node while the first node is still running it. Confirm the lock and wait settings in the Flowable advanced documentation against your release before you tune them.

The databases Flowable supports do not pay these costs in the same way. Lock behavior, row-level locking, index use on the job tables and how connections are pooled differ between PostgreSQL, MySQL, Oracle and Microsoft SQL Server, so a setting that is quiet on one can be noisy on another. Measure rather than assume: capture statements per second against the job tables, lock wait time, deadlocks, connection pool wait and transaction duration for each setting you test, on a staging copy with realistic job volume. Ask your database team which execution plans and index changes they expect to be needed for the job tables, and record the results next to each executor configuration.

Operational and security considerations for executor changes

Async executor settings change how the engine behaves on every node and in the shared database, so each change needs monitoring, failure visibility, capacity review and a controlled rollout. The cards below cover the checks that connect a tuning change to what happens in production.

Monitor through the actuator endpoint

The Spring Boot starter exposes an actuator endpoint at /actuator/flowable for monitoring running processes. Before and after any executor change, record the job backlog, the number of running processes and the trend in pending timers. A thread pool that looks healthy in the database can still hide a growing backlog, so compare the endpoint output with your own queue metrics. Confirm with your team which endpoints are exposed to which network zones, since this data describes your business processes.

Log and triage failed jobs

A job that fails repeatedly stays in the job tables with its retry state, and the engine does not alert anyone by default. Decide how failed jobs are surfaced to operators, which log level captures executor exceptions, and who owns the retry or dead-job decision. Tune retry counts and wait intervals alongside pool size, because a larger pool that retries aggressively against an unavailable downstream service adds load without finishing work.

Plan capacity for shared databases

The async executor polls and locks rows in the job tables, and the same database often serves other applications. Each increase in thread count or acquisition frequency adds queries and row locks that other workloads share. Ask your database team for connection limits, lock wait alerts and headroom during peak hours. Test a change against a copy of production job volume, and include timer bursts such as month-end batches in the test.

Control changes to production settings

Executor settings are configuration, so treat them as code. Keep the values in version control, review each change with the engineers who own the process definitions, and record which environment ran which values. Define a rollback value for each setting before the change window opens. Changes that affect only one node should not be deployed to the whole cluster without a comparison period.

Restrict access to job data

Job records carry process instance identifiers, variables and exception messages, which can contain personal or financial data. Limit database and API access to job tables and any tool that lists or retries jobs, and check whether exception text is written to logs that broader teams can read. Confirm which roles can trigger job execution or retries through the engine APIs, since those actions change business outcomes.

Protect consistency during rolling restarts

The executor coordinates nodes through lock owners and lock expiration times stored in the database. During a rolling restart, a stopping node may hold jobs that other nodes recover only after their locks expire. Set lock durations and the node shutdown timeout together, and verify that a restarted node with changed settings does not run a different thread pool configuration from its peers for long. Test a restart under load before adopting the change.

Questions teams ask before tuning the async executor

Will tuning the async executor alone fix slow jobs?

Not always. Tuning changes how many jobs the executor can run at once and how it acquires them from the database, so it helps when the bottleneck is executor capacity or acquisition. If jobs are slow because a delegate calls a slow external service, holds a long transaction, or waits on a lock, adjusting thread pool or queue settings only moves the queue. Before changing settings, confirm where time is spent by checking job durations, failed job counts and database wait events. The Flowable advanced documentation describes how the executor coordinates nodes through lock owners and lock expiration times, which is a useful reference when reading those numbers.

How long does tuning take, and what drives the effort?

The effort depends on how many nodes run the engine, how varied the job mix is, and whether load tests exist. The main drivers are the number of distinct job types (timers, async continuations, external work), how many process definitions share the same database, the availability of a representative test environment, and how many settings must be changed and retested together. Tuning is iterative: each change needs a load run, a look at the database, and a decision. Ask any provider for a fixed-scope quote that lists the load scenarios, the environments used, the number of tuning cycles and the deliverable, such as a written settings baseline.

What does the work need from our internal team?

Tuning needs people who can answer questions about the business process and the infrastructure. Expect to provide access to a staging environment that matches production database type and version, a sample of realistic job volumes, the process definitions that generate async jobs, and monitoring data from the database and application nodes. Someone must own the decision to change production settings and the rollback plan. Engineers who manage the database should be available to review connection counts, lock behavior and index use, because each executor setting adds load there.

Can our own team tune the async executor without a partner?

Yes, if the team has Flowable experience, access to a representative load environment and time to measure before and after each change. The engine exposes its settings through configuration, and the Flowable configuration documentation covers the supported database types, so an internal team can start from the documented options. A partner is most useful when the team lacks load testing capacity, when several platforms compete for the same database, or when the executor must be proven before a production release. Whichever route you choose, record the baseline and every change so later decisions have evidence.

How do we get a fixed-scope quote for this work?

Ask for a written scope before any work starts. DeployOpen works on fixed-scope engagements where scope, deliverables and timelines are agreed in writing, and quotes follow from that scope rather than from a published price list. A useful request names the Flowable version, the database type and version, the number of engine nodes, the job types in use, the target throughput or backlog limits, the test environment available, and the deliverables you expect, such as a tuned settings file, load test reports and an operations runbook. Ask the provider to state which items are one-time work and which would recur, such as retesting after upgrades.

When should we revisit executor settings after a release upgrade?

Revisit them whenever a Flowable upgrade changes the runtime baseline, the database schema, or the libraries the engine depends on, and when job volume or process design changes. Major releases can change the surrounding stack. For example, the Flowable 8.0.0 release notes describe an upgrade to Spring Framework 7 and Spring Boot 4 with no backward compatibility for Spring Boot 3. Read the release notes for each version you adopt, then rerun load tests against the settings in use. Also revisit after adding nodes, changing the database, or launching a process that creates many timers.

What should we check on the database before and after changing settings?

Check the job tables, lock columns and connection pool usage before and after each change. More executor threads mean more concurrent acquisition and update statements against the timer and async job tables, so connection pool size and database capacity can become the limit. Watch lock wait times, the number of expired locks being recovered, and growth in the job tables. If the database is shared with other applications, measure their impact too. Keep changes small, so you can tell which setting caused a shift in database load.

How DeployOpen scopes and tunes Flowable executors

DeployOpen approaches Tuning the Flowable async executor as a scoped engineering task inside your own infrastructure. We start from your job volume, database and cluster layout, then size the executor settings, test them against realistic load and hand over the configuration with the reasoning behind it.

How we scope

  • Written scope names the Flowable engines, job types and clustered nodes covered by the tuning work.
  • We collect job volume, timer patterns, database platform and current thread pool settings before estimating any change.
  • Scope states which load tests, dashboards and runbooks are deliverables, so you can check them at handover.

How we build

  • We adjust async executor thread pool, queue and acquisition settings in your own test environment first.
  • We review timer and async job table growth and the lock owner and lock expiration behavior across nodes.
  • We run load tests that exercise a full queue, record database load per setting and document the results.

How we hand over

  • You receive the final configuration, the test records and a written note on the trade-offs behind each value.
  • Runbook covers what to watch when the job queue fills and which settings to change first.
  • We walk your platform and operations team through the configuration so they can explain it to others.

How we support

  • Fixed-scope engagements end at agreed deliverables, with any follow-up work quoted separately from written scope.
  • Ongoing support, upgrades and continuous testing are available as a retainer if job volume or Flowable versions change.
  • Confirm with us during scoping which support model fits your team, your change process and your on-call arrangements.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.