Skip to content

Durable Workflow Server performance qualification #137

Description

@rmcdaniel

Objective And Priority

Determine which HTTP/PHP execution model gives Durable Workflow Server the most useful capacity per CPU and GB while preserving durability, namespace isolation, task leasing, fencing, and recovery behavior. Do not assume any proposed technology is faster.

Accepted, deferred until the current committed work is complete. This is not an immediate runtime migration or a new release gate.

Ownership And Scope

This issue owns public Server engineering: the published default image, generic HTTP/PHP configurations, application hotspots, long polling, portable SDK correctness, and reproducible backend-recovery tests useful to self-hosted users.

It does not qualify managed-service plans, provider-specific deployments, customer SLAs, pricing/margins, or Cloud production rollout. Those decisions and their private evidence are separate Cloud work; a Server benchmark pass does not authorize a managed-runtime change. Keep private infrastructure and customer information out of this issue and its artifacts.

Keep the published default Server configuration unchanged throughout experimentation. Any recommended default change needs a separately reviewed Server implementation and release, followed by independent deployment qualification by its consumers.

Baseline And Experimental Contract

  • Inspect Dockerfile, docker/apache-mpm-prefork.conf, docker/php-custom.ini, docker/entrypoint.sh, app/Support/LongPoller.php, workflow/activity/query pollers, benchmarks/capacity, and existing performance/soak scripts. Document the actual execution model before changing anything: HTTP serving, PHP worker count, Laravel bootstrap lifecycle, waiting polls, Redis wake signals, and SQL/Redis work per DW Standard Workflow v1.
  • Run the existing DW Standard Workflow v1 benchmark against the untouched published production image. Record the Server image digest and source SHA, bundled workflow package and SDK versions, benchmark-runner commit, PHP/extensions, host CPU/RAM, container CPU/memory/swap limits, OS/kernel, storage, MySQL/Redis configuration, worker counts, concurrency, payloads, warmup and measurement durations, and final backlog.
  • Record offered and completed workflows/sec separately, p50/p95/p99 end-to-end latency, errors/timeouts/retries, CPU utilization, peak RSS and total container memory, and SQL/Redis counts where measurable. Define latency boundaries and whether draining is included; do not inflate sustained throughput with an unreported drain period.
  • Freeze the workload and artifact tuple for the comparison. Keep hardware, database/cache placement and configuration, client/worker resources, network path, payloads, and measurement windows identical. Verify that load generation or SDK workers are not the bottleneck. Treat different hardware or PHP versions as separate experiments, not equivalent comparisons.
  • Use warmup and at least three measured repetitions for the baseline and finalists, reporting variation rather than a single best run. Recheck the baseline during the series to detect host drift. Preserve commands and raw outputs, including failed runs.

The initial job is to measure, not to meet an invented throughput target. A low but correct result is valid evidence. State any latency, saturation, or memory stop conditions before each experiment, and distinguish measured capacity from an advertised service guarantee.

Runtime Candidates

Build isolated Docker/config variants without replacing the production Dockerfile. Initially use the same PHP version where supported.

Candidate Qualification
Apache prefork + mod_php Untouched baseline, then separately identified tuning
nginx + PHP-FPM Same application and public protocol
Apache event MPM + PHP-FPM Same application and public protocol
Laravel Octane + FrankenPHP Persistent-worker compatibility and state reset required
Laravel Octane + Swoole or OpenSwoole Only if functional without changing public Server semantics
  • Attempt each candidate within a bounded setup effort. Record unsupported combinations and concrete failures; do not silently omit them or build a new compatibility framework to force them to work.
  • Test a small worker-count matrix appropriate to the fixed CPU/RAM envelope. Record worker count, memory per worker, total memory, CPU, and throughput/latency. Stop tuning after a reasonable stable point; reject apparent gains caused by exceeding limits, swapping uncontrollably, accumulating backlog, or growing memory without bounds.
  • Describe operational differences: process supervision, worker recycling, deployment/reload behavior, readiness, observability, additional services, and maintenance burden.

Long Polling

  • Measure workflow-task, activity-task, and query polls separately and in a realistic mixture. Identify which endpoints actually wait and their timeout semantics. Increase concurrent idle polls to a defined resource/latency limit, recording memory per waiting poll, idle CPU, task-created-to-worker-wakeup latency, disconnect behavior, and ordinary API latency while polls wait.
  • Determine whether current waiting polls occupy a full PHP process/thread, and whether a candidate's database/Redis clients or other blocking calls prevent the expected concurrency. Do not infer asynchronous behavior from the runtime name.
  • Inspect LongPoller and Redis wakeups. Where practical, compare coroutine/event waiting, FrankenPHP-compatible alternatives, blocking/event Redis notifications, or a lightweight asynchronous poll gateway using established components.
  • For every polling prototype explain and test wakeup races, timeout, cancellation/disconnect cleanup, task claiming, lease expiry, and fencing. Wake notifications must not become the authority for durable task ownership. Do not change admission limits just to increase the reported number without measuring the consequence.

