backup: probe etcd at task start and bound every etcd read - #1218
Conversation
A backup run with --backup_index_extra could hang forever on its first etcd read, with nothing in the log beyond "start to get index info from etcd". Three things had to be true at once, and all three were. clientv3.New does not dial unless the config carries grpc.WithBlock(), so the DialTimeout the backup passed was never exercised: construction succeeded against endpoints that do not exist, and the failure was deferred to the first RPC. That first RPC took the task context, which carries no deadline, so an endpoint that accepts the connection and never answers blocked the Get until the process was killed. And both etcd steps -- index extra information and the dynamic field schemas -- run after every collection has been copied, so the hang landed at the end of a backup that had otherwise finished. etcdMeta now owns the two reads. It bounds each one with a deadline, defaulting to 10s to match the etcd.requestTimeout Milvus bounds its own etcd calls with, and it carries the endpoints it was built from so a timeout names the address that did not answer -- clientv3 reports only "context deadline exceeded", which is not actionable without a packet capture. Its probe runs in initClients, and only when BackupIndexExtra is on, since that is the only case that builds an etcd client at all: a cluster whose etcd this tool cannot reach now fails in the first seconds instead of after the data has been copied. The probe is an explicit bounded Status rather than a blocking dial. A dial only proves the TCP connection, and the reported failure is an endpoint that accepts the connection and then does not speak etcd, which a blocking dial would pass; grpc.WithBlock is also deprecated. One reachable member is enough, so a single down member of an otherwise healthy cluster does not fail a backup, and the error names every endpoint that was tried. etcd TLS remains unconfigurable; that half of zilliztech#1216 is left alone. Related to zilliztech#1216 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: czs007 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Codecov Report❌ Patch coverage is ❌ Your patch status has failed because the patch coverage (77.17%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #1218 +/- ##
==========================================
+ Coverage 46.89% 47.26% +0.36%
==========================================
Files 140 141 +1
Lines 12845 12877 +32
==========================================
+ Hits 6024 6086 +62
+ Misses 6386 6354 -32
- Partials 435 437 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The dynamic field task read every field of every collection in the instance:
prefix := fmt.Sprintf("%s/meta/root-coord/fields/", cdft.etcdRootPath)
and then threw nearly all of it away, keeping at most one dynamic field per
backed-up collection. The index extra scan was scoped this way in zilliztech#1055; this
one was not.
The size of that read tracks the instance, not the backup, so a bounded read
turns a large enough instance into a failed backup rather than a slow one --
which is a regression the deadline added alongside it would otherwise
introduce. The key is "root-coord/fields/{collectionID}/{fieldID}", so the
collection id is already the first segment and one read per backed-up
collection asks for exactly what the task uses.
parseCollIDFromFieldKey goes with it: the collection id is the loop variable
now, so there is nothing left to parse out of the key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
eee70d7 to
beed9fa
Compare
beed9fa to
0265e75
Compare
issue: #1216
A backup run with
--backup_index_extracan hang forever on its first etcd read,silently, after every collection has already been copied. Three things combine:
the client is created without ever dialling, the reads carry no deadline, and both
etcd steps run last, so an unreachable etcd is only discovered at the end of the
backup.
Reported on a deployment where all 69 collections and the RBAC metadata were
written, and the run then stopped dead on
with nothing after it in a 4.9 MB log.
What changed
core/backup/etcd.go(new):etcdMetawraps the client with the endpoints itwas built from, because clientv3 never reports which address a call was talking to
and a bare
context deadline exceededgives an operator nothing to act on.probeestablishes that etcd answers before the backup starts copying. Onereachable member is enough — the client fails over, so a single down member of an
otherwise healthy cluster must not fail a backup. An unreachable cluster reports
every endpoint with its own error.
getPrefixbounds the read with a deadline: 10s, matchingetcd.requestTimeout,the knob Milvus bounds its own etcd calls with. There is no reason for this tool
to wait longer than the server it reads from.
core/backup/task.go: the probe runs ininitClients, inside the existingif t.option.BackupIndexExtrabranch — the only case that builds an etcd client atall — so a cluster whose etcd this tool cannot reach fails in the first seconds
rather than after the data is copied.
core/backup/coll_index_extra_task.go,core/backup/coll_dyn_field_task.go:read through
etcdMetainstead of callingkv.Getwith the unbounded task context.The dynamic field task's scan is also scoped to the backed-up collections: the key
is
root-coord/fields/{collectionID}/{fieldID}, so the collection id is already thefirst segment, and the previous cluster-wide read fetched every field of every
collection in order to keep at most one dynamic field per backed-up collection. The
index extra scan was scoped this way in #1055; this one was not, and its size
tracked the instance rather than the backup — which is what makes a bounded read
safe here.
Why a probe rather than a blocking dial
clientv3.Newdoes not dial unless the config carriesgrpc.WithBlock(), soconstruction succeeds against endpoints that do not exist. Adding
WithBlockwouldnot be enough: it proves the TCP connection, not that the peer speaks etcd, and an
address that accepts the connection and then never answers is exactly the reported
failure.
WithBlockis also deprecated. An explicitStatussettles both anddoubles as the start-of-task check.
Tests
core/backup/etcd_test.gocovers the probe: reachable, one member down, allunreachable naming every endpoint, a silent endpoint that must not block, and no
endpoint configured.
TestEtcdMeta_ProbeWithRealClientpins the premise of the fixwith a real
clientv3client —clientv3.Newagainst an address nothing listens onreturns no error, and only the probe reports it. The two task tests assert that an
unreachable etcd fails their
Executewith the endpoint in the message, and thateach scan asks only for the collections in the backup.
Not in this change
etcd TLS.
MilvusEtcdConfighas onlyEndpointsandRootPath, so a deploymentwhose etcd requires client certificates cannot be read by this tool at all; Milvus
itself supports
etcd.ssl.enabledwithtlsCert/tlsKey/tlsCACert. That isthe fourth defect in #1216 and is left open, which is why this says
issueratherthan
fixes.🤖 Generated with Claude Code