Skip to main content
Version: dev

Virtual Thread Configuration

HertzBeat runs on JDK 21 and uses virtual threads for the blocking execution paths that benefit from them. All hertzbeat.vthreads keys are optional. If you upgrade HertzBeat but do not merge the new YAML block into your existing application.yml, HertzBeat still starts with built-in defaults.

1. Where to Configure It

Choose the config file that matches your deployment mode:

  • Package deployment: hertzbeat/config/application.yml
  • Docker single-node deployment: mount your local application.yml to /opt/hertzbeat/config/application.yml
  • Docker Compose deployment: edit script/docker-compose/*/conf/application.yml
  • Standalone collector deployment: edit hertzbeat-collector/config/application.yml

2. No Configuration Required

You can leave out the entire hertzbeat.vthreads block.

# No virtual-thread override is required.

HertzBeat will apply runtime defaults automatically.

3. Full Optional Configuration Template

Use this only when you want to override the defaults:

hertzbeat:
vthreads:
enabled: true
common:
mode: UNBOUNDED_VT
collector:
mode: LIMIT_AND_REJECT
manager:
mode: LIMIT_AND_REJECT
max-concurrent-jobs: 10
alerter:
notify:
mode: LIMIT_AND_REJECT
max-concurrent-jobs: 64
periodic-max-concurrent-jobs: 10
log-worker:
max-concurrent-jobs: 10
queue-capacity: 1000
reduce:
max-concurrent-jobs: 2
window-evaluator:
max-concurrent-jobs: 2
notify-max-concurrent-per-channel: 4
warehouse:
mode: UNBOUNDED_VT
async:
enabled: true
concurrency-limit: 256
reject-when-limit-reached: true
task-termination-timeout: 5000

4. Built-In Defaults

KeyDefaultNotes
hertzbeat.vthreads.enabledtrueGlobal switch for the HertzBeat virtual-thread executors
hertzbeat.vthreads.common.modeUNBOUNDED_VTCommon short-running tasks
hertzbeat.vthreads.collector.modeLIMIT_AND_REJECTKeeps collector fast-fail admission
hertzbeat.vthreads.collector.max-concurrent-jobs512Balanced default for mixed HTTP and JDBC collection workloads on a single node
hertzbeat.vthreads.manager.modeLIMIT_AND_REJECTKeeps manager admission behavior
hertzbeat.vthreads.manager.max-concurrent-jobs10Same as the legacy limit
hertzbeat.vthreads.alerter.notify.modeLIMIT_AND_REJECTNotification executor admission
hertzbeat.vthreads.alerter.notify.max-concurrent-jobs64Global notify concurrency
hertzbeat.vthreads.alerter.notify-max-concurrent-per-channel4Per notification channel/type
hertzbeat.vthreads.alerter.periodic-max-concurrent-jobs10Global periodic alert concurrency
hertzbeat.vthreads.alerter.log-worker.max-concurrent-jobs10Log alert short-task concurrency
hertzbeat.vthreads.alerter.log-worker.queue-capacity1000Bounded queue to preserve backlog semantics
hertzbeat.vthreads.alerter.reduce.max-concurrent-jobs2Alarm reduce concurrency
hertzbeat.vthreads.alerter.reduce.queue-capacityunboundedLeave unset to keep the legacy unbounded queue behavior
hertzbeat.vthreads.alerter.window-evaluator.max-concurrent-jobs2Window evaluator concurrency
hertzbeat.vthreads.alerter.window-evaluator.queue-capacityunboundedLeave unset to keep the legacy unbounded queue behavior
hertzbeat.vthreads.warehouse.modeUNBOUNDED_VTStorage short tasks; downstream pools still limit real resources
hertzbeat.vthreads.async.enabledtrueDedicated @Async executor switch
hertzbeat.vthreads.async.concurrency-limit256@Async concurrency guard
hertzbeat.vthreads.async.reject-when-limit-reachedtrueReject extra @Async tasks at the limit
hertzbeat.vthreads.async.task-termination-timeout5000Milliseconds

5. Tuning Guidance

  • Start with the defaults unless you already know a downstream dependency is weak.
  • The collector default is intentionally higher than the legacy CPU-based pool size so a single HertzBeat node can carry more blocking collection work before you need extra collectors.
  • 512 is the default because it is a good mixed-workload starting point. In local verification, HTTP-heavy collection continued scaling beyond 512, while JDBC-style collection peaked around 512 and dropped when concurrency was pushed higher.
  • Virtual threads remove platform-thread pressure, but they do not remove database limits, HTTP connection limits, network bandwidth limits, file descriptor limits, or downstream rate limits. Raising concurrency too far just moves the bottleneck.
  • If most of your workload is HTTP collection across many different targets, try 768 first and then 1024 if timeouts, error rates, and connection usage remain stable.
  • If most of your workload is JDBC or other database-backed collection, keep collector.max-concurrent-jobs around 256 to 512. In this type of workload, raising concurrency above 512 can reduce total throughput instead of improving it.
  • If you are not sure about the workload mix, keep 512 as the starting point. It is a safer default than 768+ for mixed environments.
  • Lower collector.max-concurrent-jobs when the collector talks to a small database, a low-capacity HTTP endpoint, or fragile network devices.
  • Raise alerter.notify.max-concurrent-jobs or notify-max-concurrent-per-channel only if your notification providers and HTTP connection pools can absorb the increase.
  • Keep warehouse.mode unbounded unless you have a clear bottleneck model. Database and TSDB client pools should remain the main limiters.
  • reduce.queue-capacity and window-evaluator.queue-capacity are intentionally left unset by default so existing queueing semantics remain compatible.
  • Change concurrency in steps and observe timeout rate, downstream 429 or 5xx, database pool wait time, and memory or file descriptor usage before raising it again.

6. Rollback

Disable HertzBeat virtual-thread executors with:

hertzbeat:
vthreads:
enabled: false

This rolls the affected executors back to their legacy platform-thread implementations.

7. Notes

  • If your current deployment is stable, you can keep your existing application.yml unchanged.
  • Add the hertzbeat.vthreads block only when you want to tune concurrency limits or explicitly disable the feature.