Skip to content

[Epic] Sleeping-disk strategy: cache metadata + power-state flag + 503-retry + partial results #78

Open

Description

@ctengel

Coordinating issue for the "sleeping HDD" class of problems. Lengthening timeouts (#72) has been the tactical stopgap, but it's the wrong permanent architecture: a blanket long timeout makes every hard-down node cost ~16s on every request, ties up locator tasks / the oneshot replicator queue, and still doesn't bound tail latency. This epic lays out the strategic direction and the order to build it.

Root cause: two failure modes the design currently conflates

Every linked issue stems from treating these as the same event:

  • asleep-but-healthy — answers in ~15s, has real data. We want to wait for it / retry it.
  • hard-down — never answers. We want to skip it instantly.

A short timeout calls the sleepy node "down" (→ false 404 #75, duplicate write #76, spurious 507 / aborted replication #77). A long timeout calls the down node "sleepy" (→ pay 16s for nothing). No single timeout is correct, because the timeout is being asked a question it structurally can't answer. The strategy is to (a) remove the question for most traffic and (b) answer it directly for the rest.

Strategic layers (not mutually exclusive)

1. Cache metadata in RAM on the object server — root-cause fix, primary lever (#1, #3)

The traffic causing the whole class is metadata (existence, listings, checksums, health), which is small and changes only on write. The object server is the sole writer of its own filesystem, so it can authoritatively cache its own metadata in RAM and answer HEAD/existence/listing without touching the platter. Only actual object-byte GET/PUT then needs the disk — and those already carry long timeouts where a 15s spin-up is negligible against a multi-GB transfer.

This serves the actual goal (we want disks asleep to save power): timeouts/retries keep poking disks; caching lets them stay asleep and keeps metadata fast.

Elegant fit with the existing contract: <bucket>.sha256 is already an on-disk metadata index, and the checksum-append is the commit marker, so the committed-object set is exactly the parsed .sha256 files. "Cache metadata" largely means load the .sha256 files into RAM at startup and update on each PUT-commit. Disk stays source of truth; the cache is a derived in-memory index, rebuilt on restart (scrub already walks the dir). Keeps the "pure filesystem state, no database" design goal intact — no new persistent state.

Payoff: once a healthy node always answers metadata fast, a slow metadata response genuinely means down → metadata timeouts can shrink back to short and fast-fail correctly. This dissolves #72/#75/#76/#77 rather than papering over them.

2. Disk power-state health flag — cheap complement (second priority)

Object-server /health reports power state (awake / asleep / recently-spun). Lets the locator (a) deliberately prefer an awake replica for data GET/PUT instead of relying on the current 1/3-SSD luck, and (b) distinguish asleep-vs-down so 503-vs-skip decisions are honest. Caveat: querying power state (e.g. hdparm -C) must not itself wake the disk — solvable but platform-dependent.

3. Respect 503 + Retry-After on the data path — the right contract for the residual

For object-byte transfer the disk must wake. A short locator timeout + honest 503 Retry-After ("a copy exists, disk spinning up, retry in N") beats a long blind wait: the client gets an explicit signal and the locator task isn't pinned. Only safe combined with (1)/(2) — otherwise a hard-down node makes clients retry forever, and metadata that should be cached-instant would 503. Requires a bounded, shared retry helper in the client lib + replicator.

4. Partial results for GET /{bucket}/ — endpoint-specific resilience (#63)

One slow/down server shouldn't sink the whole listing. The response already carries per-object locations/error; extend it to flag which servers were unreachable/stale. Independent availability win; pairs with the flag in (2).

5. Long timeouts — demote to backstop, not strategy

Keep only as (a) the transitional tactic already shipped in #72 and (b) the floor for the in-flight transfer itself (#49). Walk the metadata timeouts back down once (1) lands.

Recommended sequencing (by leverage)

  1. Object-server metadata cache (from .sha256 + dir; update on checksum-append commit; rebuild on start). Then revert the tactical metadata timeouts to short.
  2. Disk power-state health flag.
  3. Formalize 503-Retry-After on the data path + bounded shared retry helper (client + replicator). Safe once (2) prevents retry-forever.
  4. Partial results for GET /{bucket}/ (GET /{bucket}/: optional override to return partial results when a server is down #63).

Net end state: metadata is fast and fast-failing (no spin-up in the hot path), the data path waits only when there's genuinely data worth waiting for and says so explicitly, and a single down node degrades instead of failing the request. Long timeouts stop being load-bearing.

Risks / open questions

  • Cache memory footprint per disk — bounded by the .sha256 index it derives from (name+size+digest), but verify on target hardware with the largest expected object counts.
  • Write-path cache coherence — easy in principle (sole writer; hook the existing checksum-append commit point), but needs care around partial/failed PUTs and the scrub/rebuild path.
  • "Check power state without waking the disk" — confirm the chosen mechanism (hdparm -C or a last-IO heuristic) genuinely doesn't spin the platter.
  • Whether the locator should also cache (server up/down, recent listings) layered on top of object-server caches.

Linked issues

Activity

  1. added this to the Mid-term milestone on May 29, 2026
  2. added a commit that references this issue on May 29, 2026
  3. ctengel commented on Jun 1, 2026

    @ctengel
    OwnerAuthor

    Is there a case to be made for the OS/fs just caching the metadata (directory listing and sha256 contents)?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions