Repository navigation
Conversation
…s recovery The cluster runs three nodes on per-node `file` PVCs, which is exactly the shared-nothing case `storage.cluster.private_backend` exists for, but nothing set it (oteldb/oteldb#1262). Set `spec.cluster.privateBackend` and assert the mode is really in effect via each node's admin API rather than trusting the manifest: a CRD prunes fields it does not know, and the misconfigured shape is invisible from the outside. Measured with the flag off, each flushed part exists in exactly one copy at replicationFactor 2 and a node that loses its PVC never recovers — it rediscovers the same missing shard once per maintenance tick forever. With it on, flushed parts land on RF nodes and a wiped node mirrors its shards back from a peer within a tick or two. The disk-loss spec now asserts that recovery instead of only self-healing, and the README's durability note is corrected. Blocked on oteldb/operator#16: the CRD has no privateBackend field yet, and `storage.cluster` is a reserved extraConfig path, so there is no way to express this through the operator today.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The suite deploys three nodes on per-node
filePVCs — the shared-nothing casestorage.cluster.private_backendexists for — and nothing set it. That is oteldb/oteldb#1262reproduced by the suite that is supposed to catch it. The oteldb side landed in oteldb/oteldb#1264;
this wires it into the cluster under test and, more importantly, re-runs the disk-loss experiment
to find out what the flag actually changes.
The empirical answer
The README claimed, as a durability note, that a node returning under the same identity with an
empty disk "is not auto-restored from replicas within the query window", and the disk-loss spec
asserted only self-healing plus availability.
That note is wrong once
private_backendis on, and the mechanism it implied was never thereason. Measured on kind (3 nodes,
backend: file,replicationFactor: 2,flushInterval: 10s),wiping a node's PVC and letting the StatefulSet return it:
The node had all 9 of its part objects back roughly 20s — two maintenance ticks — after becoming
Ready, and every record ingested before the wipe stayed queryable throughout. The recovery is
bootstrapGainedTenantsfinding a shard the node owns but has no engine for,cluster/partsyncmirroring that shard's backend objects from a peer, and the engine loading them. There is no
query-window wait involved: the pass runs on the engine's maintenance tick, whose cadence is
spec.engine.flushInterval.Without the flag, in the same cluster, it never recovers — not "not within the query window",
but structurally never:
bootstrapShardcallssyncParts, which no-ops for a backend the node believes its peers canalready read, so no bucket index appears locally, so no engine is created, so
hasAnyEnginestaysfalse and the next tick rediscovers the same shard. Observed for 3 minutes; the loop has no exit.
The more alarming finding is upstream of the wipe. With the flag off, flushed parts are on one
node each despite
replicationFactor: 2— the ring replicates the head, and only partsyncreplicates parts:
With the flag on, each flushed part lands on
replicationFactornodes, as intended.None of this is visible from any API the cluster exposes: writes still reach every replica through
the primary-authoritative path and every query is answered from whichever owner has the data. So
the suite now asserts the mode directly.
Changes
manifests/cluster.yamlsetsspec.cluster.privateBackend: true.internal/nodeadmin— client for a node's admin API (/api/v1/storage, port 8090). The operatordoes not publish that listener on the Service, which suits these specs: "did this node flush a
part, did this node mirror a peer's" are per-node questions a Service-level view hides behind
the read fan-out.
durability_test.goasserts (a) every node reports part-sync stats, which only a node inshared-nothing mode does, and (b) a flushed part reaches two nodes. (a) is the one that fails
loudly if the operator drops the field — see the blocker below — rather than silently testing a
shared-store cluster that is not one.
wipes a node holding one, then asserts that node mirrors parts back, holds them again, and that
the pre-disruption telemetry is still queryable.
Blocker
oteldb/operatorhas nospec.cluster.privateBackend, andstorage.clusteris a reservedextraConfigpath, so there is currently no way to express this through the CR at all — filedas oteldb/operator#16 with a two-hunk patch sketch. Against
oteldb/operator@mainthe run stops atkubectl applywithstrict decoding error: unknown field "spec.cluster.privateBackend", which iswhat CI on this PR shows. Draft until that lands.
durability_test.gois the backstop for thequieter failure mode — an operator that accepts the field and drops it.
Testing
make test-keepagainst a 4-node kind cluster, with an operator carrying the proposedprivateBackendfield: 7/7 specs pass. Run twice back to back — once against a cold cluster (freshetcd and PVCs) and once warm — since an earlier iteration of the flushed-part helper waited for a
part count to grow, which a merge folding parts back into one makes unreachable on a warm cluster.
The helper now waits on a state, not a change.
Refs oteldb/oteldb#1262, oteldb/oteldb#1264, oteldb/operator#16.