Насколько быстрый go-bpm
В 2021 году в статье «Zeebe и Camunda: сравниваем известные BPM-системы под высокими нагрузками» мы сравнивали Camunda, Zeebe, Apache Camel, Spring Integration и самописный движок bpm-light на простом процессе. Здесь тот же тест повторён для go-bpm, на той же машине: Intel Core i7-4770K, 4 ядра, 24 ГБ памяти, SSD.
Коротко#
- Процесс с историей в PostgreSQL — 1 153 экземпляров в секунду, если задачи — методы, и 419, если это service task через очередь. Camunda с историей в PostgreSQL в статье давала 293, Zeebe с экспортом в Elasticsearch — 112.
- Без истории go-bpm с методами — 1 361 экз./с. Это уровень Camunda без истории с базой H2 в памяти (1577), только у go-bpm каждый шаг закоммичен в PostgreSQL и переживает падение сервиса.
- История стоит 10–15%. Она пишется в той же транзакции, что и переход токена, поэтому добавляет строки, но не коммиты. Camunda в статье с историей в PostgreSQL была в пять раз медленнее, чем без истории на H2 в памяти.
- Потолок — PostgreSQL и стенд, а не движок: каждый шаг — транзакция с записью на диск.
Если допустимо потерять последние доли секунды при падении сервера базы,
synchronous_commit=offподнимает service task до 752 экз./с.
Результаты#
Экземпляров в секунду на 100 тыс. запущенных: число завершённых, делённое на время от первого старта, как
в статье. Столбцы go-bpm — медиана трёх прогонов, разброс — в подсказке. Остальные значения — из таблиц статьи.
История go-bpm пишется в PostgreSQL, async commit — прогон с synchronous_commit=off (см. оговорки).
Без истории#
С историей или экспортом#
Camel и Spring Integration не BPM-движки: они не хранят состояние процесса, и упавший экземпляр не продолжится после перезапуска. Их строки — ориентир «быстрее на этом железе не бывает».
По отметкам#
Как в статье: сколько экземпляров в секунду в среднем от старта к моменту, когда завершилось 1 тыс., 10 тыс. … 100 тыс. экземпляров. У go-bpm — медианный прогон.
| Система | 1 тыс. | 10 тыс. | 20 тыс. | 30 тыс. | 40 тыс. | 50 тыс. | 60 тыс. | 70 тыс. | 80 тыс. | 90 тыс. | 100 тыс. |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Без истории | |||||||||||
| Apache Camel | 2 217 | 4 029 | 4 087 | 4 154 | 4 135 | 4 138 | 4 128 | 4 112 | 4 167 | 4 130 | 4 154 |
| Spring Integration | 1 996 | 3 757 | 3 859 | 3 891 | 3 898 | 3 899 | 3 888 | 3 900 | 3 914 | 3 922 | 3 910 |
| bpm-light, без экспорта | 1 244 | 2 199 | 2 222 | 2 243 | 2 242 | 2 238 | 2 236 | 2 236 | 2 232 | 2 237 | 2 235 |
| Camunda, без истории (H2 в памяти) | 971 | 1 565 | 1 569 | 1 579 | 1 584 | 1 582 | 1 591 | 1 587 | 1 593 | 1 582 | 1 577 |
| go-bpm, методы, без истории | 2 508 | 2 422 | 2 409 | 2 298 | 2 206 | 1 898 | 1 641 | 1 600 | 1 683 | 1 610 | 1 361 |
| go-bpm, service task, без истории | 173 | 549 | 568 | 652 | 496 | 408 | 435 | 446 | 469 | 471 | 466 |
| Zeebe, без экспорта | 100 | 112 | 117 | 118 | 119 | 119 | 118 | 118 | 119 | 117 | 117 |
| С историей или экспортом | |||||||||||
| bpm-light, экспорт в H2 (файл) | 1 105 | 1 943 | 1 970 | 1 976 | 1 978 | 1 960 | 1 967 | 1 957 | 1 972 | 1 963 | 1 960 |
| go-bpm, методы, история, async commit | 1 319 | 597 | 471 | 640 | 781 | 897 | 992 | 1 071 | 1 134 | 1 186 | 1 228 |
| go-bpm, методы, история | 1 422 | 1 119 | 591 | 759 | 887 | 973 | 1 016 | 1 064 | 1 109 | 1 139 | 1 153 |
| bpm-light, экспорт в Elasticsearch | 307 | 900 | 1 107 | 1 105 | 1 103 | 1 099 | 1 070 | 1 089 | 1 105 | 1 030 | 1 102 |
| bpm-light, экспорт в ClickHouse | 700 | 999 | 1 012 | 991 | 1 003 | 981 | 1 002 | 1 003 | 999 | 1 004 | 983 |
| go-bpm, service task, история, async commit | 575 | 191 | 305 | 371 | 406 | 454 | 513 | 572 | 637 | 696 | 752 |
| go-bpm, service task, история | 761 | 550 | 412 | 347 | 398 | 435 | 448 | 438 | 450 | 455 | 419 |
| Camunda, история в PostgreSQL | 279 | 346 | 302 | 331 | 306 | 319 | 296 | 309 | 303 | 296 | 293 |
| Zeebe, экспорт в Elasticsearch | 88 | 106 | 112 | 114 | 113 | 115 | 113 | 113 | 113 | 113 | 112 |
Как мерили#
Процесс — старт, два REST-вызова, конец. Каждый вызов — GET статического {} со случайным параметром ?v=
против кэширования. В статье ответ отдавал nginx, здесь — HTTP-заглушка в том же процессе Go.
Процесс собран в двух вариантах:
- Методы (
task): оба вызова выполняются в одном шаге системной очереди, синхронно, какJavaDelegateу Camunda. Шаг атомарен: упал второй вызов — шаг повторится целиком. - Service task: каждый вызов — отдельная задача очереди со своими повторами, как job worker у Zeebe. Взять задачу, завершить её и поставить следующую — отдельные транзакции, отсюда разница в скорости.
// bench/internal/bpmgen/bench_methods/method_rest2.go
// Rest2 - метод задачи Rest2 «Вызов REST №2»
func Rest2(ctx context.Context, svc *services.Services, data *model.DataModel) error {
if err := svc.Rest.Call(ctx); err != nil {
return err
}
svc.Finished()
return nil
}// bench/internal/bpmgen/handlers/rest_call.go
// RestCall - обработчик service task с jobType "restCall" во всех процессах, где он встречается
// (job.ProcessName, job.ElementId). Вызывается асинхронно из очереди задач и при ошибке повторяется,
// поэтому должен быть идемпотентным (ключ - job.InstanceUid + job.ElementId)
func RestCall(ctx context.Context, svc *services.Services, job bpm.Job, data *model.DataModel) error {
if err := svc.Rest.Call(ctx); err != nil {
return err
}
if job.ElementId == "Rest2" {
svc.Finished()
}
return nil
}Нагрузку даёт команда bench: 8 горутин
запускают 100 тыс. экземпляров через StartProcess так быстро, как успевают, 16 системных воркеров и 16 воркеров
service task их выполняют. Все варианты прогоняются по кругу три раза, база перед каждым прогоном очищается.
GO_BPM_BENCH_DSN='postgres://postgres:postgres@localhost:5432/go_bpm_bench?sslmode=disable' ./run.shСтенд: Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz (8 потоков), PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2), go1.26.8, 2026-10-26; до и после прогонов база выдерживала от 5 534 до 36 620 коммитов в секунду с ожиданием диска. PostgreSQL 18 в Docker с сетью хоста, shared_buffers=2GB, max_wal_size=4GB, остальное по
умолчанию; synchronous_commit=on, кроме отмеченных прогонов.
Оговорки#
- Цифры статьи — 2021 года, тогда были другие версии Java, Camunda и Zeebe. Сравнение — по порядку величин на одном железе, а не до процента.
- Разброс большой. Скорость коммитов NVMe стенда между прогонами менялась в разы (см. стенд выше), а четыре ядра делят стартеры, воркеры, заглушку и PostgreSQL. Пока стартеры ставят экземпляры, завершения идут медленнее, после — быстрее. Поэтому на графиках медиана, а в подсказке — худший и лучший прогон.
- Настройки PostgreSQL. В статье Camunda работала с PostgreSQL по умолчанию (
shared_buffers=128MB,max_wal_size=1GB), здесь — с минимальной настройкой. На настройках по умолчанию и через проброс порта Docker go-bpm на этом стенде медленнее: 1 тыс. — 1,9 тыс. экз./с у методов, 350–500 — у service task. synchronous_commit=off— не бесплатное ускорение: коммит не ждёт записи журнала на диск, и при падении сервера базы пропадут транзакции последних долей секунды. Целостность базы при этом не страдает. Включать — только осознанно, лучше для отдельных соединений движка.
Что нашёл нагрузочный тест#
Первые прогоны иногда «зависали» на 40 секунд в конце. Причина — гонка в выдаче задач очереди: под нагрузкой один запрос брал в аренду несколько задач, воркер получал одну, а остальные ждали конца аренды. Исправлено в go-bpm (коммит d7d82c6), результаты выше — с исправлением.