Fleet version: 4.79.1
Web browser and operating system: N/A
💥 Actual behavior
When Fleet has been configured with both a Writer and Read replicas, during an AWS Aurora Failover event, Fleet does not appear to be detecting the new Writer and re-establishing connections.
Started cross AZ failover to DB instance: <db_instance_name>
Completed RDS initiated failover to DB instance: <db_instance_name>
When Fleet does not detect the event through /healthz check failures, this results in Fleet staying attached to the (old) writer when it's been demoted to a reader.
During the time any requests triggering a write transaction appear to fail. It was reported that:
- Fleet logins did not work
- My Device page access did not work
We did observe a /healthz failure, 1 for each task in the ECS cluster, during 2 separate failover events:
{"component":"healthz","err":"invalid connection","health-checker":"mysql","ts":"<timestamp>"}
{"component":"healthz","err":"dial tcp 10.10.21.118:3306: connect: connection refused","health-checker":"mysql","ts":"<timestamp>"}
After the failover Fleet generates the following logs when write transactions are executed against the reader (and not limited to):
{"err":"MarkHostsSeen transaction: exec update: Error 1792 (25006): Cannot execute statement in a READ ONLY transaction.","level":"info","msg":"failed to update host seen times","ts":"<timestamp>"}
{"component":"http","err":"error in query ingestion || error in query ingestion || error in query ingestion || error in query ingestion || insert label query executions (<redacted>): Error 1792 (25006): Cannot execute statement in a READ ONLY transaction. || updating hosts policy updated at: Error 1792 (25006): Cannot execute statement in a READ ONLY transaction. || save host with id <id>: Error 1792 (25006): Cannot execute statement in a READ ONLY transaction.","host_id":<id>,"ingestion-err":"ingesting query users: update host users: insert users: Error 1792 (25006): Cannot execute statement in a READ ONLY transaction.",<redacted>}
🛠️ To fix
To force Fleet to establish new connections to the correct Writer and Read replicas, we restart all the Fleet tasks when the issue is detected.
A few things that may help address the issue (link to slack 🧵) with no human intervention.
- report a failure with
/healthz (and monitoring can catch and trigger a Fleet restart)
- panic and have fleet exit, triggering task restarts
- Detect and handle the errors, triggering Fleet to re-lookup the host and re-connect/re-establish connections to Aurora MySQL reader and writers
Modify HealthCheck() to attempt a write operation.
Not sure what a minimal write operation will work. SELECT @@read_only or attempt a dummy insert and rollback should do the trick.
Detect error 1792 and panic/exit
func isReadOnlyTransactionError(err error) bool {
err = ctxerr.Cause(err)
var mySQLErr *mysql.MySQLError
if errors.As(err, &mySQLErr) {
return mySQLErr.Number == ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
}
return false
}
🧑💻 Steps to reproduce
I have not reproduced the issue, but I believe this can be reproduced by inducing a DB failover event. We can try to reproduce by promoting a database reader to a writer, which should lead to a similar outcome.
Victor's proposed repro steps:
- Deploy Fleet with Aurora MySQL (writer + reader replica configured)
- Configure ECS/load balancer health checks to hit /healthz
- Trigger Aurora failover:
aws rds failover-db-cluster --db-cluster-identifier <cluster-id>
- Observe Fleet logs for Error 1792 messages
- Confirm /healthz still returns 200 (the bug)
🕯️ More info (optional)
Related help-p1 alerts:
- https://fleetdm.slack.com/archives/C03EG80BM2A/p1770058093332579
- https://fleetdm.slack.com/archives/C03EG80BM2A/p1770075439043209
Additional context:
- https://fleetdm.slack.com/archives/C062D0THVV1/p1770076322006369
QA
Note: the server's behavior during failover is not deterministic. Only the previous DB connections are "bad". Any new DB connections created after the failover work fine. This means the healthz endpoint may not deterministically return a failing status.
1. Deploy your branch to the loadtest environment
2. Identify the Aurora cluster
Find the Aurora cluster name:
aws rds describe-db-clusters \
--query "DBClusters[?contains(DBClusterIdentifier, '<workspace-name>')].DBClusterIdentifier" \
--output text
Verify it has 2 instances (one writer, one reader):
aws rds describe-db-clusters \
--db-cluster-identifier <cluster-id> \
--query "DBClusters[0].DBClusterMembers[*].{Instance:DBInstanceIdentifier,IsWriter:IsClusterWriter}" \
--output table
3. Start write traffic
Create a script (or use the template below) that sends continuous write requests to Fleet. This is necessary because failover is only detectable when the application attempts a write operation against the now-read-only former primary.
Save as tmp/failover-test.sh:
#!/bin/bash
# Generate continuous write traffic against Fleet to exercise the read-only
# detection during an Aurora failover.
URL="https://fleet-<workspace-name>.loadtest.fleetdm.com"
TOKEN="<your-api-token>"
AUTH="Authorization: Bearer $TOKEN"
echo "=== Starting write traffic at $(date) ==="
echo "Sending PATCH /api/v1/fleet/config every 0.5s..."
echo "Watch for non-200 responses indicating read-only errors."
echo ""
i=0
while true; do
i=$((i + 1))
status=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 \
-X PATCH "$URL/api/v1/fleet/config" \
-H "$AUTH" \
-H 'Content-Type: application/json' \
-d "{\"org_info\":{\"org_name\":\"FailoverTest-$i\"}}")
ts=$(date +%H:%M:%S)
if [ "$status" != "200" ]; then
echo "$ts [#$i] status=$status <--- NON-200"
else
echo "$ts [#$i] status=$status"
fi
sleep 0.5
done
Run it:
chmod +x tmp/failover-test.sh
bash tmp/failover-test.sh
You should see a stream of status=200 responses.
4. Trigger the failover
In a separate terminal, initiate an Aurora failover:
aws rds failover-db-cluster --db-cluster-identifier <cluster-id>
This takes about 15-30 seconds for Aurora to complete. During this window:
- The current primary becomes read-only
- The reader is promoted to the new primary
- The cluster endpoint DNS updates to point to the new primary
5. Observe the results
In the write traffic output
You should see a pattern like:
- All 200s - normal operation before failover
- 401s and 502s - Fleet is returning errors because DB writes are failing (401s happen because
MarkSessionAccessed fails, invalidating the session)
- 000s - connection timeouts during the transition
- All 200s again - recovery after ECS replaces the tasks
In CloudWatch logs
Open the CloudWatch log group fleet-<workspace-name> in the AWS console (us-east-2 region).
Search for the key log message:
"fatal database error detected, initiating graceful shutdown"
This confirms that Fleet detected the read-only error and triggered a graceful shutdown. The sequence should be:
- MySQL read-only errors appear (error 1792, 1290, or 1836)
- The "fatal database error detected" message appears
- The server begins graceful shutdown
- ECS detects the stopped task and launches a replacement
In ECS console
Check the ECS service events for the cluster fleet-<workspace-name>. You should see:
- Tasks being deregistered from the target group
- New tasks being launched
- New tasks passing health checks and being registered
6. Expected timeline
| Time |
Event |
| T+0s |
Failover initiated |
| T+15-30s |
First read-only errors hit Fleet |
| T+15-35s |
TriggerFatalError fires, graceful shutdown begins |
| T+30-60s |
ECS deregisters old tasks, starts replacements |
| T+60-120s |
New tasks healthy, all requests returning 200 |
Total disruption window: approximately 1-2 minutes.
Fleet version: 4.79.1
Web browser and operating system: N/A
💥 Actual behavior
When Fleet has been configured with both a Writer and Read replicas, during an AWS Aurora Failover event, Fleet does not appear to be detecting the new Writer and re-establishing connections.
When Fleet does not detect the event through
/healthzcheck failures, this results in Fleet staying attached to the (old) writer when it's been demoted to a reader.During the time any requests triggering a write transaction appear to fail. It was reported that:
We did observe a
/healthzfailure, 1 for each task in the ECS cluster, during 2 separate failover events:After the failover Fleet generates the following logs when write transactions are executed against the reader (and not limited to):
🛠️ To fix
To force Fleet to establish new connections to the correct Writer and Read replicas, we restart all the Fleet tasks when the issue is detected.
A few things that may help address the issue (link to slack 🧵) with no human intervention.
/healthz(and monitoring can catch and trigger a Fleet restart)Modify
HealthCheck()to attempt a write operation.Not sure what a minimal write operation will work.
SELECT @@read_onlyor attempt a dummy insert and rollback should do the trick.Detect error 1792 and panic/exit
🧑💻 Steps to reproduce
I have not reproduced the issue, but I believe this can be reproduced by inducing a DB failover event. We can try to reproduce by promoting a database reader to a writer, which should lead to a similar outcome.
Victor's proposed repro steps:
aws rds failover-db-cluster --db-cluster-identifier <cluster-id>🕯️ More info (optional)
Related help-p1 alerts:
Additional context:
QA
Note: the server's behavior during failover is not deterministic. Only the previous DB connections are "bad". Any new DB connections created after the failover work fine. This means the healthz endpoint may not deterministically return a failing status.
1. Deploy your branch to the loadtest environment
2. Identify the Aurora cluster
Find the Aurora cluster name:
aws rds describe-db-clusters \ --query "DBClusters[?contains(DBClusterIdentifier, '<workspace-name>')].DBClusterIdentifier" \ --output textVerify it has 2 instances (one writer, one reader):
3. Start write traffic
Create a script (or use the template below) that sends continuous write requests to Fleet. This is necessary because failover is only detectable when the application attempts a write operation against the now-read-only former primary.
Save as
tmp/failover-test.sh:Run it:
You should see a stream of
status=200responses.4. Trigger the failover
In a separate terminal, initiate an Aurora failover:
This takes about 15-30 seconds for Aurora to complete. During this window:
5. Observe the results
In the write traffic output
You should see a pattern like:
MarkSessionAccessedfails, invalidating the session)In CloudWatch logs
Open the CloudWatch log group
fleet-<workspace-name>in the AWS console (us-east-2 region).Search for the key log message:
This confirms that Fleet detected the read-only error and triggered a graceful shutdown. The sequence should be:
In ECS console
Check the ECS service events for the cluster
fleet-<workspace-name>. You should see:6. Expected timeline
TriggerFatalErrorfires, graceful shutdown beginsTotal disruption window: approximately 1-2 minutes.