Profiling And Independent PHP Tuning

  • Profile standard workflows: SQL count/cumulative time and highest-frequency/slowest queries; Redis command count/cumulative time; Laravel request CPU; payload/serialization cost where measurable. Identify the top five measured server-side costs and record measurement limitations. Keep intrusive profiling separate from throughput runs and quantify its overhead where practical.
  • Make application optimizations only for measured hotspots, with isolated before/after comparisons and unchanged correctness contracts.
  • Independently test opcache.validate_timestamps=0 for immutable images, Composer authoritative classmaps, OPcache sizing, preload if practical, and a newer supported PHP version if dependencies allow. Keep these results separate from HTTP-runtime comparisons so improvements can be attributed correctly.

Correctness And Recovery

  • Run the existing Server test/conformance suite against every materially faster finalist. Use published first-party PHP, Python, and Rust SDKs for relevant end-to-end protocol coverage; keep the performance workload itself fixed. Do not equate a PHP-only happy path with full portable correctness.
  • Persistent workers must prove no cross-request mutable state or cross-namespace/authentication leakage, including alternating/concurrent authenticated requests, failures followed by new requests, and worker recycling.
  • Cover workflow start, workflow-task poll/completion, activity poll/completion, signals, queries, updates, timers, worker registration, external payloads, failure/retry, cancellation, and application reload/restart. Check history/results, duplicate claims/completions, and final backlog, not only HTTP success rates.
  • Test generic backend recovery in reproducible, non-production fixtures: MySQL connection loss/restart or primary change and Redis interruption/promotion. Existing MySQL Router and Redis Sentinel fixtures may be used where applicable; these are reusable Server tests, not a qualification of any particular managed topology. Verify polls during interruption, stale persistent connections being discarded, reconnect behavior, lease/fencing correctness, and readiness failing and recovering. A faster candidate that weakens these Server guarantees is not acceptable.
  • Run finalists for at least two hours with representative short requests and long polls. Record initial/peak/final memory, slope per hour after warmup, worker recycling, errors, completions, final backlog, Redis key growth, and server-owned cache growth. Distinguish bounded cache warmup from a leak; inspect recycling behavior rather than concealing growth behind restarts.

Deliverables

  • A readable report with this comparison table and definitions for the reported units, load level, and limits:
Candidate WF/s Delta baseline p95 p99 CPU Peak RAM Idle polls Poll wake latency 2h memory slope Correctness Operational complexity
  • Include p50, repetitions/variation, exact commands, all Docker/config variants, hardware/environment identity, failures and rejected experiments, measured hotspots, and raw artifact links. Mark unmeasured fields as such rather than estimating them.
  • Keep reproducible source/configuration in small isolated commits in this repository. Store sanitized raw run outputs in existing artifact storage with an explicit bounded retention period sufficient for review. Do not commit permanent generated per-run directories or create a new benchmark orchestration platform.
  • Recommend one of: keep Apache/mod_php; move to nginx/PHP-FPM; move to Apache event/PHP-FPM; move to Octane/FrankenPHP; move to Octane/Swoole; retain the HTTP runtime but redesign long polling; combine a runtime change with a long-poll redesign. State the measured reason, correctness/recovery tradeoffs, operational cost, confidence, and any remaining qualification required before a rollout.

Constraints

  • No changes to public API semantics, workflow durability, task leasing, fencing, namespace isolation, or failover guarantees.
  • Use isolated fixture data only. No live customer workloads or production configuration changes during experiments.
  • No successful benchmark claims from partial, failed, or correctness-unqualified runs. Preserve failures as findings, not passes.
  • Do not optimize DW Standard Workflow v1 at the expense of timers, signals, queries, activities, or long polls; measure those separately.
  • Reuse the existing benchmark/conformance commands and mature OSS. Keep automation small and approachable; do not build a new orchestration platform.

Related completed foundations: #104 (DW Standard Workflow v1 definition), #86 (disposable performance soak runner). This issue evaluates execution architecture; it does not reopen their completed work.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority:P3Lower-priority product improvement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions