Skip to content

Commit d9d7cde

Browse files
authored
refactor: optimize file reading for disk-based cache on Unix (#17638)
* refactor: Optimize file reading for disk-based cache on Unix For Unix platforms (`#[cfg(unix)]`): - Open files with the `O_NOATIME` flag to avoid updating access time. - Leverage known file size to streamline reading. - Bypass `stat` and `seek` operations used by `std::fs::File::read_to_end`. * fix: comment typo * fix: add licenses header * fix: cargo fmt
1 parent 3f111dd commit d9d7cde

File tree

9 files changed

+420
-24
lines changed

9 files changed

+420
-24
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/query/storages/common/cache/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,16 @@ rustix = { workspace = true }
3131
siphasher = { workspace = true }
3232

3333
[dev-dependencies]
34+
criterion = { workspace = true }
35+
mockall = { workspace = true }
3436
tempfile = { workspace = true }
3537

38+
[[bench]]
39+
name = "read_cache_content"
40+
harness = false
41+
42+
[target.'cfg(unix)'.dependencies]
43+
libc = { workspace = true }
44+
3645
[lints]
3746
workspace = true
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2021 Datafuse Labs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::fs::File;
16+
use std::io::Write;
17+
use std::path::PathBuf;
18+
19+
use criterion::black_box;
20+
use criterion::criterion_group;
21+
use criterion::criterion_main;
22+
use criterion::Criterion;
23+
use databend_storages_common_cache::read_cache_content;
24+
use tempfile::TempDir;
25+
26+
fn create_test_file(dir: &TempDir, name: &str, size: usize) -> PathBuf {
27+
let path = dir.path().join(name);
28+
let mut file = File::create(&path).unwrap();
29+
let content = vec![42u8; size];
30+
file.write_all(&content).unwrap();
31+
path
32+
}
33+
34+
fn bench_read_cache_content(c: &mut Criterion) {
35+
let temp_dir = TempDir::new().unwrap();
36+
37+
let small_file_path = create_test_file(&temp_dir, "small.bin", 4 * 1024); // 4KB
38+
let medium_file_path = create_test_file(&temp_dir, "medium.bin", 64 * 1024); // 64KB
39+
let large_file_path = create_test_file(&temp_dir, "large.bin", 1024 * 1024); // 1MB
40+
41+
// Benchmark Unix specific implementation (open with O_NOATIME, read with libc::read without stat / seek)
42+
#[cfg(unix)]
43+
{
44+
let mut group = c.benchmark_group("read_cache_content_unix_specific_impl");
45+
46+
group.bench_function("small_file_4KB", |b| {
47+
b.iter(|| {
48+
let path = small_file_path.clone();
49+
let result = read_cache_content(path, 4 * 1024);
50+
black_box(result)
51+
})
52+
});
53+
54+
group.bench_function("medium_file_64KB", |b| {
55+
b.iter(|| {
56+
let path = medium_file_path.clone();
57+
let result = read_cache_content(path, 64 * 1024);
58+
black_box(result)
59+
})
60+
});
61+
62+
group.bench_function("large_file_1MB", |b| {
63+
b.iter(|| {
64+
let path = large_file_path.clone();
65+
let result = read_cache_content(path, 1024 * 1024);
66+
black_box(result)
67+
})
68+
});
69+
70+
group.finish();
71+
}
72+
73+
// Benchmark using read_to_end from std lib
74+
let mut group = c.benchmark_group("read_cache_std_lib_read_to_end");
75+
76+
group.bench_function("small_file_4KB", |b| {
77+
b.iter(|| {
78+
let path = small_file_path.clone();
79+
let result = fallback_std_lib_read_to_end(path, 4 * 1024);
80+
black_box(result)
81+
})
82+
});
83+
84+
group.bench_function("medium_file_64KB", |b| {
85+
b.iter(|| {
86+
let path = medium_file_path.clone();
87+
let result = fallback_std_lib_read_to_end(path, 64 * 1024);
88+
black_box(result)
89+
})
90+
});
91+
92+
group.bench_function("large_file_1MB", |b| {
93+
b.iter(|| {
94+
let path = large_file_path.clone();
95+
let result = fallback_std_lib_read_to_end(path, 1024 * 1024);
96+
black_box(result)
97+
})
98+
});
99+
100+
group.finish();
101+
}
102+
103+
fn fallback_std_lib_read_to_end(path: PathBuf, size: usize) -> std::io::Result<Vec<u8>> {
104+
use std::fs::File;
105+
use std::io::Read;
106+
107+
let mut v = Vec::with_capacity(size);
108+
let mut file = File::open(path)?;
109+
file.read_to_end(&mut v)?;
110+
Ok(v)
111+
}
112+
113+
criterion_group!(benches, bench_read_cache_content);
114+
criterion_main!(benches);

src/query/storages/common/cache/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub use caches::CachedObject;
3131
pub use caches::SegmentBlockMetasCache;
3232
pub use caches::SizedColumnArray;
3333
pub use manager::CacheManager;
34+
// Unfortunately, criterion benchmarks need it to be pub
35+
pub use providers::disk_cache::disk_cache_lru::read_cache_content;
3436
pub use providers::DiskCacheError;
3537
pub use providers::DiskCacheKey;
3638
pub use providers::DiskCacheResult;

src/query/storages/common/cache/src/providers/disk_cache/disk_cache.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,12 @@ impl DiskCache {
321321
self.cache.contains(&cache_key.0)
322322
}
323323

324-
pub fn get_cache_path(&mut self, key: &str) -> Option<PathBuf> {
324+
pub fn get_cache_item_path_and_size(&mut self, key: &str) -> Option<(PathBuf, u64)> {
325325
let cache_key = self.cache_key(key);
326326
self.cache
327327
.get(&cache_key.0)
328-
.map(|_| ()) // release the &mut self
329-
.map(|_| self.abs_path_of_cache_key(&cache_key))
328+
.map(|item| item.get_inner().0)
329+
.map(|size| (self.abs_path_of_cache_key(&cache_key), size))
330330
}
331331

332332
/// Remove the given key from the cache.

0 commit comments

Comments
 (0)