Skip to content

feat(vfs): add read intents and per-mount read profiles - #22

Merged
yinzhenyu-su merged 45 commits into
mainfrom
feat/read-intent-policy
Nov 9, 2026
Merged

yinzhenyu-su merged 45 commits into
mainfrom
feat/read-intent-policy

Conversation

@yinzhenyu-su

Copy link
Copy Markdown
Owner

关联 Issue

Closes #21

改了什么 / 为什么

并行读不是普适加速:真实云盘实测下 Koofr、Drime、123 单流更快(#21 的对比数据也确认默认 1 MiB 子块会退化),所以驱动默认仍保持单流,同时把并行读做成每个 mount 可显式开启的读策略。

  • 读意图 bulk / interactive / probe 贯穿 VFS、读调度和驱动;只有 bulk 与自适应顺序流可以并行,交互读与探测保持单流。
  • 驱动可声明 ReadProfile;新增 [mounts.read_profile] 的 streams / window_chunks,优先级为 mount 覆盖 > 驱动 profile > 内置默认,驱动 limiter 仍可下调。
  • 校验:streams 为 0/1–32,window_chunks 为 0/1–64;streams > 1 要求 parallel_range,internal_parallel_read 驱动拒绝多流。
  • 并行读统一走读预算调度器;预取、取消、缓存准入、直接复制和分享取回复用同一 profile。
  • 新增 probe test read 的 streams/window 覆盖,以及 probe bench read / probe bench upload,用于先测 provider 上限再改 profile。
  • 文档与生成物:schema、full-config.md、support-drivers.md 的“并行读”列、troubleshooting、architecture、driver-development、debug、FAQ,OpenSpec 变更已归档。
  • CI:race 层按 CPU 动态设置包并行度(RACE_P 可覆盖),测试集合与 -race 覆盖不变。

测试证据

  • scripts/ci/check-all.sh 通过(Go 1.27.3,输出 == local CI check passed ==)。
  • 修复 Windows 专属失败:TestRunDriverReadBenchmark 读取 2 MiB 内存数据低于 Windows 单调时钟粒度,time.Since 为 0 导致吞吐为 0;测试驱动增加可观测延迟,断言保持不变。
  • 真实云盘 A/B 数据记录在 功能建议:支持多线程并行下载(同一文件多 Range 并发拉取) #21 和 openspec/changes/archive/2026-11-08-add-read-profile-override/benchmark-baseline.md。

风险与回滚

未设置 [mounts.read_profile] 时行为由驱动 profile 和能力决定;Koofr、Drime、123 默认单流不变,interactive / probe 始终单流。该配置只影响 bulk 读,改完需重启 qrypt mount。回滚本 PR 即可退回原读路径。

检查清单

  • scripts/ci/check-all.sh 通过
  • 新行为有测试;bug fix 有回归测试
  • 文档/schema 由生成器更新,没有手改生成物
  • 没有提交密钥、cookie、token 或真实配置
  • PR 标题符合 Conventional Commits

- FakeDriver read behavior for strict-range fallback, gated bodies, and call/concurrency stats
- scenario tests for parallel ranges, per-file cap, slot release, and prefetch cancellation
- VFS delete, FUSE adapter release, mobile close/cancel, and syncer worker isolation
- cancellation registries drain together across invalidate/session/seek/close
- scheduler capacity invariants hold under 32x100 concurrent acquire/release
- findWindow costs ~31/80/450 ns/op at 1/10/100 in-flight windows
- VFS and direct read agree on parallel/single/fallback policy
- record the minimum-viable scope decision in the OpenSpec change
- add ReadPlan and PlanResolver with concrete defaults (no 0/-1 sentinels)
- move plan resolution after the FUSE seek heuristic and apply the concrete window
- adaptive sequential upgrade returns a new plan while keeping the FUSE window
- add resolver table tests and assert the adaptive window stays unchanged
- add ScopeRegistry with (cacheKey, sessionID) keys, refcounts, and cancel reasons
- remove prefetch.cancels/reasons/inFlight; dedupe via the window registry
- move the foreground read counter into its own foregroundState
- remove canceled scopes so later prefetches start with a fresh lifetime
- update cancellation tests and OpenSpec phase 2 tasks
- add CachePolicy as the single hot/durable admission point
- collapse direct and wait-window cache lookups into lookupCachedChunk
- preserve existing phase/source diagnostics for both lookup paths
- add admission matrix and parallel hot-only regression tests
- record phase 3 in the OpenSpec change
- architecture.md: list plan, scope, and cache-policy responsibilities
- changelog: record the internal read-pipeline convergence
- mark the phase 4 documentation and gate tasks complete
- keep the first per-file channel when a partial window asks for a different limit
- pass the stable profile BulkStreams as perFileLimit; window streams only control the split
- add a regression test that fails before the fix
- record the Scheduler next phase and mark tasks 5.1/5.2 complete
- Scheduler owns global normal/high slots, per-file budgets, prefetch permits, and the foreground count
- replace slotState, parallelState, prefetchState, and foregroundState in the VFS read path
- keep the two-phase acquire order and priority behavior unchanged
- migrate VFS foreground, parallel, prefetch, and stream read-ahead; direct sharing remains task 5.5
- move the scheduler implementation to pkg/readbudget so drive and vfs can share it
- keep VFS aliases and behavior unchanged
- give direct reads an independent 16-slot scheduler instance
- update the architecture policy, docs, and task 5.5
- ReadPlan.MaxStreams is a hint; Scheduler.GrantStreams clamps it by the live per-file budget
- WithGrantedStreams carries the actual split through window, prefetch, and diagnostics
- ReadStream and prefetch record one stream; adaptive bulk recomputes the grant
- update tests, docs, and OpenSpec tasks 5.6/5.7
Quark stores content hashes in lowercase; an uppercase SHA-1 makes
/file/update/hash reject content the provider already knows with
"sha1 非法", which breaks deduplicated uploads. Send lowercase md5/sha1.

Quark's CDN answers an out-of-range Range request with the whole object,
so clamp reads to the entry and return an empty stream at EOF.

The crypt wrapper reads ciphertext through the raw driver but passed the
plaintext size, so drivers that clamp to entry.Size truncated the final
block and decryption failed. Pass the encrypted size instead.
The delete scheduler drops a key before running its callback, so an
already-fired remote delete looked idle. Probes that wait for idle
returned before the provider delete finished, and an immediate unmount
left test directories behind on slow providers such as Koofr.

Expose active deletes in the debug snapshot and include them in the
probe idle wait.

Harden the real-cloud probes at the same time: accept a workspace root
id, keep the control socket path short, use plain-text fixture names for
providers that block .bin downloads, and fall back to cache counters when
the bounded read-event ring evicts traversal evidence.
Add --path to run the read benchmark against a file that is already on
the mount. This keeps large-file baselines read-only: no fixture upload,
no cleanup, and no remote writes. Sequential samples cross-check their
content hash, and seek probes skip the fixture byte pattern when the
content is unknown.
The encrypted wrapper dropped parallel_range and internal_parallel_read,
so an encrypted mount always looked single-stream to the read planner and
never forwarded the raw driver's ReadProfile. Forward both so encrypted
mounts keep the backend's read scheduling.
…bject

A strict sub-range request that the CDN answers with 200 was returned as
if it were the requested range. Return drive.ErrUnsupported so the read
layer falls back to a single stream instead of assembling the whole
object into one sub-range. The non-strict path keeps the 200 fallback.
The migration worked functionally (streams=3, no fallback) but measured
25-30% lower cold sequential throughput than a single stream on the same
file. Record the control experiment and keep Quark on internal_parallel_read.
Add --streams and --window-chunks so read benchmarks can compare
configurations on one mounted VFS instead of rebuilding and remounting
for every profile. The override is measurement-only: it applies to the
read planner for the duration of the probe call, is cleared on return,
and is not exposed through config or production read paths.
Live measurements showed concurrent sub-ranges slower than one 4 MiB
range on all three providers: fixed per-request latency dominates and
does not scale with concurrency. Set ReadProfile BulkStreams=1 and keep
parallel_range declared so a measured profile can re-enable it later.
- classify not-found upload failures as permanent, so a deleted target
  does not accumulate retries forever
- stop requeueing after maxUploadRetries consecutive transient failures
- classify sftp staging errors so a missing target reports not found
…e watchdog

Replace the fixed API timeouts for Koofr, WebDAV, and 139 uploads with a
driverutil upload budget derived from the source size plus an idle
watchdog that cancels a request only when the transport stops consuming
the body. Multi-GB uploads could not finish within the shared 15-minute,
30-second, and 60-second timeouts and restarted from zero on every
attempt.
- add [mounts.read_profile] with streams/window_chunks, inheriting the
  driver profile for unset fields
- validate ranges and reject parallel streams for drivers without
  parallel_range or with internal_parallel_read
- resolve the effective profile once in core and pass it to VFS reads and
  direct copy/share-fetch paths
- expose the effective profile and its source in the mount debug snapshot
- keep Koofr/Drime defaults single-stream; document the opt-in and record
  the cross-environment benchmark in OpenSpec
@yinzhenyu-su
yinzhenyu-su merged commit 6ba696b into main Nov 9, 2026
22 checks passed
@yinzhenyu-su
yinzhenyu-su deleted the feat/read-intent-policy branch November 9, 2026 14:35
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.

功能建议:支持多线程并行下载(同一文件多 Range 并发拉取)

1 participant