go-bpm

Насколько быстрый 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 (см. оговорки).

Без истории#

  • Apache Camel4 154
  • Spring Integration3 910
  • bpm-light, без экспорта2 235
  • Camunda, без истории (H2 в памяти)1 577
  • go-bpm, методы, без истории1 361
  • go-bpm, service task, без истории466
  • Zeebe, без экспорта117

С историей или экспортом#

  • bpm-light, экспорт в H2 (файл)1 960
  • go-bpm, методы, история, async commit1 228
  • go-bpm, методы, история1 153
  • bpm-light, экспорт в Elasticsearch1 102
  • bpm-light, экспорт в ClickHouse983
  • go-bpm, service task, история, async commit752
  • go-bpm, service task, история419
  • Camunda, история в PostgreSQL293
  • Zeebe, экспорт в Elasticsearch112

Camel и Spring Integration не BPM-движки: они не хранят состояние процесса, и упавший экземпляр не продолжится после перезапуска. Их строки — ориентир «быстрее на этом железе не бывает».

По отметкам#

Как в статье: сколько экземпляров в секунду в среднем от старта к моменту, когда завершилось 1 тыс., 10 тыс. … 100 тыс. экземпляров. У go-bpm — медианный прогон.

Система1 тыс.10 тыс.20 тыс.30 тыс.40 тыс.50 тыс.60 тыс.70 тыс.80 тыс.90 тыс.100 тыс.
Без истории
Apache Camel2 2174 0294 0874 1544 1354 1384 1284 1124 1674 1304 154
Spring Integration1 9963 7573 8593 8913 8983 8993 8883 9003 9143 9223 910
bpm-light, без экспорта1 2442 1992 2222 2432 2422 2382 2362 2362 2322 2372 235
Camunda, без истории (H2 в памяти)9711 5651 5691 5791 5841 5821 5911 5871 5931 5821 577
go-bpm, методы, без истории2 5082 4222 4092 2982 2061 8981 6411 6001 6831 6101 361
go-bpm, service task, без истории173549568652496408435446469471466
Zeebe, без экспорта100112117118119119118118119117117
С историей или экспортом
bpm-light, экспорт в H2 (файл)1 1051 9431 9701 9761 9781 9601 9671 9571 9721 9631 960
go-bpm, методы, история, async commit1 3195974716407818979921 0711 1341 1861 228
go-bpm, методы, история1 4221 1195917598879731 0161 0641 1091 1391 153
bpm-light, экспорт в Elasticsearch3079001 1071 1051 1031 0991 0701 0891 1051 0301 102
bpm-light, экспорт в ClickHouse7009991 0129911 0039811 0021 0039991 004983
go-bpm, service task, история, async commit575191305371406454513572637696752
go-bpm, service task, история761550412347398435448438450455419
Camunda, история в PostgreSQL279346302331306319296309303296293
Zeebe, экспорт в Elasticsearch88106112114113115113113113113112

Как мерили#

Процесс — старт, два 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), результаты выше — с исправлением.

Предложить правку на GitHub