Skip to content

fix(replication): prevent excessive wait times after deploy (fixes #131) - #137

Open
renecannao wants to merge 7 commits into
masterfrom
fix/131-replica-ready-racing-fix
Open

fix(replication): prevent excessive wait times after deploy (fixes #131)#137
renecannao wants to merge 7 commits into
masterfrom
fix/131-replica-ready-racing-fix

Conversation

@renecannao

@renecannao renecannao commented Jul 29, 2026

Copy link
Copy Markdown

After dbdeployer deploy replication in v2.4.0, the deployment takes ~5 minutes instead of seconds. This PR fixes two independent sources of excessive delay:

Root causes

1. wait_until_wsrep_ready spins 120s per node on non-Galera MySQL

Commit a2caf94 added wait_wsrep_after_start (to fix PXC/Galera flakes) but it runs unconditionally for every sandbox node. On vanilla MySQL, SHOW STATUS LIKE 'wsrep_ready' returns empty, grep -qi 'ON' fails, and the function waits the full 60 attempts × 2s = 120s per node. With master + 1 slave that is 4 minutes of unnecessary delay.

2. wait_until_replica_ready default max_attempts=60

The #131 fix (4adea12) polled replicas with max_attempts=60, sleep_sec=1. The original reporter found 5s was sufficient. Combined with the wsrep delay, this pushed the total to ~5min.

Fixes

File Change
sb_include.gotxt wait_until_wsrep_ready: check if wsrep_ready status variable exists before polling; return immediately on non-Galera
sb_include.gotxt wait_until_replica_ready: default max_attempts 60→20
init_slaves.gotxt Call with 20 1 instead of 60 1
init_slaves_84.gotxt Same

Tests added

  • TestWaitWsrepAfterStart_DoesNotBlockNonGalera — verifies wait_wsrep_after_start template renders and uses || true
  • TestSbInclude_WaitUntilWsrepReady_DetectsNonGalera — verifies the wsrep-existence guard exists in sb_include
  • TestSbInclude_WaitUntilReplicaReady_BoundedWait — verifies max_attempts default is 20 (≤30s budget)
  • TestInitSlavesTemplates_IncludeReplicaReadyWait — enhanced to check timeout warning message; fixed pre-existing missing template fields

Fixes #131

Summary by CodeRabbit

  • Bug Fixes

    • Reduced replica readiness polling to a shorter, bounded retry window, speeding up initialization when replicas lag.
    • Improved readiness detection for non-Galera setups, avoiding unnecessary waits.
    • Adjusted wsrep readiness checks to complete gracefully on unsupported configurations.
  • Tests / CI

    • Expanded coverage for replication startup and readiness behavior.
    • Added time-bounded deployment checks to detect readiness and startup regressions.

Two issues caused multi-minute delays on 'deploy replication' in v2.4.0:

1. wait_until_wsrep_ready spun 120s per node on non-Galera MySQL
   (commit a2caf94 added wait_wsrep_after_start but didn't guard
   against vanilla MySQL where wsrep_ready does not exist)

2. wait_until_replica_ready defaulted to max_attempts=60 (60s per
   replica), while the original reporter found 5s was sufficient

Fixes:
- wait_until_wsrep_ready: check if the wsrep_ready status variable
  exists before polling; return immediately on non-Galera nodes
- wait_until_replica_ready: reduce default max_attempts from 60 to
  20 (≤20s total wait per replica)
- Update call sites in init_slaves.gotxt and init_slaves_84.gotxt

Tests:
- TestInitSlavesTemplates_IncludeReplicaReadyWait: also check
  timeout warning message; fix pre-existing missing template fields
- TestWaitWsrepAfterStart_DoesNotBlockNonGalera: verifies the
  wait_wsrep_after_start template renders and uses || true
- TestSbInclude_WaitUntilWsrepReady_DetectsNonGalera: verifies
  the wsrep existence guard exists in sb_include
- TestSbInclude_WaitUntilReplicaReady_BoundedWait: verifies
  max_attempts default is 20 (≤30s budget)
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Replica readiness polling is shortened from 60 to 20 attempts across replication templates. Single-instance wsrep readiness now exits early when the status variable is unavailable. New integration coverage measures replication deployment time and verifies immediate master and replica readiness.

Changes

Replica readiness and template validation

Layer / File(s) Summary
Readiness polling templates
sandbox/templates/replication/*.gotxt, sandbox/templates/single/*.gotxt
Replication startup uses 20-attempt retry windows. Wsrep readiness uses root socket commands and bypasses polling for non-Galera servers.
Template validation
sandbox/templates_flavor_test.go
Tests verify rendered flavor commands, retry counts, timeout messages, wsrep handling, and bounded wait defaults.

Deployment time regression coverage

Layer / File(s) Summary
Deployment budget test
test/deploy-time-budget.sh
The test deploys replication, checks immediate replica and master query readiness, enforces a configurable time limit, checks logs, and cleans up resources.
Integration workflow wiring
.github/workflows/integration_tests.yml
A MySQL 8.4.4 workflow job runs the deployment budget test with a 45-second limit and performs cleanup.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Poem

A rabbit counts twenty hops,
While wsrep waits—or gently stops.
Replicas answer, masters too,
A timed deployment follows through.
Clean logs glow beneath the moon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: reducing excessive wait times after replication deployment.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/131-replica-ready-racing-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
sandbox/templates_flavor_test.go (1)

236-247: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the template assertions beyond substring presence.

The replica test verifies that each wait command exists, but not that it appears after the corresponding START REPLICA command. The wsrep test similarly checks only the grep token and default value; it would pass even if the guard returned failure—or incorrectly returned success when the probe command failed.

Assert command ordering for each replica and execute the rendered wsrep helper with stubs covering both “no variable” and “probe failed” cases.

Also applies to: 282-287

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sandbox/templates_flavor_test.go` around lines 236 - 247, Strengthen the
rendered-template tests by asserting each wait_until_replica_ready invocation
occurs after its matching START REPLICA command, rather than only checking
presence. Extend the wsrep helper assertions to execute the rendered helper with
stubs for both an unset variable and a failed probe, verifying the guard returns
the expected failure status in each case and does not report success when
probing fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sandbox/templates/single/sb_include.gotxt`:
- Around line 80-82: Update the wsrep status probe around $use_cmd so a failed
SHOW STATUS command is not interpreted as a non-Galera node. Capture and
distinguish the command’s exit status from grep’s no-match result: short-circuit
only when SHOW STATUS succeeds without a wsrep_ready row, and otherwise preserve
the probe failure for continued polling or return it.

---

Nitpick comments:
In `@sandbox/templates_flavor_test.go`:
- Around line 236-247: Strengthen the rendered-template tests by asserting each
wait_until_replica_ready invocation occurs after its matching START REPLICA
command, rather than only checking presence. Extend the wsrep helper assertions
to execute the rendered helper with stubs for both an unset variable and a
failed probe, verifying the guard returns the expected failure status in each
case and does not report success when probing fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c344a9a-45c5-4caa-b885-101b9a887a54

📥 Commits

Reviewing files that changed from the base of the PR and between 373c2ad and c8b8dc8.

📒 Files selected for processing (4)
  • sandbox/templates/replication/init_slaves.gotxt
  • sandbox/templates/replication/init_slaves_84.gotxt
  • sandbox/templates/single/sb_include.gotxt
  • sandbox/templates_flavor_test.go

Comment on lines +80 to +82
if ! $use_cmd -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null | grep -q 'wsrep_ready'; then
return 0
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not treat a failed status probe as “non-Galera”.

When $use_cmd fails, stderr is suppressed and the pipeline produces no matching output, so ! ... | grep enters this branch and returns success. A Galera node that is temporarily unavailable can therefore skip all readiness polling and proceed before wsrep_ready=ON.

Only short-circuit when SHOW STATUS succeeds and returns no row; otherwise continue polling or return the probe failure.

Suggested fix
+    local wsrep_status
-    if ! $use_cmd -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null | grep -q 'wsrep_ready'; then
-        return 0
+    if wsrep_status=$($use_cmd -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null); then
+        if ! grep -q 'wsrep_ready' <<<"$wsrep_status"; then
+            return 0
+        fi
     fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ! $use_cmd -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null | grep -q 'wsrep_ready'; then
return 0
fi
local wsrep_status
if wsrep_status=$($use_cmd -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null); then
if ! grep -q 'wsrep_ready' <<<"$wsrep_status"; then
return 0
fi
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sandbox/templates/single/sb_include.gotxt` around lines 80 - 82, Update the
wsrep status probe around $use_cmd so a failed SHOW STATUS command is not
interpreted as a non-Galera node. Capture and distinguish the command’s exit
status from grep’s no-match result: short-circuit only when SHOW STATUS succeeds
without a wsrep_ready row, and otherwise preserve the probe failure for
continued polling or return it.

Adds test/deploy-time-budget.sh, a standalone bash test that:
- Deploys replication (1 slave) with real MySQL binaries
- Measures wall-clock time against MAX_DEPLOY_SECONDS (45s)
- Verifies the replica serves queries immediately after deploy
  (catches the #131 race condition where deploy returned before
   replica was ready)
- Checks for server errors in the error log

Adds a deploy-time-budget CI job in integration_tests.yml that:
- Downloads and unpacks MySQL 8.4.4
- Runs the time-budget test with MAX_DEPLOY_SECONDS=45
- Fails if deploy exceeds the budget (catches regressions like
  the 120s wsrep spin or 60s replica wait from v2.4.0)
… -n flag

- sb_include.gotxt: connect as root (no password) via socket for wsrep
  detection guard, so it works before msandbox user exists
- deploy-time-budget.sh: use -n 2 (total nodes, not slaves) for
  replication with 1 slave
wait_until_wsrep_ready now uses root via socket for both detection
and polling, since msandbox user does not exist before load_grants.
This eliminates the 120s wasted polling loop per node on fresh
Galera/PXC nodes while still correctly waiting for wsrep_ready=ON
before grants/DML.
The start/stop/use templates use shell if blocks (not Go template
{{if}}) so both branches' literal strings appear in rendered output.
Tests now verify the correct default AND the presence of the flavor
conditional, rather than asserting the absence of the other flavor's
binary string.
@renecannao
renecannao force-pushed the fix/131-replica-ready-racing-fix branch from 34601c0 to df96a7f Compare July 30, 2026 00:53
- Retry root socket connect before Galera detection so a not-yet-ready
  server is not mistaken for non-Galera (empty query output).
- Resolve client as mariadb/mysql by FLAVOR (CLIENT_BASEDIR then BASEDIR).
- Match wsrep_ready ON specifically; keep a modest connect budget (30s).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
.github/workflows/integration_tests.yml (1)

134-142: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Honor the restored tarball cache.

This step always downloads and overwrites the cached tarball, so the cache provides no time or reliability benefit. Match the existing matrix job’s file-existence guard.

Proposed fix
           mkdir -p /tmp/mysql-tarball
           TARBALL="mysql-8.4.4-linux-glibc2.17-x86_64.tar.xz"
-          curl -L -f -o "/tmp/mysql-tarball/$TARBALL" \
-            "https://dev.mysql.com/get/Downloads/MySQL-8.4/$TARBALL" \
-            || curl -L -f -o "/tmp/mysql-tarball/$TARBALL" \
-              "https://downloads.mysql.com/archives/get/p/23/file/$TARBALL"
+          if [ ! -f "/tmp/mysql-tarball/$TARBALL" ]; then
+            curl -L -f -o "/tmp/mysql-tarball/$TARBALL" \
+              "https://dev.mysql.com/get/Downloads/MySQL-8.4/$TARBALL" \
+              || curl -L -f -o "/tmp/mysql-tarball/$TARBALL" \
+                "https://downloads.mysql.com/archives/get/p/23/file/$TARBALL"
+          fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/integration_tests.yml around lines 134 - 142, Update the
“Download MySQL 8.4.4” step to guard the curl download with the existing tarball
file-existence check used by the matrix job, so an already cached
/tmp/mysql-tarball/$TARBALL is preserved; continue listing the tarball
afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/integration_tests.yml:
- Line 114: Update the actions/checkout@v4 step in the executable-download job
to disable persisted credentials by configuring persist-credentials as false.
Keep the checkout behavior otherwise unchanged.

In `@test/deploy-time-budget.sh`:
- Around line 93-111: Invoke the script’s existing cleanup function immediately
before the `dbdeployer deploy replication` command, ensuring stale `rsandbox_*`
fixtures are removed before deployment while preserving the current
cleanup-on-exit behavior.
- Around line 50-68: Update find_test_version so its missing-directory and
no-versions diagnostics are written to stderr, keeping failed discovery output
out of VERSION. Require the preferred-version branch to select only 8.4+ or any
9.x version, and update the VERSION assignment/call site to branch on
find_test_version’s exit status so failures trigger the existing skip path
instead of continuing with diagnostic text.

---

Nitpick comments:
In @.github/workflows/integration_tests.yml:
- Around line 134-142: Update the “Download MySQL 8.4.4” step to guard the curl
download with the existing tarball file-existence check used by the matrix job,
so an already cached /tmp/mysql-tarball/$TARBALL is preserved; continue listing
the tarball afterward.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c68ab1b-c48b-452b-8c0a-1138302ea084

📥 Commits

Reviewing files that changed from the base of the PR and between c8b8dc8 and df96a7f.

📒 Files selected for processing (5)
  • .github/workflows/integration_tests.yml
  • sandbox/templates/single/sb_include.gotxt
  • sandbox/templates/single/wait_wsrep_after_start.gotxt
  • sandbox/templates_flavor_test.go
  • test/deploy-time-budget.sh

Comment thread .github/workflows/integration_tests.yml
Comment thread test/deploy-time-budget.sh Outdated
Comment thread test/deploy-time-budget.sh
- workflow: persist-credentials: false on the executable-download job
  so a compromised MySQL binary cannot read the job token (artipacked).
- deploy-time-budget.sh: send version-discovery diagnostics to stderr
  and branch on exit status so a failed discovery hits the skip path
  instead of leaking text into VERSION.
- Require MySQL >= 8.4 (was >= 8.0) to match the stated #131 target.
- Clean stale rsandbox_* fixtures before deploying so a leftover
  ready topology cannot make the test pass spuriously.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sandbox/templates/single/sb_include.gotxt`:
- Around line 125-130: Update the wsrep_check query handling near the local
wsrep_check assignment to capture and inspect $mysql_cmd’s exit status before
interpreting empty output. Return 0 only when the query succeeds with no result;
propagate or handle a failed query as an error so transient connection failures
do not bypass the wsrep_ready wait.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b4191f0-dcf9-45cd-97b4-f9538fd72dac

📥 Commits

Reviewing files that changed from the base of the PR and between df96a7f and 829d68f.

📒 Files selected for processing (4)
  • .github/workflows/integration_tests.yml
  • sandbox/templates/single/sb_include.gotxt
  • sandbox/templates_flavor_test.go
  • test/deploy-time-budget.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/integration_tests.yml

Comment on lines +125 to +130
# Connected: empty SHOW STATUS means wsrep_ready does not exist (non-Galera).
local wsrep_check
wsrep_check=$($mysql_cmd --no-defaults -S "$SOCKET_FILE" -u root -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null)
if [ -z "$wsrep_check" ]; then
return 0
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Check the query's exit status before treating empty output as "non-Galera".

Line 127 discards the exit status of $mysql_cmd. If the query fails after a successful connect (transient socket drop, server restart), stdout is empty and line 128 wrongly returns 0, treating a real Galera node as non-Galera and skipping the wsrep_ready wait. This is the same failure pattern already flagged on this file for the previous grep-pipeline version: a command failure is indistinguishable from "no matching row" and is misclassified as success.

Distinguish command failure from an empty-but-successful result.

🐛 Proposed fix to distinguish query failure from a genuinely missing variable
     # Connected: empty SHOW STATUS means wsrep_ready does not exist (non-Galera).
     local wsrep_check
-    wsrep_check=$($mysql_cmd --no-defaults -S "$SOCKET_FILE" -u root -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null)
-    if [ -z "$wsrep_check" ]; then
-        return 0
-    fi
+    if wsrep_check=$($mysql_cmd --no-defaults -S "$SOCKET_FILE" -u root -BN -e "SHOW STATUS LIKE 'wsrep_ready';" 2>/dev/null); then
+        if [ -z "$wsrep_check" ]; then
+            return 0
+        fi
+    else
+        echo "WARNING: wsrep_ready query failed after connect; retrying" >&2
+    fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sandbox/templates/single/sb_include.gotxt` around lines 125 - 130, Update the
wsrep_check query handling near the local wsrep_check assignment to capture and
inspect $mysql_cmd’s exit status before interpreting empty output. Return 0 only
when the query succeeds with no result; propagate or handle a failed query as an
error so transient connection failures do not bypass the wsrep_ready wait.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dbdeployer deploy replication returns before replica ready.

1 participant