Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Blame · docs/load-testing.md

Line attribution on main · 96 line(s)

0448a978
Keenan Nemetz
10d ago
1 # Load testing
2
3 Vole ships a load harness that drives a real instance on a loopback port with a
4 temporary data directory. It runs the same server code an operator runs, behind
5 the `load` build tag so `go test ./...` and the verify workflow never execute it.
6
7 Use it to answer a question with a number: did this change make a route faster,
8 and where does it stop? The limits in [Configuration](configuration.md) were
9 chosen from reasoning first; a run here is what replaces that with measurement.
10
11 ## One command
12
1dfa8cbd
Keenan Nemetz
10d ago
13 Every route:
14
0448a978
Keenan Nemetz
10d ago
15 ```sh
1dfa8cbd
Keenan Nemetz
10d ago
16 VOLE_LOAD_SCENARIO=all VOLE_LOAD_CONCURRENCY=4 VOLE_LOAD_DURATION=5s \
17 go test -tags load,sqlite_fts5 -run '^TestLoad$' -v ./server
18 ```
19
20 One route, or clones past the limit:
21
22 ```sh
23 VOLE_LOAD_SCENARIO=clone VOLE_LOAD_CONCURRENCY=16 VOLE_LOAD_DURATION=30s \
0448a978
Keenan Nemetz
10d ago
24 go test -tags load,sqlite_fts5 -run '^TestLoad$' -v ./server
25 ```
26
27 The build needs `sqlite_fts5` beside `libsqlite3` (see
28 [Getting started](getting-started.md)); `-tags load` on the command line replaces
29 `GOFLAGS`, so both belong on it.
30
31 ## Knobs
32
33 | Variable | Default | What it does |
34 |---|---|---|
122cbe4f
Keenan Nemetz
7d ago
35 | `VOLE_LOAD_SCENARIO` | `clone` | `all`, or one of `clone`, `clone-large`, `blob-small`, `blob-limit`, `raw-small`, `raw-limit`, `raw-range`, `overview`, `tree`, `commits`, `compare`, `change`, `issue`, `activity`, `instance-activity`, `home`, `search-code`, `search-find`, `artifact`, `artifact-range`, `site`, `comment`. |
0448a978
Keenan Nemetz
10d ago
36 | `VOLE_LOAD_CONCURRENCY` | `4` | Workers driving the scenario in parallel. |
37 | `VOLE_LOAD_DURATION` | `10s` | How long the workers keep going. |
38
39 ## What it seeds
40
1dfa8cbd
Keenan Nemetz
10d ago
41 Three repositories and a site:
42
43 - `demo`: 300 commits; files at several sizes including one at the configured
44 `max_blob_bytes` (the harness sets 8 MiB, so the file is one a push could
45 deliver); an issue with five comments; a change request whose branch carries a
46 4000-line diff, with five review comments and an approval by a second account;
47 a release tag with one artifact in the LFS store; and a `pages` branch served
48 at `demo.pages.test`.
49 - `small`: 50 commits and small files, so a clone is quick.
50 - `large`: 3000 commits and one 16 MiB incompressible blob, so a clone is not.
0448a978
Keenan Nemetz
10d ago
51
1dfa8cbd
Keenan Nemetz
10d ago
52 `demo` and `large` are read once before the run so their indexes are built
53 outside the measurement.
0448a978
Keenan Nemetz
10d ago
54
1dfa8cbd
Keenan Nemetz
10d ago
55 The clone scenario gives every worker a distinct `X-Forwarded-For` address (the
56 fixture trusts the header on loopback), so the global `git_http_max_concurrent`
57 is the limit that refuses rather than the per-client one. At the configured
58 `git_http_max_concurrent` the run stays at `200`; above it, refusals arrive as
59 `503` with `Retry-After`, and a clone caught by one reports the curl failure.
0448a978
Keenan Nemetz
10d ago
60
122cbe4f
Keenan Nemetz
7d ago
61 ## The one scenario that writes
62
63 Every scenario above reads. `comment` posts a comment as the fixture's writer
64 account, which is one commit on the metadata branch that every write in the instance
65 shares: the issue, the change request, the approval, and the release pointer all move
66 the same ref. It is the scenario to run after touching the write path, and the one
67 where throughput should stay flat as workers are added while latency grows with the
68 queue behind it — a rising error count or a falling throughput means writers are
69 fighting over the branch instead of taking turns.
70
0448a978
Keenan Nemetz
10d ago
71 ## What it reports
72
73 Per scenario: requests and throughput, latency at p50/p90/p99, HTTP statuses,
74 failure reasons, and the peak of process RSS, open file descriptors, goroutines,
75 and git child processes during the run.
76
77 ## What it does not tell you
78
79 - The server runs inside the test process, so RSS and goroutines include the
80 harness and its clients.
81 - Child processes are counted by name (`http-backend`, `upload-pack`,
82 `receive-pack`). The harness's own `git clone` processes are excluded.
83 - Loopback has no network cost and no TLS. The numbers are a floor and a way to
84 compare changes, not a prediction of a real deployment.
85 - A warm cache is not simulated beyond the index build: the first run after a
86 restart will be slower.
26c9be33
Keenan Nemetz
10d ago
87 - The `raw-range` and `artifact-range` scenarios send a `Range` header and expect
88 `206` with only the requested bytes. Before #119 the raw route answered `200`
89 with the whole file, which is why the scenario exists.
0448a978
Keenan Nemetz
10d ago
90
91 ## Adding a scenario
92
93 A scenario is a step function and a deadline: each worker calls the step in a
94 loop until `VOLE_LOAD_DURATION` expires, and the step reports the HTTP status it
95 saw and any failure. The seeding, sampling and reporting in
96 `server/load_test.go` are shared, so a new route is a `case` and a function.