Repository navigation
[#1090] Check replication convergence under load next to the Docker tests, and run the Docker tests of both images after their builds - #1192
Conversation
498e144 to
0c3033e
Compare
0c3033e to
8a4319a
Compare
maximthomas
left a comment
There was a problem hiding this comment.
praise: The harness checks the data itself, and its checks hold up against real runs.
changelog-gaps.pyreads the real changelog: on theconvergence-rs534artifact of run 38865878001 it parsed 327697-344522 changes per replica with contiguous sequence numbers, and cv-d0's 344522 matches its clients' 318.5 + 64.3 ops/s over 900 s.docker-test, expanded per flavor with the matrix values substituted, is the old test steps ofbuild-docker/build-docker-alpineexcept the three changes the description states (512m for alpine, 5-day retention, PDB vs JE on default only).assert_offlinerefuses exports that failed alike (assert-converged.sh:69-73), andverify-index's error count cannot wrap to 0 (Utils.filterExitCodecaps it at 255).
question (blocking): Should a red docker-convergence row on a push to master skip the whole of deploy.yml?
.github/workflows/build.yml:1105-1144, .github/workflows/deploy.yml:18-21, :44
docker-convergence has no continue-on-error and no job-level if, so it runs on every push of build.yml:18-19, master included. deploy.yml runs package-deploy-maven only when the Build run concluded success (:44). The description expects rs534 to fail until #1206 and combined until #1195, and both issues are open. So every master push with a red row loses the snapshot deploy, the docs site and wiki publish and the launcher refresh, and the description does not mention that. If that is intended, say so in the description and in a comment on the job. If not, keep the row visible but out of the run's conclusion until both issues are fixed:
docker-convergence:
# #1090: writes to every directory server of a replication topology at once, then checks
# that the replicas hold the same data; next to docker-test, on the image of build-docker
needs: build-docker
# rs534 fails until #1206 and combined until #1195 are fixed: a red row must not turn the
# Build run red, which deploy.yml reads as "skip the snapshot deploy and the site publish"
continue-on-error: trueThis relies on GitHub's documented job-level continue-on-error behaviour; this review did not run it.
issue (non-blocking): The online checks read a failed search or log read as a pass.
.github/scripts/replication/assert-converged.sh:166, :175, :142; .github/scripts/replication/run.sh:83
assert_online || checks=... turns errexit and the ERR trap off inside the function, so the status of ldaps_search | awk | sort at :166 is lost. A search that fails the same way on every DS gives equal empty dns/*.txt files, and cmp passes. [ -z "$(ldaps_search … | grep '^dn:')" ] at :175 drops the status in any context: a failed conflict search on the node that holds conflicts reads as "no conflicts", and nothing offline checks conflicts. since_mark | grep … || true at :142 reads an errors log that could not be read as "no replication errors". A probe under run.sh's set -eE -o pipefail and trap ran the or-call to its end, nothing flagged, exit 0. assert_rs_mesh (:152) stays as is, since got=0 is flagged, and so does record_switches (:137), which counts rather than checks:
for n in $DS_NODES; do
ldaps_search $n "$BASE_DN" sub "$f" 1.1 >"$OUT/dns/$i.$n.ldif" \
|| { echo "::error::$f failed on $n"; failed=1; }
awk '/^dn: /{ print tolower($0) }' "$OUT/dns/$i.$n.ldif" | sort >"$OUT/dns/$i.$n.txt"
done
# …
for n in $DS_NODES; do
# the load makes no naming conflicts: each server adds under a uid prefix of its own
if ! out=$(ldaps_search $n "$BASE_DN" sub "(ds-sync-conflict=*)" 1.1); then
echo "::error::the ds-sync-conflict search failed on $n"; failed=1
elif grep -q '^dn:' <<<"$out"; then
echo "::error::$n holds ds-sync-conflict entries"; failed=1
fi
done
for n in $ALL_NODES; do
if ! lines=$(since_mark $n); then
echo "::error::the errors log of $n could not be read"; failed=1; continue
fi
lines=$(grep -E "category=SYNC severity=ERROR|SSLHandshakeException|bad_certificate" <<<"$lines" || true)
[ -z "$lines" ] || { printf '%s\n' "$lines" >"$OUT/logs/$n.errors"; echo "::error::$n logged replication errors, see logs/$n.errors"; failed=1; }
doneAdd out to the local line of assert_online. errors_since_mark then has no caller left.
issue (non-blocking): check_client passes a client that stopped before the end of the load.
.github/scripts/replication/load.sh:99-107
check_client looks at the exit code and the OOM kill only on its "did no work" branch, and it never compares the row count with DURATION. Take a client whose kept-open connection drops at t≈400 s. PerformanceRunner calls stopTool(true), StatsThread.stopRecording(true) skips the last row, and the tool exits 0. The check then sees 39 rows, ops > 0 and no errors, and passes, even though the load stopped at 400 of 900 s and a request failed. A request that fails after the server assigned its CSN then shows up as a changelog gap reported as a lost change. Run 38865878001 printed 90-91 rows for 900 s at --statInterval 10:
check_client() { # <client>
awk -F, -v c=$1 -v exit_state="$(cat "$OUT/load/$1.exit")" -v want=$((DURATION / 10 - 1)) '
/^Time \(seconds\)/ { for (i = 1; i <= NF; i++) { if ($i == "Recent throughput (ops/second)") t = i; if ($i == "Errors/second") e = i }; next }
t && $1 ~ /^[0-9.]+$/ { rows++; ops += $t; errors += $e; last = $0 }
END {
if (!rows || ops == 0) { printf "::error::%s did no work (exit code and OOM kill: %s)\n", c, exit_state; exit 1 }
if (errors > 0) { printf "::error::%s got errors back\n", c; exit 1 }
if (exit_state != "0 false") { printf "::error::%s ended with exit code and OOM kill %s\n", c, exit_state; exit 1 }
if (rows < want) { printf "::error::%s stopped after %d of %d intervals\n", c, rows, want; exit 1 }
printf "%s: %d intervals, last: %s\n", c, rows, last
}' "$OUT/load/$1.csv"
}issue (non-blocking): The switch count leaves out the load-balancing switches (msgID 213).
.github/scripts/replication/assert-converged.sh:131-139
ReplicationBroker (:2600-2619) logs NOTE_LOAD_BALANCE_REPLICATION_SERVER (213) on the other branch of the same decision as 188, then makes the same reStart(true). In rs534 a DS that leaves an overloaded RS logs 213, so the summary's switches column reads 0 for a run that did switch.
echo "$n $(since_mark $n | grep -E "msgID=(188|213) " | grep -cF "for domain \"$BASE_DN\"" || true)"issue (non-blocking): sampler_stop's rm can fail a green run.
.github/scripts/replication/monitor.sh:91-101, :33-40
kill/wait $SAMPLER reach only the sampler subshell. The admin_search | awk >"$dir/$rs" & children it has forked live on. If a child opens its file after rm -rf has read the directory, rmdir fails with ENOTEMPTY and rm exits 1. Under set -eE that marks a green run failed, and inside the ERR trap errexit stops collect and the cleanup. The window is rare (between a fork and its open), but nothing guards it.
rm -rf "$OUT"/sample.* || truesuggestion (non-blocking): Count the switches of a run that fails before the online checks too.
.github/scripts/replication/run.sh:40, .github/scripts/replication/assert-converged.sh:162
record_switches has one caller, assert_online. A load check or a wait_converged timeout, the failures a switch under load would explain, therefore leave "-" in the column while the containers are still up.
trap 'code=$?; [ "$BASH_SUBSHELL" -eq 0 ] || exit $code; sampler_stop; [ -f "$OUT/switches.txt" ] || [ ! -f "$OUT/logs/${DS_NODES%% *}.mark" ] || record_switches || true; timings; collect; summarize failed; [ "$KEEP" = 1 ] || cleanup; exit $code' ERRquestion (non-blocking): Is it intended that a failed build of one flavor skips the docker tests of both?
.github/workflows/build.yml:634-637
docker-test needs both builds, so a failed or cancelled build-docker-alpine, for example on one of its QEMU platforms, skips docker-test (default) as well. At BASE the default image's tests, Trivy and the benchmarks ran in build-docker. The run is red either way, so this loses signal but gives no wrong verdict: Minor. If it is not intended, gate on either build. The row whose image was never uploaded then fails at its download. A job-level if cannot read matrix; this review did not run either point:
needs: [build-docker, build-docker-alpine]
if: ${{ !cancelled() && (needs.build-docker.result == 'success' || needs.build-docker-alpine.result == 'success') }}question (non-blocking): Is the 1-day retention of the image artifacts a deliberate storage trade-off?
.github/workflows/build.yml:544-549, :627-632
On a red convergence row, which is expected while #1195 and #1206 are open, "Re-run failed jobs" does not re-run the passed build jobs. More than 24 h later it fails at "Download the image". At BASE a re-run rebuilt from the 5-day build-maven artifact. The tars are about 250 MB each (264012084 and 250900074 bytes on run 38865878001). If the retention is not deliberate, keep them for retention-days: 5. If it is, a comment saying that a late re-run needs "Re-run all jobs" would do.
suggestion (non-blocking): Make the three converged samples span one monitoring period.
.github/scripts/replication/monitor.sh:64-80
A replication server rebuilds missing-changes once per monitoring-period, 60 s by default and set nowhere under .github. A server the snapshot does not hold reads as 0 (ReplicationDomainMonitorData.getMissingChanges). Three samples about 5 s apart can therefore all read one all-zero snapshot taken while changes are still in flight. The data checks would then fail on changes still on their way: a false red, and a CONVERGED_IN that is too short. Not run: it needs a run that logs the monitor build times or injects a DS switch after the load. No such early convergence was seen on the cited runs.
wait_converged() {
local start=$SECONDS hits=0 first=
until [ $hits -ge 3 ] && [ $((SECONDS - first)) -ge 60 ]; do
if converged_once; then hits=$((hits + 1)); first=${first:-$SECONDS}; else hits=0; first=; fisuggestion (non-blocking): Wait for the joins to end, not only for "healthy", before marking the logs.
.github/scripts/replication/topology-up.sh:46-51
join.sh touches BOOTSTRAP_COMPLETE (:965) before repair_replication_servers (:967), which can still wait for peers and rewrite replication-server lists. Such a write after mark_logs would put its errors into the run's errors check and a broker restart into the switch count. On run 38865878001 no repair wrote anything: after membership the container logs show only "waiting for … to register" lines. So nobody has seen this road; it was not traced through join.sh:509-565. docker-test-replication.sh:182 (bootstrap_ended, used at :781) already waits for exactly this.
suggestion (non-blocking): The changelog check passes when no change of a directory server matches the parser.
.github/scripts/replication/changelog-gaps.py:81-82, :123-124; .github/scripts/replication/assert-converged.sh:92-109
A record with an unknown type byte, or a changelog with no *.log file, is skipped, and the script exits 0 with nothing to report. A format drift that only filters records out therefore leaves the #1195 detector vacuous while CI stays green. The parser matches today's layout, as shown above.
for id in $(awk '{ print $1 }' "$OUT/server-ids.txt"); do
grep -q "^replica $id (" "$OUT/changelog/$n.txt" \
|| { echo "::error::the changelog of $n holds no change of the load by $(grep "^$id " "$OUT/server-ids.txt" | cut -d' ' -f2)"; failed=1; }
donePin: with this loop in assert_changelogs, the mutant UPDATE_TYPES = set() turns the run red.
suggestion (non-blocking): No CI row shows that the checks can fail.
.github/workflows/build.yml:1129-1136, .github/scripts/replication/lib.sh:40, .github/scripts/replication/run.sh:74-77
INJECT_DIVERGENCE is never set in CI. These mutants stay green on every row: cmp -s replaced by true, the ldifdiff || {...} dropped, case 1) mapped to 0) in assert_changelogs.
- name: Convergence self-test (the checks must report the injected divergence)
if: matrix.topology == 'combined'
shell: bash
env:
TOPOLOGY: combined
DURATION: "60"
SAMPLE_DATA: "1000"
INJECT_DIVERGENCE: "1"
OUT: ${{ runner.temp }}/replication-selftest
run: |
! .github/scripts/replication/run.sh "localhost:5000/${GITHUB_REPOSITORY,,}:${{ needs.build-docker.outputs.release_version }}" > "$RUNNER_TEMP/selftest.log" 2>&1
grep -q "(sn=Diverged) returns other entries" "$RUNNER_TEMP/selftest.log"suggestion (non-blocking): The changelog check cannot see a lost first or last change of a replica.
.github/scripts/replication/changelog-gaps.py:140-141
Gaps are counted only between consecutive changes inside the window. If a replica's last change of the load, or a tail of them, reaches no replication server, the check prints "0 missing". The data shows such a loss only until another server overwrites it (assert-converged.sh:87-91). Pin: count the gap between the last change before the window and the first one inside it, and have run.sh make one marker change on every DS after run_load and before LOAD_END_MS. A changelog with a replica's last load change removed then goes red.
suggestion (non-blocking): State the workflow couplings this PR keeps where they happen.
.github/workflows/build.yml:1105, :637, :549, :632
No test runs the workflow graph. Whatever the answers to the three questions above keep (the deploy skip, the cross-flavor skip, the 1-day re-run limit), a comment on the job naming the consequence is the only pin.
note (non-blocking): Not checked
docker-test (default|alpine)anddocker-convergence (rs534|combined)have not run at bbe9484. The head commit changed themodratevalues inload.shand the sampler stop inmonitor.shafter run 38865878001, and this review has no Docker daemon. It closes when those four checks finish on this head.
…mbined topologies for a replication convergence run .github/scripts/replication/run.sh <image> starts four directory servers and two replication servers (OpenIdentityPlatform#534, TOPOLOGY=rs534) or four combined servers (TOPOLOGY=combined) through the background join of the image, each in its REPLICATION_ROLE, and checks the shape: who runs a replication server, and that every directory server holds the seeded users. The joins take their turns, so the whole topology gets one deadline (UP_TIMEOUT) rather than one per server.
…with ldifdiff and verify their indexes Once the servers are stopped, a one-off container exports each replica (export-ldif --offline, without ds-sync-hist and ds-sync-state), checks that the export holds more than the seeded users, compares it with the first replica's through ldifdiff and runs verify-index. The differences go to diff/. INJECT_DIVERGENCE=1 writes one change to one replica only, through the replication repair control, which the run must report.
…searches the same, holds no conflicts and logged no replication errors While the servers run: the same equality searches on indexed attributes return the same DNs everywhere (an entry missing from one replica's index, OpenIdentityPlatform#1021), no ds-sync-conflict entry, every replication server connected to every other one, and no SYNC error or TLS handshake failure in any errors log after the topology came up. KEEP=1 leaves the servers of a failed run up.
…irectory servers seen from every replication server, and sample the backlog The topology has converged once every replication server knows every directory server in the domain of BASE_DN and none of them misses a change, on three samples in a row: a missing monitor entry is not a zero. A timeout prints the monitor. A sampler records missing-changes and approximate-delay every 5 s for the peaks of the run.
…modrate, addrate and searchrate Three clients per directory server, in containers of the same image: modrate on the shared users from every server at once, addrate with fifo deletes under a uid prefix of each server, searchrate. A rate tool exits with 0 whatever it got back, so each client must have done work and reported no errors in its CSV output.
…iled convergence run, and summarize the run A failed run packs every server's container log, logs/, config/ and changelogDb into state.tgz. Every run writes summary.md - and the job summary in GitHub Actions - with the convergence time, the peaks of missing-changes and approximate-delay per directory server, the throughput of each client and the time of each phase. The load collects every client's output and exit state before it checks any of them.
… and run their tests in one matrix job after them build-docker and build-docker-alpine build for every platform as before and save their linux/amd64 image as an artifact: the registry service of a build job is gone with it. docker-test loads it, under the tag the build pushed, and runs the test steps of both jobs as one matrix (default, alpine). Expanded per flavor, its steps are byte for byte the old ones; the PDB vs JE benchmark stays with the default image.
…t to the Docker tests docker-convergence runs .github/scripts/replication/run.sh on the image of build-docker, for the OpenIdentityPlatform#534 and the combined topology, next to docker-test, and uploads what the run leaves behind.
… open connections, read the load rates whatever the locale, and give the summary's delay in seconds The load is what the replication carries, so it sends writes only: searchrate is gone, since a read sends nothing to the replication servers. modrate and addrate use plain LDAP and --noRebind with eight connections each, rather than LDAPS with a connection and a bind for every operation, which cost more than the operation and held each client to some 30 operations a second. Their ceilings are LOAD_MOD_RATE and LOAD_ADD_RATE (250 and 50 a second for each directory server), and the summary shows them. The checks of the load and the peaks of the summary sum rates printed with a dot, which awk reads as 0 where the locale's decimal separator is a comma: the run sets LC_ALL=C. approximate-delay is in seconds (ReplicationDomainMonitorData divides by 1000), not milliseconds. docker-test is named after its flavor rather than after every value of its matrix; both flavors run their containers with 512m, and both benchmark artifacts are kept 5 days.
… let the servers and clients of the convergence run take the memory they need LOAD_MOD_RATE and LOAD_ADD_RATE default to 325 and 65 a second for each directory server, up from 250 and 50, which the clients reached on the CI runner, and docker-convergence keeps the load up for 900 s rather than 300 s. The servers and the load clients run with the JVM settings of the image and no container memory limit. The summary says what addrate's rate counts: adds or deletes, one or the other on each iteration.
… of the convergence run The one run without a bound on the heap lost both its runners: each of the fourteen JVMs of the rs534 topology - six servers, eight load clients - may take a quarter of the memory of a 16 GB runner, under a load that keeps them busy for 15 minutes. SERVER_JAVA_ARGS (-server -Xmx1g) and CLIENT_JAVA_ARGS (-Xmx256m) bound them again, twice what they had before; the containers stay without a memory limit, which would kill a server in the middle of the run.
…count the switches of replication server, and look for changes of the load missing from the changelogs The sampler asked the replication servers one after the other, each through an ldapsearch that starts a JVM: on a runner the load keeps busy that took a sample every minute or so. It asks them at once now, and the summary shows how often it got a sample. The summary counts, for each directory server, the switches of replication server (msgID 188) after the topology came up: changes made during such a switch under load were lost in OpenIdentityPlatform#1195. changelog-gaps.py reads the changelog of every replication server, streamed out of the stopped container, and reports the sequence numbers missing among the changes each replica made during the load: a directory server numbers its changes one after the other, so a gap there is a change that reached no replication server, which the data shows only until a later change overwrites it and missing-changes never. On the state of the failed run of OpenIdentityPlatform#1195 it finds the 81 lost changes of cv-c3 and nothing else. The offline checks now run after failed online checks as well, so that a run says which changes are missing where.
…wn, and remove the directory of the sample the stop of the sampler cuts short
…require each load client to cover the run, count every reconnection, and span a monitoring period with the converged samples The online checks run in a condition of run.sh, where neither errexit nor the ERR trap stops a command that fails: searches that failed alike on every replica gave the same empty answers, a failed conflict search read as no conflicts, and an errors log that could not be read as no errors. Each of them checks its own status now. A rate tool whose connection breaks stops at once, exits with 0 and leaves out the interval that would count the error. A client must exit with 0, print nothing besides its CSV output and cover the load. The summary counted the switches the broker decided on (msgID 188) and missed the others: 213, a connection the replication server closed, one that broke. It counts every new connection now (msgID 62), also when a run fails before its checks; OUT must be new or empty, so that the files of an earlier run do not mix in. A replication server computes missing-changes once a monitoring period, 60 s, so the three converged samples must span one. The run waits for the background joins to end once the containers are healthy, since a join still repairs the replication server lists then. Every changelog must hold changes of every directory server, and the docstring of changelog-gaps.py states what it cannot see. docker-test runs when either image was built, and the images are kept 3 days for a re-run of the failed jobs. A comment on docker-convergence states that a red row skips the snapshot deploy of a push to master.
bbe9484 to
e4a8431
Compare
|
Round 1: e4a8431, on master with #1205. The description is updated. question (blocking): should a red issue: the online checks read a failed search or log read as a pass. Fixed as you wrote it. The output of each indexed search, its error output included, goes to issue: issue: the switch count leaves out the load-balancing switches (msgID 213). Right, and 213 is not the only other road: a replication server that closes the connection (63) and a connection that breaks reconnect without 188 as well. The column counts every new connection now - msgID 62 (96 where the generation IDs differ), which the broker logs at the end of every connect ( issue: suggestion: count the switches of a run that fails before the online checks too. Done in the trap, as you wrote it. question (non-blocking): is it intended that a failed build of one flavor skips the docker tests of both? No; your question (non-blocking): is the 1-day retention of the image artifacts a deliberate storage trade-off? It is 3 days now, with a comment that a later re-run needs "Re-run all jobs". suggestion: make the three converged samples span one monitoring period. Done: suggestion: wait for the joins to end, not only for "healthy", before marking the logs. Done: suggestion: the changelog check passes when no change of a directory server matches the parser. Your loop is in, and suggestion: no CI row shows that the checks can fail. Not in this PR. For now the real rows show it: on run 38893291531 suggestion: the changelog check cannot see a lost first or last change of a replica. Right; documented - in the docstring of
suggestion: state the workflow couplings this PR keeps where they happen. Done: comments on note: not checked. Run 38893291531 at bbe9484 has finished:
|
Fixes #1090 with the base variant (points 1, 2, 4, 5 and 6 of the proposal). The disturbances of point 3 follow one at a time in later PRs, under #1208.
What it does
.github/scripts/replication/run.sh <image>writes to every directory server of a replication topology at once, then checks that the replicas hold the same data. It runs in CI and on a laptop with Docker alone; every setting comes from the environment (lib.sh).Topology (
topology-up.sh):TOPOLOGY=rs534- four directory servers without a replication server and two replication servers without data, the topology of Missing Changes count is piling up during peak load testing with OpenDJ 4.9.4 #534 - orTOPOLOGY=combined- four combined servers. Every server joins through the background join of the image (OPENDJ_REPLICATION_TYPE=simple) in itsREPLICATION_ROLE(Docker image: the background join has no server role, so a standalone replication server or a directory server without one needs the deprecated one-shot types #1178), and the run waits for every join to end, not only for every container to be healthy: a join goes on repairing the replication server lists after that. The run checks who runs a replication server and that every directory server holds the seeded users.Load (
load.sh), on every directory server at once forDURATIONseconds, from containers of the same image, over plain LDAP with eight connections per client kept open and bound once (--noRebind), up toLOAD_MOD_RATEmodifications andLOAD_ADD_RATEadds or deletes a second per directory server (325 and 65), with no container memory limit and the heap bounded (SERVER_JAVA_ARGS-server -Xmx1g,CLIENT_JAVA_ARGS-Xmx256m):modrateon the shared users from every server (concurrent modifications of one entry, Replication of modify operations on same DN may be replayed out of order #139),addratewith fifo deletes under a uid prefix of each server (no naming conflicts). The valuesmodratewrites start with the name of their server: the rate tools seedrandandrandstralike in every client, so all of them would write the same values in the same order, a change lost on its way could hide behind the same value written later by another server, and a value in a diff would not tell which server made it. Writes only: a read sends nothing to the replication servers. A rate tool exits with 0 whatever it got back, and one whose connection breaks stops early, still with 0, leaving out the interval that would count the error: so each client must have done work, reported no errors in its CSV output, exited with 0, printed nothing besides its CSV output and covered the whole load.Convergence (
monitor.sh): every replication server knows every directory server of the domain and none misses a change, on three samples in a row that span a monitoring period (60 s: a replication server computesmissing-changesonce a period, and a server its last computation does not know reads as 0) - a missing monitor entry is not a zero. A sampler recordsmissing-changesandapproximate-delayfor the peaks, asking every replication server at once (each ask is anldapsearch, a JVM that takes tens of seconds to start on a busy runner); stopping the sampler removes what the sample it cuts short leaves behind.Checks on the data (
assert-converged.sh), sincemissing-changesreaching 0 is not enough:ds-sync-conflictentry; every replication server connected to every other one; noSYNCerror, TLS handshake failure or replay give-up in any errors log after the topology came up. A search or a read of a log that fails is an error of its own: searches that failed alike on every replica would give the same empty answer;export-ldif --offlineof every replica withoutds-sync-hist/ds-sync-state, a guard against empty exports,ldifdiffagainst the first replica (the differences go todiff/),verify-index;changelog-gaps.pyreads the changelog of every replication server, streamed out bydocker cp, and reports the sequence numbers missing among the changes each replica made during the load. A directory server numbers its changes one after the other (CSNGenerator.newCSN()) and no operation of the load fails, so a gap is a change that reached no replication server - which the data shows only until a later change overwrites it, andmissing-changesnever (Replication: two deletes a directory server made while switching replication servers under load reached no replication server, and missing-changes still reached 0 #1195). A change lost after the changelog, on its way to a directory server, leaves no gap there; the data shows it (Replication: a replication server skipped minutes of one replica's changes for a directory server catching up, and missing-changes still reached 0 #1206). Every changelog must hold changes of every directory server, so that records the script no longer reads do not pass for a changelog without gaps. What it cannot see: a change lost at either end of a replica's changes has no gap after or before it, and a directory server restarted during the load would read as a lost change - itsReplicaOfflineMsgtakes a sequence number that onlyoffline.stateholds, and only the newest one; the run restarts none.The offline checks and the changelogs are checked after failed online checks as well, so that a run says which changes are missing where.
What is left behind (
collect.sh):summary.mdand the job summary - convergence time, how often the monitor was sampled, peakmissing-changesandapproximate-delay(seconds) and the reconnections to a replication server per directory server (msgID 62, whatever made them: a switch the broker decided on, 188 or 213, a connection the replication server closed or that broke; changes made around one were lost in Replication: two deletes a directory server made while switching replication servers under load reached no replication server, and missing-changes still reached 0 #1195), counted also when a run fails before its checks, the changes missing from the changelogs, throughput per client, time per phase; on failurestate.tgzwith every server's container log,logs/,config/andchangelogDb.OUTmust be new or empty: the files of an earlier run would mix into the summary.CI
The registry of a build job is a service of that job and is gone with it, so the image goes over as an artifact:
docker-testholds the test steps ofbuild-dockerandbuild-docker-alpineonce. Expanded per flavor, its steps were byte for byte the old ones (checked with a script); since then two settings changed on purpose: the containers of both flavors run with512m(alpine had1g), and both benchmark artifacts are kept 5 days (3 and 90 before). The PDB vs JE benchmark stays with the default image. The tests, Trivy and the benchmarks now run next to the convergence check instead of after the build in the same job.Three couplings of the jobs, each stated in a comment where it happens:
docker-convergencerow turns the Build run red, and on a push to masterdeploy.ymlthen skips the snapshot deploy and the publishing of the docs.rs534fails until Replication: a replication server skipped minutes of one replica's changes for a directory server catching up, and missing-changes still reached 0 #1206 is fixed andcombineduntil Replication: two deletes a directory server made while switching replication servers under load reached no replication server, and missing-changes still reached 0 #1195 is.docker-testruns when either image was built (as the tests of each image ran in its own build job before): a matrix row cannot need its own build only, so the row of an image that was not built fails at its download.Testing
This round (the review of maximthomas): no CI run yet. Each check it changes was run on stubs and on the artifact of run 38893291531, against the scripts before the round:
ds-sync-conflictsearch failing on one replicaUPDATE_TYPES = {}inchangelog-gaps.py, on the changelog ofcv-r0cv-d0...cv-d3CONVERGED_INis the first of themrmfails in the stop of the sampler-What the round does not change came out the same before and after: the client output of a whole run, the same answers everywhere, a conflict entry, answers that differ, the whole changelog of
cv-r0(green, green, red, red, green). The reconnections in the errors logs of that run, which go on to the shutdown:cv-d28 - each of its 8 switches of #1198 connected it again to the replication server it left -,cv-d11, its reconnection while the servers stopped, the others 0.run.shwith anOUTthat holds a file exits with 2 before it touches Docker. The wait for the end of the joins is the one ofdocker-test-replication.sh, which runs on both images; it has not run here (no Docker on the host).build.yml: actionlint 1.7.7, no findings.CI, run 38893291531 (the sixth round: values of its own for every
modrateclient, the stop of the sampler):docker-test (default)anddocker-test (alpine)green,combinedgreen, converged 212 s after the load.rs534failed rightly, again on a loss on the way to a directory server (#1206):cv-d2kept 12 entries the others had deleted - 7 ofcv-d0, 5 ofcv-d3- while both changelogs held every change; it converged 40 s after the load.cv-d2switched replication servers 8 times (msgID 188), and each switch connected it again to the replication server it left - #1198, fixed by #1205 since, which this branch is now on. Before CI, withstart_clientstubbed, eachmodrateclient got a template of its own (description:cv-d0-%2$s,description:cv-d1-%2$s, ...), whichString.format- as ModRate calls it - turns intodescription:cv-d0-AbCdEfGhIjKlMnOp; stopping the sampler in the middle of a sample (a stubbedadmin_searchthat takes 3 s) left itssample.*directory behind before that round and left nothing after it.CI, run 38865878001 (the fifth round: every replication server asked at once, the switch count, the changelog check): every job that finished was green but
docker-convergence (rs534)(thedocker-testjobs were still running when this round was pushed).rs534failed rightly, on a loss of another kind than #1195: a replication server skipped minutes of the changes of one directory server for another that was catching up - about 70 thousand changes ofcv-d2never reachedcv-d3, about 24 thousand ofcv-d3never reachedcv-d2- with nothing logged andmissing-changesat 0 (converged 348 s after the load), now #1206. The changelog check found no gap in the changelog of either replication server - every change was there, the loss was on the way out - and none incombined, so it raised no false alarm on a complete changelog. The sampler took 22 samples in 900 s onrs534(13 in the fourth round) and counted 10 switches ofcv-d1(the pattern of #1198), none elsewhere. Two things this round changes came out of it: which server made a stale value took the changelog to tell, since everymodrateclient wrote the same values, and asample.*directory the stop of the sampler had cut short was uploaded with the run. Sodocker-convergence (rs534)can fail until #1206 is fixed, ascombineduntil #1195.The fifth round, before CI:
changelog-gaps.py, the switch count and the summary were run on the state of the failedcombinedrun of #1195 (run 38605759421 attempt 1, artifactconvergence-combined) withdockerstubbed: the replica ofcv-c3misses 81 changes in all four changelogs - the number the analysis of #1195 found independently - the other three replicas none, also over the whole history including the setup of the topology;cv-c3switched replication servers once. Asking the replication servers at once took one ask's time for four of them. A whole local run was not possible (no Docker on the host at the time).CI, run 38755406531 (the fourth round, 325 modifications and 65 adds or deletes a second for 900 s, heaps of 1 GB and 256 MB): every job green. The clients reached 92-98% of their ceilings, about 1500 writes a second over the topology.
rs534converged 269 s after the load:cv-d1andcv-d2fell behind steadily, about 300 thousand changes and 320 s at the end of the load, and caught up.combinedconverged 83 s after the load. The sampler took 13 samples in 900 s there, one ask after the other, which this round changes.CI, run 38694855431 (the third round, 325 modifications and 65 adds or deletes a second for 900 s, no bound on any heap): both
docker-convergencerunners lost communication with GitHub in the middle of the run, with no log and no artifact left. Fourteen JVMs that may each take a quarter of a 16 GB runner are the likely cause, so the heaps are bounded again.docker-test (default)failed inDocker test replication(dj-r1 did not join through dj-r0, the joins on empty volumes of #1178), which this PR does not touch.CI, run 38605759421 (the second round, at 250 modifications and 50 adds or deletes a second for 300 s): the clients reached their ceilings, about 1180 writes a second over the topology.
rs534passed.combinedfailed in attempt 1, and rightly: two deletescv-c3made while its directory server switched replication servers reached no replication server, the other replicas kept both entries, andmissing-changesreached 0 all the same - #1195. Attempt 2 of the same job passed, with peaks ofmissing-changesup to 58078 and ofapproximate-delayup to 105 s: the loss depends on a delete landing in the window of a switch. Sodocker-convergence (combined)can fail until #1195 is fixed.CI, run 38528537312 (the first round of this PR, before the load moved to plain LDAP on open connections): every job green.
docker-convergencetook 14 minutes for each topology, next todocker-test(103 and 117 minutes).rs534converged 22 s after the load,combined75 s; the clients reached 10-30 operations a second each, since each operation opened an LDAPS connection and bound first, which the second round removed.Locally, Docker 29.8.2, 4 CPU / 4 GB, image built from this branch:
combinedandrs534with the load for 120 s: green;combinedfor 300 s: green, converged 83 s after the load,ldifdifffound no attribute that differs beyond the two excluded.combinedwith the load of this round (plain LDAP, open connections, writes only) for 60 s: green, converged 158 s after the load.INJECT_DIVERGENCE=1(one change and one entry on one replica only, through the replication repair control): red on the indexed searches and onldifdiff, whose output is exactly the injected change.SYNCerror in an errors log, a stopped replication server (the others see 2 of 3 peers), a directory server whose monitor entry is gone (the wait times out and prints the monitor), a load client that reaches no server or binds with a wrong password.0.5as 0 with a decimal comma; the run setsLC_ALL=C(red on a saved client output before, green after).build.yml: actionlint 1.7.7, no findings.Not done:
rs534with the load for 300 s locally. The first run failed on a load client that printed nothing (the run now records each client's exit code and OOM kill); the second was stopped for lack of memory on the host. On 4 CPU / 4 GB with the host itself busy, the servers get a few operations a second from each client, so the local runs check the logic, not the throughput. Once, locally, a combined server did not turn healthy within an hour while the topology came up; its logs were lost to the cleanup, whichKEEP=1andstate.tgznow prevent.