Skip to content

fix(umbp): SSD staging no longer fails local loads; a local read holds its bytes until it is done - #762

Merged
TianDi101 merged 27 commits into
mainfrom
fix/umbp-ssd-staging-local-leases
Oct 16, 2026
Merged

TianDi101 merged 27 commits into
mainfrom
fix/umbp-ssd-staging-local-leases

Conversation

@TianDi101

@TianDi101 TianDi101 commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Two fixes found by the DRAM 300 GB → SSD 1.5 TB end-to-end run (InferenceY agentic, DeepSeek-V4-Pro, n06-21), where SSD-backed loads under the sglang UMBP linker crashed the scheduler.

1. A load batch larger than the SSD staging arena failed whole

Symptom: UMBP get failed … success=78/2664, with 2,124 staging arena busy; rolled back N reservations warnings, then sglang's scheduler died (the linker raises on any failed get after a hit).

Cause: the linker asks for ~2,000 keys per call. A resolve either fit the whole batch in staging or failed all of it. Local readers also held staging for the full 3 s read lease after their copy had finished, so the arena had to cover read rate × 3 s.

Fix:

  • A local SsdBackend::BatchResolve stages up to half the arena per call (the first key always fits). The rest comes back kBusy.
  • PoolClient serves local gets and ranged gets in parts (resolve → copy → drop the part's holds) until every key is served.
  • Staging returns as soon as its last hold drops. It is reclaimed by the reaper, and on demand when an allocation finds the arena full.
  • Existence probes use BatchContains, so they stage nothing.

2. A local read's hold could expire mid-copy

In-process readers were protected only by time: the SSD staging lease (3 s) and the DRAM/HBM eviction fence (500 ms). A copy that outlived its lease could have its staging slot reused, or its DRAM page evicted, while still reading it.

Fix: a local read holds its bytes by refcount. ResolvedEntry::hold is a shared_ptr that the reader keeps for as long as it reads. PageBackend slots and SSD staging spans each own one, and neither evicts, frees nor reuses what is still held. There is nothing to release and nothing that expires, so no token, generation or overdue state is needed. Remote readers keep the timed lease (#749 is the remote half and is not fixed here).

API change to review

MediumBackend::BatchResolve gains a 4th parameter ResolveReader reader = kRemote, and ResolvedEntry gains hold (kLocal only). The reader stays a parameter because three behaviours depend on it: no timed lease, partial staging (a remote reader retries the whole batch, so partial staging would livelock it), and #781's extents.

Metrics

  • SSD medium event staging_expired_reclaim: staging spans freed after their lease and holds were gone.
  • mori_umbp_backend_medium_state{tier="SSD",state="staging_pages_peak"}: the arena's highest occupancy since start. staging_pages_in_use is sampled per scrape and misses bursts.
  • kBusy keys are not counted as resolve outcomes. A held-but-unreadable key is status="failed" instead of a miss.

Also

test_peer_pool's FailingEngine now overrides CreatePlanBuilder (#774's interface). Before this, test_peer_pool did not build on main.

Testing

🤖 Generated with Claude Code

TianDi101 and others added 14 commits October 13, 2026 04:59
Eviction is decided in three places (master EvictKey, PageBackend's
masterless round, PeerSsdManager's after-write round) and freed in two
more ways than the medium's Evict (the page round's own copy of it, and
the SPDK daemon's EvictLRU). The master's request also drops the tier it
measured, so the peer frees by placement and can delete copies nobody
asked about.

The doc lays out the target: the master names (key, tier); PeerPool::Evict
is the one routing entry point both the master and a background local
eviction worker call; MediumBackend::Evict is the only code that frees a
committed key; nothing evicts on the put path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rees that copy

The master picks victims per (node, tier) but shipped only keys, so the
peer freed by placement: on a tier without on_evict offload it deleted the
key from every backend, and with copy-mode promotion (key in DRAM and SSD)
SSD pressure freed the DRAM copy and left SSD as full as before.

- EvictionVictim{key, tier}: SelectVictims and EvictKeyDispatcher carry
  the tier each victim was charged to.
- EvictKeyRequest gains `repeated TierType tiers = 2`, parallel to keys.
  An older peer ignores it; an older master sends none and the peer frees
  each key from the medium that currently holds it.
- PeerPool::Evict takes PoolEvictRequest{key, tier, backend_id} and frees
  exactly one copy: demote it when it is the placement and its tier has a
  downstream (any trigger -- on_evict vs watermark now only says whether
  the pool also offloads proactively), otherwise drop that copy only. Drops
  run outside the pool lock; lifecycle_mutex_ keeps ClearLocal out.
- A transition that lands on a parked (draining) copy promotes it to the
  durable one, so a later eviction cannot delete it as still-draining.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rough PeerPool::Evict

Before this, a committed key could be freed four ways: the master's
EvictKey (through PeerPool and the medium's Evict), PageBackend's
masterless round (its own copy of Evict's body, inline on commit),
PeerSsdManager's round (inline after every SSD write, plus
evict-then-retry on a failed write), and tier transitions.  The two
inline rounds bypassed PeerPool -- no on_evict demotion, stale placement,
invisible to InstrumentedBackend -- and the page round ran before the
pool's watermark check, so on a masterless node it deleted the cold keys
the watermark offload was about to move.

Now:
- MediumBackend::Evict is the only code that frees a committed key for
  capacity.  PageBackend::MaybeEvictToLowWatermark and
  PeerSsdManager::EvictToLowWatermark (with its watermarks, after-write
  checks and evict-and-retry) are gone; a write that does not fit fails.
- MediumBackend::EnableLocalEviction is replaced by ordering hooks that
  free nothing: TrackEvictionOrder() and EvictionCandidates(max) ->
  EvictionOffer{key, bytes}, coldest first, skipping leased/pinned keys.
- PeerPool::EnableLocalEviction(backend, high, low) starts one background
  worker.  Commits, NO_SPACE allocations and finished transitions only wake
  it; it takes offers until their bytes cover the overage and calls
  PeerPool::Evict({key, backend_id}, kReclaim) -- the call the master's
  EvictKey makes -- so a tier with a downstream demotes instead of
  deleting.  Lowest tier first, 64 keys per call, with a fixed byte budget
  as well as the live usage check.
- PoolClient enables it for every medium without a master (paged media at
  local_evict_*, SSD at its own watermarks) and for SSD with a master, as
  before.
- A demotion that loses a race to another transition no longer falls back
  to deleting the source, which could abort that transition and lose the
  key.
- mori_umbp_client_local_evict_total{event=round|key|no_candidate|stalled};
  the SSD medium's eviction_round event is gone (rounds are the pool's,
  keys and bytes are InstrumentedBackend's).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With ssd_backend=spdk, PeerSsdManager reaches the device through the
spdk_proxy daemon, whose SpdkSsdTier retried a failed allocation by
dropping its own oldest keys (EvictLRU).  Nothing told PeerSsdManager:
it kept listing those keys, no REMOVE reached the master, and the master
kept routing reads to bytes that were gone.  Fragmentation could trigger
it well below the SSD watermark.

A write that does not fit now fails, as it does on the file tier, and
freeing space is the owner's local eviction, which reaches the tier
through Evict().  PeerSsdManager is the daemon's only client.

Not built by the default dev image (no SPDK); checked with a syntax-only
compile against 3rdparty/spdk/include.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR description carries the design; the two code comments that pointed at
the doc already explain themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same coverage, fewer lines:
- the SSD- and DRAM-scoped evicts of a promoted key share one test, as do
  the stale-tier and backend-scoped evicts;
- the read-recency check rides on the drain test, and the bad-config check
  on the unordered-medium one;
- the mismatched-tier-count case rides on the unscoped EvictKey test;
- the two "writes never evict" cases share one fill;
- dropped VictimCarriesTheTierItWasChargedTo (KeyOverBudgetInTwoTiers checks
  the tier on both victims) and the master-chain SSD-pressure test (the
  parked-source chain test checks the SSD victim end to end, and
  TierScopedEvictFreesOnlyThatMediumsCopy what the pool does with it);
- removed the now-unused Keys() and HasRemove() helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folds #742 into this PR.

- Master: a round frees each (node, tier)'s overage in bytes, optionally
  capped by UMBP_EVICT_MAX_BYTES_PER_ROUND, instead of 32 candidates per
  bucket -- which freed a few MB per round and took hours to drain a
  512 GiB tier's 0.9 -> 0.7 band.  The store returns every eligible row and
  the strategy trims to the budget.
- Peer: PeerPool::Evict queues a demotion on the transition worker instead
  of running the copy inside the EvictKey RPC, so the call never waits on
  bytes.  Each result carries an outcome (freed, demotion queued, in
  flight).  The worker runs an eviction's demotion whatever the tier's own
  watermark says, and drops the copy if nothing downstream can take it.
- Local eviction counts queued demotions toward its budget (a migration pin
  no longer hides a key from EvictionCandidates), so a pass never queues
  more than the overage while the copies run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tests

Comments: drop history and restated rationale (the commit message has it).

Tests, same coverage with fewer of them:
- The two GetEnvUint64 tests become one.
- Drop LruMasterEvictStrategy.ABudgetLargerThanAnyRowCapIsMetInOneRound:
  the strategy never had a row cap (the manager did), and it is unchanged.
- The three MasterEvictionBytes tests become one, which also reads the
  limit through UMBP_EVICT_MAX_BYTES_PER_ROUND.
- EvictReturnsBeforeTheDemotionCopyRuns folds into
  OnEvictMigratesBytesToConfiguredBackend, and
  QueuedDemotionsCountTowardTheBudget into the local-eviction demotion test
  (now ATierWithADownstreamDemotesJustTheOverage).
- GatedEngine's hold times out after 10 s, so it needs no ReleaseOnExit
  guard to keep a failed test from hanging teardown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment-only.  Drops narrative and rationale that restates the code, and
the same "the pool decides, the medium only supplies order" paragraph that
was repeated across several headers; field and helper docs become trailing
one-liners.  Added comment lines across the PR: ~400 -> 195.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a master, a node can evict at its own watermarks instead of waiting
for the master's EvictKey rounds, while still taking its victims from the
master's cluster-wide recency.

- Master: new GetEvictionCandidates RPC returns a node's keys on one medium
  (optionally one logical tier), least recently accessed first, until their
  sizes cover the requested bytes; master-leased keys are skipped.
  RegisterClient and every Heartbeat carry peer_driven_eviction, and
  EvictionManager leaves such nodes out of its rounds (in memory only).
  ClientRegistryConfig::advertise_peer_driven_eviction gates both.
- Peer: local eviction asks a candidate source first (at most once per
  200 ms per backend), rounds its value sizes up to the backend's pages,
  evicts through PeerPool::Evict, and covers the rest from the medium's own
  LRU.  In peer-driven mode PoolClient runs local eviction for every medium,
  at a tiered policy's per-tier watermarks when there is one.
- Opt-in: PoolClientConfig::peer_driven_eviction or
  UMBP_PEER_DRIVEN_EVICTION=1, and only once the master confirms support;
  otherwise behaviour is unchanged.  New metric event master_key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the peer_driven_eviction switch with PoolClientConfig::eviction_mode
(env UMBP_EVICTION_MODE), which says how a node with a master evicts:

- local (default): every medium at the node's own watermarks, by its own
  LRU, without asking the master.
- master_ranked: the same, with victims ranked by the master
  (GetEvictionCandidates), falling back to local LRU.
- master: DRAM/HBM wait for the master's EvictKey rounds; SSD still evicts
  locally (the previous behaviour).

A self-evicting node (local or master_ranked) asks the master to leave it
out of its rounds; every mode still serves EvictKey, so the master can evict
on demand.  A node without a master evicts locally as before.  Self-evicting
nodes use a tiered policy's per-tier watermarks when there is one.

Tests that fill tiny tiers on purpose (cross-node smoke, tier benchmark)
now run in master mode, the behaviour they were written against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two ways a local read of SSD-resident keys failed although every key was
on the device, both seen in the DRAM->SSD demotion e2e run (DSv4-Pro,
sglang direct linker, 300 GB DRAM + 1.5 TB SSD):

* A resolve batch needing more staging than the whole arena failed outright
  ("batch requires 77580 staging pages but arena has 65536; not retryable"),
  however long the caller waited.  The linker's layer-wise reads asked for
  ~2.6k objects per call, so every such call failed.
* Staging was only ever freed by the read lease's expiry (3 s): no local
  reader released it, so the arena had to hold read_rate x 3 s -- ~27 GiB at
  the 9 GiB/s the run reached.

Now:
* SsdBackend::BatchResolve stages at most half the arena per call and
  returns the rest as ResolveOutcome::kDeferred -- held, readable, resolve it
  in a further call.  The first key is always taken, so a batch progresses.
  PeerPool keeps deferred keys' placements; on the wire a deferral is a
  failure (a remote reader cannot come back for the rest).
* MediumBackend::ReleaseResolved hands staging back as soon as the reader's
  copy is done.  Leases are refcounted per published entry and carry a token
  (ResolvedEntry::lease_token), so a shared lease outlives its first reader
  and a release arriving after expiry cannot free the successor's pages.  A
  BUSY resolve drops the holders it took, since the caller discards it.
  Readers that never release (peers over the wire) still expire as before.
* PoolClient's local readers (BatchGetRanges, ServeLocalGets) run resolve ->
  copy -> release in rounds until nothing is deferred; ExecuteLocalGet and
  the two medium existence probes release what they staged.
* InstrumentedBackend counts a kFailed resolve as failed instead of a miss,
  and does not count kBusy/kDeferred (the retry is counted).
* New SSD medium events: staging_early_release, resolve_deferred.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resolve's read lease protected its bytes for a fixed window (SSD staging
3 s, DRAM/HBM pages 500 ms) whoever the reader was.  For an in-process
reader that is a use-after-free waiting to happen: a copy that outlives the
window has its staging reclaimed (SSD) or its pages evicted (DRAM/HBM) and
reused while it is still reading them.  The timer exists for peers reading
over the wire, which cannot report completion; a local reader can.

BatchResolve now takes a ResolveReader (kRemote by default, the one that
cannot leak).  A kLocal reader always hands its entries back with
ReleaseResolved, and until it does:
* SsdBackend counts it in the lease's local_holders; the reaper skips such
  a lease past expiry (warning once at 30 s overdue).  Remote holders keep
  the old expiry, so a lease a peer touched still waits for its timer after
  the local reader releases.
* PageBackend counts it in the slot's local_readers, which fences Evict,
  eviction candidates and the local reclaimer like a live lease.  Releases
  carry the slot generation as their token.
PoolClient resolves its local reads as kLocal and releases on every path:
the busy retry gives back a discarded response's holds, the read rounds
release from a scope guard, and an existence-only ResolveLocalBatch caller
releases at once.  PeerPool forwards the reader and gains ReleaseResolved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment-only, plus ProbeLocal for the two resolve-and-release probes that
were written out twice.  ResolveReader now sits above ResolveOutcome's
comment instead of between it and its enum.  The SSD backend tests fold
from eight to five (one shared-lease test, one remote-expiry/stale-token
test) and the PageBackend pair into one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TianDi101

Copy link
Copy Markdown
Collaborator Author

End-to-end validation of the eviction PR chain: passed.

Branch test/umbp-evict-e2e-v2 @ 2e1663d91 = main (097bb470f) + merges of #741, #762, #763 and #683, nothing else. (The #683 merge drops PageBackend's local-evict counters, which #741 removed, and adds #763's K dropped summary line.) Workload: InferenceY single-node agentic (DeepSeek-V4-Pro fp4, TP8 + DP attention, MTP, conc 256, CI run 29816's config) on n06-21, 3600 s profiling. UMBP is a standalone server with no master (so default local eviction), with a 300 GB DRAM tier demoting to a 1.5 TB SSD tier on 3 drives.

earlier e2e on #753 this chain (exact PR heads)
Input throughput 281,015 tok/s 283,993 tok/s
Prefix hit (HBM + UMBP) 92.03% 91.99%
Failed loads / SSD read or write errors / sglang crashes 0 / 0 / 0 0 / 0 / 0
DRAM→SSD demotions / copies dropped 1.48M / not measured 1.47M / 0
Demotion failures retried until they landed (#763) not measured 5,048 (SSD staging full), all landed
SSD keys deferred to a later round / staging released early (#762) 2.93M / 60.3M 2.83M / 60.3M
Local eviction rounds / stalls (#741) 19 / 0 19 / 0

The log signatures of the 7 bugs this e2e originally found all stay at 0: whole-batch staging failures, UMBP get failed, overdue local holds, SSD write failures, PrepareReadBatch read failed, dropped demotions, and any [error] line. An earlier pass on the pre-trim heads gave the same result (277,392 tok/s, 0 failures).

Notes:

  • The harness sets UMBP_STANDALONE_STARTUP_TIMEOUT_MS=1800000. Since fix(umbp): keep the standalone server serving when its GPU-registered DRAM pool is migrated #756 the server reports ready while its 300 GB pool is still being GPU-registered, and under concurrent model loading sglang's default 30 s connect timed out. That is not part of this chain.
  • One rerun was lost to an sglang GPU memory fault in one DP rank (its own kernel, no UMBP data being loaded at the time); the rerun passed.

🤖 Generated with Claude Code

TianDi101 and others added 6 commits October 14, 2026 05:27
The master ranks a node's keys by last_accessed_at, which only RouteGet
updates.  A local_first node serves its hits without the master, so under
master_ranked (and master) the hottest keys looked the coldest and went
first: in a 4 GB DRAM / 8 GB SSD smoke a hot set re-read throughout the
fill survived 0/512 under master_ranked vs 512/512 under local, and the
hot reads then served from SSD slowed demotion until DRAM sat at 100%.

A node whose eviction follows the master's recency now ships the keys it
reads locally (deduped, bounded) on its heartbeats, and the master
records an access for each.  Local mode sends nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al-leases

Picks up #741's local-read reports; ResolveLocalBatch keeps this branch's probe release and deferred keys alongside them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
master_ranked asked GetEvictionCandidates synchronously inside the
eviction pass, at most every 200 ms per backend.  The in-memory master
answers by scanning its whole index; at e2e scale (500k DRAM + 2.7M SSD
keys on one node) that is 0.66 s per DRAM query and 1.9 s per SSD query,
so a DRAM pass stalled most of every second and every SSD query hit the
1 s deadline and was thrown away after the master had done the work.

The pass now waits at most 20 ms for the query it sends (a small index
still answers the same pass); a slower answer serves a later pass unless
it is over 3 s old, and the medium's own order evicts meanwhile.  One
query per backend is in flight at a time.  The deadline goes to 3 s to
match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al-leases

Picks up #741's non-blocking ranked queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One pass reclaims a backend's whole watermark band: in the e2e a single
DRAM pass covered 108k keys, while one GetEvictionCandidates answer
carries at most 4096 and arrives after the pass has moved on.  The pass
asked once, at its start, so master_ranked ranked 0 of those 108k
victims (at most 4% even when it used to wait for the answer).

The pass now takes the master's answer whenever it is ready, and asks
again for what the budget still needs as each answer runs out (still at
most one query per 200 ms per backend); the medium's own order evicts in
between.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al-leases

Picks up #741's whole-pass master ranking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TianDi101 and others added 2 commits October 15, 2026 01:50
Conflict with #758 in BatchGetRanges: #758 replaced the local_items vector
with an incremental plan builder, and this branch splits the local phase into
resolve -> copy -> release rounds.  Each round now gets its own builder, since
its copies must finish before its holds are released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The branch already carries #741's head e8b950a, which bb6af32 squashes
onto 38a4fa3, so the tree is kept as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TianDi101
TianDi101 changed the base branch from feat/umbp-evict-converge to main October 15, 2026 01:58
TianDi101 and others added 2 commits October 15, 2026 09:45
staging_pages_in_use is sampled once per scrape, so a burst that fills
the arena between two scrapes never shows.  staging_pages_peak keeps the
highest occupancy since start, which is what says whether the arena was
ever too small.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TianDi101 and others added 3 commits October 16, 2026 09:08
BatchResolve now counts a held-but-unreadable key as status="failed"
instead of a miss, but kProduces still marked resolve/failed as never
produced, so SampleMetrics skipped the series.  Those keys left "miss"
and showed up nowhere: the backend panel read zero resolve failures and
its hit ratio left them out of the denominator.  Mark the pair produced,
so it is published from the first tick like every other series.

Tests: a scripted MockBackend checks that kFailed/kBusy/kDeferred/missing
count as failed/nothing/nothing/miss, and the failed series exists at
zero before its first event.  Both fail with the table unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…local-leases

# Conflicts:
#	src/umbp/distributed/pool_client.cpp
A local reader now protects what it resolved by keeping a shared_ptr hold
(ResolvedEntry::hold). The bytes stay put while any hold lives and come back
when the last one drops. This replaces the lease tokens, generations,
ReleaseResolved and kDeferred that the first version of this PR added.

- PageBackend: each committed slot owns a hold. Evict, EvictionCandidates and
  ClearLocal skip a slot whose hold is shared. A kLocal resolve takes no lease.
- SsdBackend: a staging span is reclaimed once its lease has expired and its
  last hold is gone. Reclaim runs in the reaper and on demand when staging is
  full. A kLocal batch is staged up to half the arena at a time; the rest
  comes back kBusy, and the caller serves it in further parts. A kRemote batch
  keeps all-or-nothing staging.
- PeerPool: a partial kLocal batch still touches and promotes what it served.
- PoolClient: ServeLocalGets and BatchGetRanges drop each part's holds before
  resolving the next part. The probes use BatchContains, so they stage nothing.

Also fixes test_peer_pool's FailingEngine to override CreatePlanBuilder
(#774's interface). Before this, test_peer_pool did not build on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TianDi101 TianDi101 changed the title fix(umbp): SSD staging no longer fails local loads; a local read's hold never expires under its copy fix(umbp): SSD staging no longer fails local loads; a local read holds its bytes until it is done Oct 16, 2026
TianDi101 added a commit that referenced this pull request Oct 16, 2026
…nt-staging

Moves extent staging and the pipelined local get onto #762's holds: an extent
stage keeps a hold and is reclaimed with the read leases once the last one
drops; a pipeline part's holds live in its Stage, so the release paths (scope
guards, drain-ahead) are gone.  Brings in #775, with whose ShardedSsdTier
capabilities the ranged_read check is combined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TianDi101
TianDi101 merged commit b04d893 into main Oct 16, 2026
15 checks passed
TianDi101 added a commit that referenced this pull request Oct 16, 2026
…ce-read-touch

# Conflicts:
#	src/umbp/include/umbp/local/tiers/sharded_ssd_tier.h
#	src/umbp/include/umbp/local/tiers/ssd_tier.h
#	src/umbp/local/tiers/sharded_ssd_tier.cpp
#	src/umbp/local/tiers/ssd_tier.cpp
#	tests/cpp/umbp/local/test_ssd_segment_reclaim.cpp
TianDi101 added a commit that referenced this pull request Oct 16, 2026
…-ssd-direct-migration

# Conflicts:
#	src/umbp/distributed/peer/backend/ssd_backend.cpp
#	src/umbp/distributed/pool/peer_pool.cpp
#	src/umbp/include/umbp/distributed/peer/backend/page_backend.h
#	src/umbp/include/umbp/distributed/peer/backend/ssd_backend.h
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.

1 participant