Repository navigation
[core] Reduce shared disk cache lock contention - #1186
JingsongLi merged 6 commits into
Conversation
|
[P2] Avoid retaining the block lock on an idle caller runtime In reader.rs:115–120, the detached fill owns Reproduction
#[test]
fn review_1186_cross_runtime_read_does_not_stall_behind_idle_fill() {
let directory = tempfile::tempdir().unwrap();
let delegate = Arc::new(CountingReader {
data: Bytes::from_static(b"abcdefgh"),
reads: AtomicUsize::new(0),
});
let cache = Arc::new(
LocalCache::new(LocalCacheConfig {
shared: true,
dir: Some(directory.path().to_path_buf()),
namespace: "test".into(),
max_size: Some(4096),
block_size: 4,
whitelist: std::collections::HashSet::from([FileType::Meta]),
})
.unwrap(),
);
let reader = CachedFileReader::new(delegate.clone(), "data.parquet", 8, cache);
let first_runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
assert_eq!(first_runtime.block_on(reader.read(0..4)).unwrap(), "abcd");
// Keep the first runtime alive, but stop driving it.
let second_runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let result = second_runtime.block_on(async {
tokio::time::timeout(std::time::Duration::from_millis(500), reader.read(0..4)).await
});
assert!(
result.is_ok(),
"a completed read must not hold a block lock on its idle caller runtime"
);
assert_eq!(result.unwrap().unwrap(), "abcd");
}
cargo +1.94.0 test --locked -p paimon --lib \
--no-default-features --features storage-fs,storage-memory -j4 \
review_1186_cross_runtime_read_does_not_stall_behind_idle_fillExpected: both reads finish successfully. Actual on Verified on macOS with Rust 1.94.0: the identical test passes on base Suggested fix A minimal fix is to keep publication synchronous when the caller uses |
Purpose
Follow-up to #1175: reduce contention and foreground publication work in the opt-in node-shared disk cache under multi-process range reads.
Brief change log
Bytesslice instead of copying its payload into another allocation.No experimental timer module or environment-controlled retry tuning is included.
Tests
The experimental candidate passed all 81 cache tests and ten repeated complete cache-test runs. The cleaned PR branch also passed all 81 cache tests, formatting and whitespace checks with:
cargo +1.94.0 test --release --locked -p paimon io::cache --lib -j4 cargo +1.94.0 fmt --all -- --check git diff --checkCoverage includes owned-buffer CRC/key/truncation validation, block-striped download coordination, hits during directory-lock contention, generation invalidation, bounded asynchronous publication/single-flight, live staging during recovery, process-exit orphan cleanup, and capacity/priority accounting.
The broader
io::test filter also passed all 184 tests with proxy variables removed for localhost HTTP fixtures. The first run with inherited proxy settings had one HTTP error-message assertion failure (183 passed); the exact test and full filter passed after unsetting HTTP(S)/ALL_PROXY and setting NO_PROXY to localhost/127.0.0.1. No source change was needed for that retest.Performance evidence and limitations
A complete training DataLoader experiment used one 64-vCPU/256-GiB host, a 60-CPU/200-GiB process scope, 8 ranks x 4 workers, batch 16/rank, prefetch 2, 64 warmup + 128 measured batches, per-batch synchronization, fixed input snapshots and sequences, a fresh tmpfs cache per trial, 20-GB configured capacity and 1-MB blocks. The pipeline included Steps, pose, images and JPEG decoding; image bodies used the existing Python OSS path, not Rust BLOB reading.
The staging change alone, on top of the previous experimental lockless-hit/asynchronous-fill candidate, changed the two-run median from 225.2 to 294.7 samples/s (+30.8%). This is not a direct main-versus-PR benchmark. Peak memory did not improve (93.3 to 94.4 GiB), and node RX/sample increased slightly (4.66 to 4.77 MiB). A single cache-disabled reference reached 367.7 samples/s and 7.57 MiB/sample. Thus caching still traded throughput and memory for lower network reception in this workload. No claim of a disk-cache result or universal speedup is made.
Diagnostic instrumentation was disabled for those headline trials. The cleaned PR branch removes that instrumentation and is not separately full-DataLoader benchmarked. Smoke value hashes matched and full-run sample sequences/image counts matched; full-run output value hashes were not enabled. Remaining directory-locked eviction and download coordination are intentionally not redesigned here.
API and Format
No public API, configuration default, or published cache-block format change. Only shared mode uses background fills. Eight fill slots and eight staging slots are per cache instance, not per node; temporary staging bytes are additional to the published capacity. Published blocks remain immutable, and storage mutation fencing remains mandatory.
Documentation
Updated the shared-cache section of
docs/src/getting-started.mdwith concurrency, staging headroom, and whole-file/range overlap behavior.