Skip to content

Commit 7f021de

Browse files
committed
Merge branch 'feat/conc-control' of github.com:simstudioai/sim into feat/conc-control
2 parents 9477964 + 53733e4 commit 7f021de

File tree

5 files changed

+209
-0
lines changed

5 files changed

+209
-0
lines changed

apps/sim/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
"dev:sockets": "bun run socket/index.ts",
1414
"dev:worker": "bun run worker/index.ts",
1515
"dev:full": "bunx concurrently -n \"App,Realtime,Worker\" -c \"cyan,magenta,yellow\" \"bun run dev\" \"bun run dev:sockets\" \"bun run dev:worker\"",
16+
"load:workflow": "bun run load:workflow:baseline",
17+
"load:workflow:baseline": "BASE_URL=${BASE_URL:-http://localhost:3000} WARMUP_DURATION=${WARMUP_DURATION:-10} WARMUP_RATE=${WARMUP_RATE:-2} PEAK_RATE=${PEAK_RATE:-8} HOLD_DURATION=${HOLD_DURATION:-20} bunx artillery run scripts/load/workflow-concurrency.yml",
18+
"load:workflow:waves": "BASE_URL=${BASE_URL:-http://localhost:3000} WAVE_ONE_DURATION=${WAVE_ONE_DURATION:-10} WAVE_ONE_RATE=${WAVE_ONE_RATE:-6} QUIET_DURATION=${QUIET_DURATION:-5} WAVE_TWO_DURATION=${WAVE_TWO_DURATION:-15} WAVE_TWO_RATE=${WAVE_TWO_RATE:-8} WAVE_THREE_DURATION=${WAVE_THREE_DURATION:-20} WAVE_THREE_RATE=${WAVE_THREE_RATE:-10} bunx artillery run scripts/load/workflow-waves.yml",
19+
"load:workflow:isolation": "BASE_URL=${BASE_URL:-http://localhost:3000} ISOLATION_DURATION=${ISOLATION_DURATION:-30} TOTAL_RATE=${TOTAL_RATE:-9} WORKSPACE_A_WEIGHT=${WORKSPACE_A_WEIGHT:-8} WORKSPACE_B_WEIGHT=${WORKSPACE_B_WEIGHT:-1} bunx artillery run scripts/load/workflow-isolation.yml",
1620
"build": "bun run build:pptx-worker && next build",
1721
"build:pptx-worker": "bun build ./lib/execution/pptx-worker.cjs --target=node --format=cjs --outfile ./dist/pptx-worker.cjs",
1822
"start": "next start",

