nix(loadtest): report latency instead of rate
We previously used "rate", i.e. number of requests per second, as the primary metric to judge loadtest results. However, this has always been varying from run to run quite a bit, especially in CI where other jobs possibly run on the same VM host. The run-to-run variance has massively increased after splitting the results up per request. Example run in CI with rate on the PR introducing this change (on which we would expect no change at all): | rate [1/s] | main | head | Δ | |:-----------------------------------|-------:|-------:|-----:| | / | 9.4 | 9.5 | 1% | | /actors | 870.4 | 1023.0 | 18% | | /actors?actor=eq.1 | 188.5 | 198.6 | 5% | | /actors?actor=eq.1&columns=name | 197.3 | 167.1 | -15% | | /actors?select=*,roles(*,films(*)) | 153.9 | 144.9 | -6% | | /films?columns=id,title | 157.9 | 182.6 | 16% | | /films?columns=id,title,year,... | 87.0 | 87.1 | 0% | | /roles | 204.5 | 267.3 | 31% | | /rpc/call_me | 231.3 | 208.8 | -10% | | /rpc/call_me?name=John | 212.2 | 201.7 | -5% | From the data we can easily tell that the very reason that rate as a paramter has only worked, so far, because the data was *heavily* dominated by the requests on the root endpoint for OpenAPI. The longer duration makes the request much less vulnerable for concurrent activity. For all other requests its essentially not possible to judge the effect of a PR this way. One way to counter this would be to massively increase the time the loadtest runs. More samples will result in a smoother average. However, that's not practical for usability of CI. In the original PR #1812 I already evaluated using the *minimum latency* as the most reliable criteriumi, but this has never really caught on. The theory behind this is: The variation in timings between requests is happening because of concurrent activity, priority chosen by the scheduler, availability of resources and such - all factors *outside* our control, and *irrelevant* to the Haskell code we're writing. Using the minimum latency is an estimation of how fast the code can run *in the best case*. This might not be a number relevant for production, but it's much more directly related to the code we write. Here's to show how variation becomes *much* smaller with minimum latency as the parameter: | min latency [μs] | main | head | Δ | |:-----------------------------------|---------:|-------:|-----:| | / | 1275.3 | 1263.6 | -1% | | /actors | 10.0 | 9.9 | -1% | | /actors?actor=eq.1 | 50.7 | 48.3 | -5% | | /actors?actor=eq.1&columns=name | 54.1 | 54.0 | 0% | | /actors?select=*,roles(*,films(*)) | 63.2 | 61.9 | -2% | | /films?columns=id,title | 51.1 | 50.7 | -1% | | /films?columns=id,title,year,... | 121.9 | 121.8 | 0% | | /roles | 42.9 | 42.6 | -1% | | /rpc/call_me | 45.6 | 45.4 | 0% | | /rpc/call_me?name=John | 44.4 | 44.2 | 0% | Since we're separating results per request now, we can only sensibly focus on *one* parameter - otherwise this would get really clunky UI-wise. Especially for automated CI failures, minimum latency is the logical choice. This commit starts using minimum latency, i.e. P0, but any percentile should be an improvement over the status quo. A later commit will change to a different P-value.
This commit is contained in:
@@ -268,7 +268,7 @@ let
|
||||
}
|
||||
''
|
||||
${vegeta}/bin/vegeta encode "$_arg_file" \
|
||||
| ${jq}/bin/jq --slurp 'map(select(.url != "")) | group_by("\(.code) \(.method) \(.url)") | map({("\(.[0].code) \(.[0].method) \(.[0].url)" | sub("http://postgrest";"")): add | .latency / length | 10e6 / . }) | .[]' \
|
||||
| ${jq}/bin/jq --slurp 'map(select(.url != "")) | group_by("\(.code) \(.method) \(.url)") | map({("\(.[0].code) \(.[0].method) \(.[0].url)" | sub("http://postgrest";"")): map(.latency) | min / 10e3 }) | .[]' \
|
||||
| ${jq}/bin/jq --arg branch "$(basename "$_arg_file" .bin)" '. + {branch: $branch}'
|
||||
'';
|
||||
|
||||
@@ -282,7 +282,8 @@ let
|
||||
import pandas as pd
|
||||
|
||||
pd.read_json(sys.stdin) \
|
||||
.set_index('rate') \
|
||||
.rename(columns={'latency': 'min latency [μs]'}) \
|
||||
.set_index('min latency [μs]') \
|
||||
.drop(['branch']) \
|
||||
.convert_dtypes() \
|
||||
.to_markdown(sys.stdout, floatfmt='.1f')
|
||||
@@ -305,8 +306,8 @@ let
|
||||
echo -e "## Loadtest results $marker\n"
|
||||
|
||||
find loadtest -type f -iname '*.bin' -exec ${reporter} {} \; \
|
||||
| ${jq}/bin/jq '[paths(scalars) as $path | {rate: $path | join("."), (.branch): getpath($path)}]' \
|
||||
| ${jq}/bin/jq --slurp 'flatten | group_by(.rate) | map(add)' \
|
||||
| ${jq}/bin/jq '[paths(scalars) as $path | {latency: $path | join("."), (.branch): getpath($path)}]' \
|
||||
| ${jq}/bin/jq --slurp 'flatten | group_by(.latency) | map(add)' \
|
||||
| ${toMarkdown}
|
||||
|
||||
echo -e "\n\n## Loadtest elapsed seconds vs CPU/MEM usage $marker\n"
|
||||
|
||||
Reference in New Issue
Block a user