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.ymlto/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
| Key | Default | Notes |
|---|---|---|
hertzbeat.vthreads.enabled | true | Global switch for the HertzBeat virtual-thread executors |
hertzbeat.vthreads.common.mode | UNBOUNDED_VT | Common short-running tasks |
hertzbeat.vthreads.collector.mode | LIMIT_AND_REJECT | Keeps collector fast-fail admission |
hertzbeat.vthreads.collector.max-concurrent-jobs | 512 | Balanced default for mixed HTTP and JDBC collection workloads on a single node |
hertzbeat.vthreads.manager.mode | LIMIT_AND_REJECT | Keeps manager admission behavior |
hertzbeat.vthreads.manager.max-concurrent-jobs | 10 | Same as the legacy limit |
hertzbeat.vthreads.alerter.notify.mode | LIMIT_AND_REJECT | Notification executor admission |
hertzbeat.vthreads.alerter.notify.max-concurrent-jobs | 64 | Global notify concurrency |
hertzbeat.vthreads.alerter.notify-max-concurrent-per-channel | 4 | Per notification channel/type |
hertzbeat.vthreads.alerter.periodic-max-concurrent-jobs | 10 | Global periodic alert concurrency |
hertzbeat.vthreads.alerter.log-worker.max-concurrent-jobs | 10 | Log alert short-task concurrency |
hertzbeat.vthreads.alerter.log-worker.queue-capacity | 1000 | Bounded queue to preserve backlog semantics |
hertzbeat.vthreads.alerter.reduce.max-concurrent-jobs | 2 | Alarm reduce concurrency |
hertzbeat.vthreads.alerter.reduce.queue-capacity | unbounded | Leave unset to keep the legacy unbounded queue behavior |
hertzbeat.vthreads.alerter.window-evaluator.max-concurrent-jobs | 2 | Window evaluator concurrency |
hertzbeat.vthreads.alerter.window-evaluator.queue-capacity | unbounded | Leave unset to keep the legacy unbounded queue behavior |
hertzbeat.vthreads.warehouse.mode | UNBOUNDED_VT | Storage short tasks; downstream pools still limit real resources |
hertzbeat.vthreads.async.enabled | true | Dedicated @Async executor switch |
hertzbeat.vthreads.async.concurrency-limit | 256 | @Async concurrency guard |
hertzbeat.vthreads.async.reject-when-limit-reached | true | Reject extra @Async tasks at the limit |
hertzbeat.vthreads.async.task-termination-timeout | 5000 | Milliseconds |
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.
512is the default because it is a good mixed-workload starting point. In local verification, HTTP-heavy collection continued scaling beyond512, while JDBC-style collection peaked around512and 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
768first and then1024if timeouts, error rates, and connection usage remain stable. - If most of your workload is JDBC or other database-backed collection, keep
collector.max-concurrent-jobsaround256to512. In this type of workload, raising concurrency above512can reduce total throughput instead of improving it. - If you are not sure about the workload mix, keep
512as the starting point. It is a safer default than768+for mixed environments. - Lower
collector.max-concurrent-jobswhen the collector talks to a small database, a low-capacity HTTP endpoint, or fragile network devices. - Raise
alerter.notify.max-concurrent-jobsornotify-max-concurrent-per-channelonly if your notification providers and HTTP connection pools can absorb the increase. - Keep
warehouse.modeunbounded unless you have a clear bottleneck model. Database and TSDB client pools should remain the main limiters. reduce.queue-capacityandwindow-evaluator.queue-capacityare intentionally left unset by default so existing queueing semantics remain compatible.- Change concurrency in steps and observe timeout rate, downstream
429or5xx, 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.ymlunchanged. - Add the
hertzbeat.vthreadsblock only when you want to tune concurrency limits or explicitly disable the feature.