apps/sim/scripts/load/README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Workflow Load Tests
2+
3+
These local-only Artillery scenarios exercise `POST /api/workflows/[id]/execute` in async mode.
4+
5+
## Requirements
6+
7+
- The app should be running locally, for example with `bun run dev:full`
8+
- Each scenario needs valid workflow IDs and API keys
9+
- All scenarios default to `http://localhost:3000`
10+
11+
The default rates are tuned for these local limits:
12+
13+
- `ADMISSION_GATE_MAX_INFLIGHT=500`
14+
- `DISPATCH_MAX_QUEUE_PER_WORKSPACE=1000`
15+
- `DISPATCH_MAX_QUEUE_GLOBAL=50000`
16+
- `WORKSPACE_CONCURRENCY_FREE=5`
17+
- `WORKSPACE_CONCURRENCY_PRO=50`
18+
- `WORKSPACE_CONCURRENCY_TEAM=200`
19+
- `WORKSPACE_CONCURRENCY_ENTERPRISE=200`
20+
21+
That means the defaults are intentionally aimed at forcing queueing for a Free workspace without overwhelming a single local dev server process.
22+
23+
## Baseline Concurrency
24+
25+
Use this to ramp traffic into one workflow and observe normal queueing behavior.
26+
27+
Default profile:
28+
29+
- Starts at `2` requests per second
30+
- Ramps to `8` requests per second
31+
- Holds there for `20` seconds
32+
- Good for validating queueing against a Free workspace concurrency of `5`
33+
34+
```bash
35+
WORKFLOW_ID=<workflow-id> \
36+
SIM_API_KEY=<api-key> \
37+
bun run load:workflow:baseline
38+
```
39+
40+
Optional variables:
41+
42+
- `BASE_URL`
43+
- `WARMUP_DURATION`
44+
- `WARMUP_RATE`
45+
- `PEAK_RATE`
46+
- `HOLD_DURATION`
47+
48+
For higher-plan workspaces, a good local starting point is:
49+
50+
- Pro: `PEAK_RATE=20` to `40`
51+
- Team or Enterprise: `PEAK_RATE=50` to `100`
52+
53+
## Queueing Waves
54+
55+
Use this to send repeated bursts to one workflow in the same workspace.
56+
57+
Default profile:
58+
59+
- Wave 1: `6` requests per second for `10` seconds
60+
- Wave 2: `8` requests per second for `15` seconds
61+
- Wave 3: `10` requests per second for `20` seconds
62+
- Quiet gaps: `5` seconds
63+
64+
```bash
65+
WORKFLOW_ID=<workflow-id> \
66+
SIM_API_KEY=<api-key> \
67+
bun run load:workflow:waves
68+
```
69+
70+
Optional variables:
71+
72+
- `BASE_URL`
73+
- `WAVE_ONE_DURATION`
74+
- `WAVE_ONE_RATE`
75+
- `QUIET_DURATION`
76+
- `WAVE_TWO_DURATION`
77+
- `WAVE_TWO_RATE`
78+
- `WAVE_THREE_DURATION`
79+
- `WAVE_THREE_RATE`
80+
81+
## Two-Workspace Isolation
82+
83+
Use this to send mixed traffic to two workflows from different workspaces and compare whether one workspace's queue pressure appears to affect the other.
84+
85+
Default profile:
86+
87+
- Total rate: `9` requests per second for `30` seconds
88+
- Weight split: `8:1`
89+
- In practice this sends heavy pressure to workspace A while still sending a light stream to workspace B
90+
91+
```bash
92+
WORKFLOW_ID_A=<workspace-a-workflow-id> \
93+
SIM_API_KEY_A=<workspace-a-api-key> \
94+
WORKFLOW_ID_B=<workspace-b-workflow-id> \
95+
SIM_API_KEY_B=<workspace-b-api-key> \
96+
bun run load:workflow:isolation
97+
```
98+
99+
Optional variables:
100+
101+
- `BASE_URL`
102+
- `ISOLATION_DURATION`
103+
- `TOTAL_RATE`
104+
- `WORKSPACE_A_WEIGHT`
105+
- `WORKSPACE_B_WEIGHT`
106+
107+
## Notes
108+
109+
- `load:workflow` is an alias for `load:workflow:baseline`
110+
- All scenarios send `x-execution-mode: async`
111+
- Artillery output will show request counts and response codes, which is usually enough for quick local verification
112+
- At these defaults, you should observe queueing behavior before you approach `ADMISSION_GATE_MAX_INFLIGHT=500` or `DISPATCH_MAX_QUEUE_PER_WORKSPACE=1000`
113+
- If you still see lots of `429` or `ETIMEDOUT` responses locally, lower the rates again before increasing durations
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
config:
2+
target: "{{ $processEnvironment.BASE_URL }}"
3+
phases:
4+
- duration: "{{ $processEnvironment.WARMUP_DURATION }}"
5+
arrivalRate: "{{ $processEnvironment.WARMUP_RATE }}"
6+
rampTo: "{{ $processEnvironment.PEAK_RATE }}"
7+
name: baseline-ramp
8+
- duration: "{{ $processEnvironment.HOLD_DURATION }}"
9+
arrivalRate: "{{ $processEnvironment.PEAK_RATE }}"
10+
name: baseline-hold
11+
defaults:
12+
headers:
13+
content-type: application/json
14+
x-api-key: "{{ $processEnvironment.SIM_API_KEY }}"
15+
x-execution-mode: async
16+
scenarios:
17+
- name: baseline-workflow-concurrency
18+
flow:
19+
- post:
20+
url: "/api/workflows/{{ $processEnvironment.WORKFLOW_ID }}/execute"
21+
json:
22+
input:
23+
source: artillery-baseline
24+
runId: "{{ $uuid }}"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
config:
2+
target: "{{ $processEnvironment.BASE_URL }}"
3+
phases:
4+
- duration: "{{ $processEnvironment.ISOLATION_DURATION }}"
5+
arrivalRate: "{{ $processEnvironment.TOTAL_RATE }}"
6+
name: mixed-workspace-load
7+
defaults:
8+
headers:
9+
content-type: application/json
10+
x-execution-mode: async
11+
scenarios:
12+
- name: workspace-a-traffic
13+
weight: "{{ $processEnvironment.WORKSPACE_A_WEIGHT }}"
14+
flow:
15+
- post:
16+
url: "/api/workflows/{{ $processEnvironment.WORKFLOW_ID_A }}/execute"
17+
headers:
18+
x-api-key: "{{ $processEnvironment.SIM_API_KEY_A }}"
19+
json:
20+
input:
21+
source: artillery-isolation
22+
workspace: a
23+
runId: "{{ $uuid }}"
24+
- name: workspace-b-traffic
25+
weight: "{{ $processEnvironment.WORKSPACE_B_WEIGHT }}"
26+
flow:
27+
- post:
28+
url: "/api/workflows/{{ $processEnvironment.WORKFLOW_ID_B }}/execute"
29+
headers:
30+
x-api-key: "{{ $processEnvironment.SIM_API_KEY_B }}"
31+
json:
32+
input:
33+
source: artillery-isolation
34+
workspace: b
35+
runId: "{{ $uuid }}"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
config:
2+
target: "{{ $processEnvironment.BASE_URL }}"
3+
phases:
4+
- duration: "{{ $processEnvironment.WAVE_ONE_DURATION }}"
5+
arrivalRate: "{{ $processEnvironment.WAVE_ONE_RATE }}"
6+
name: wave-one
7+
- duration: "{{ $processEnvironment.QUIET_DURATION }}"
8+
arrivalRate: 1
9+
name: quiet-gap
10+
- duration: "{{ $processEnvironment.WAVE_TWO_DURATION }}"
11+
arrivalRate: "{{ $processEnvironment.WAVE_TWO_RATE }}"
12+
name: wave-two
13+
- duration: "{{ $processEnvironment.QUIET_DURATION }}"
14+
arrivalRate: 1
15+
name: quiet-gap-two
16+
- duration: "{{ $processEnvironment.WAVE_THREE_DURATION }}"
17+
arrivalRate: "{{ $processEnvironment.WAVE_THREE_RATE }}"
18+
name: wave-three
19+
defaults:
20+
headers:
21+
content-type: application/json
22+
x-api-key: "{{ $processEnvironment.SIM_API_KEY }}"
23+
x-execution-mode: async
24+
scenarios:
25+
- name: workflow-queue-waves
26+
flow:
27+
- post:
28+
url: "/api/workflows/{{ $processEnvironment.WORKFLOW_ID }}/execute"
29+
json:
30+
input:
31+
source: artillery-waves
32+
runId: "{{ $uuid }}"
33+
waveProfile: single-workspace

0 commit comments

Comments
 (0)