Skip to content

Commit 5898472

Browse files
committed
feat(cubestore): Add metastore benchmarks for get_tables_with_path performance testing
- Add comprehensive benchmarks for small (100), medium (1K), and large (25K) table datasets - Test both cached and non-cached execution paths - Include cold vs warm cache comparison benchmarks
1 parent db487ac commit 5898472

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

rust/cubestore/cubestore/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ md5 = "0.8.0"
126126
name = "cachestore_queue"
127127
harness = false
128128

129+
[[bench]]
130+
name = "metastore"
131+
harness = false
132+
129133
[features]
130134
# When enabled, child processes will die whenever parent process exits.
131135
# Highly recomended for production, available only on Linux with prctl system call.
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
2+
use cubestore::config::Config;
3+
use cubestore::metastore::{BaseRocksStoreFs, Column, ColumnType, MetaStore, RocksMetaStore};
4+
use cubestore::remotefs::LocalDirRemoteFs;
5+
use cubestore::CubeError;
6+
use std::env;
7+
use std::fs;
8+
use std::sync::Arc;
9+
use tokio::runtime::{Builder, Runtime};
10+
11+
fn prepare_metastore(name: &str) -> Result<Arc<RocksMetaStore>, CubeError> {
12+
let config = Config::test(name);
13+
14+
let store_path = env::current_dir()
15+
.unwrap()
16+
.join("target")
17+
.join("bench")
18+
.join(format!("test-local-{}", name));
19+
let remote_store_path = env::current_dir()
20+
.unwrap()
21+
.join("target")
22+
.join("bench")
23+
.join(format!("test-remote-{}", name));
24+
25+
let _ = fs::remove_dir_all(store_path.clone());
26+
let _ = fs::remove_dir_all(remote_store_path.clone());
27+
28+
let remote_fs = LocalDirRemoteFs::new(Some(remote_store_path.clone()), store_path.clone());
29+
30+
RocksMetaStore::new(
31+
store_path.join("metastore").as_path(),
32+
BaseRocksStoreFs::new_for_metastore(remote_fs.clone(), config.config_obj()),
33+
config.config_obj(),
34+
)
35+
}
36+
37+
async fn populate_metastore(
38+
metastore: &Arc<RocksMetaStore>,
39+
num_schemas: usize,
40+
tables_per_schema: usize,
41+
) -> Result<(), CubeError> {
42+
for schema_idx in 0..num_schemas {
43+
let schema_name = format!("schema_{}", schema_idx);
44+
metastore.create_schema(schema_name.clone(), false).await?;
45+
46+
for table_idx in 0..tables_per_schema {
47+
let table_name = format!("table_{}_{}", schema_idx, table_idx);
48+
let global_table_id = schema_idx * tables_per_schema + table_idx;
49+
let columns = vec![
50+
Column::new("name".to_string(), ColumnType::String, 1),
51+
Column::new("timestamp".to_string(), ColumnType::Timestamp, 2),
52+
Column::new("float_measure".to_string(), ColumnType::Float, 3),
53+
Column::new("int_measure".to_string(), ColumnType::Int, 4),
54+
];
55+
56+
let table_id = metastore
57+
.create_table(
58+
schema_name.clone(),
59+
table_name,
60+
columns,
61+
None, // locations
62+
None, // import_format
63+
vec![], // indexes
64+
false, // is_ready
65+
None, // build_range_end
66+
None, // seal_at
67+
None, // select_statement
68+
None, // source_columns
69+
None, // stream_offset
70+
None, // unique_key_column_names
71+
None, // aggregates
72+
None, // partition_split_threshold
73+
None, // trace_obj
74+
false, // drop_if_exists
75+
None, // extension
76+
)
77+
.await?;
78+
79+
// Make some tables ready and some not ready for realistic testing
80+
if global_table_id % 4 != 3 {
81+
metastore.table_ready(table_id.get_id(), true).await?;
82+
}
83+
}
84+
}
85+
86+
Ok(())
87+
}
88+
89+
async fn bench_get_tables_with_path(
90+
metastore: &Arc<RocksMetaStore>,
91+
include_non_ready: bool,
92+
iterations: usize,
93+
) {
94+
for _ in 0..iterations {
95+
let result = metastore.get_tables_with_path(include_non_ready).await;
96+
assert!(result.is_ok());
97+
}
98+
}
99+
100+
fn benchmark_get_tables_with_path_small(c: &mut Criterion, runtime: &Runtime) {
101+
let metastore = runtime.block_on(async {
102+
let metastore = prepare_metastore("get_tables_with_path_small").unwrap();
103+
populate_metastore(&metastore, 10, 10).await.unwrap(); // 100 tables
104+
metastore
105+
});
106+
107+
c.bench_with_input(
108+
BenchmarkId::new("get_tables_with_path_small_include_non_ready_true", 100),
109+
&100,
110+
|b, &iterations| {
111+
b.to_async(runtime)
112+
.iter(|| bench_get_tables_with_path(&metastore, true, iterations));
113+
},
114+
);
115+
116+
c.bench_with_input(
117+
BenchmarkId::new("get_tables_with_path_small_include_non_ready_false", 100),
118+
&100,
119+
|b, &iterations| {
120+
b.to_async(runtime)
121+
.iter(|| bench_get_tables_with_path(&metastore, false, iterations));
122+
},
123+
);
124+
}
125+
126+
fn benchmark_get_tables_with_path_medium(c: &mut Criterion, runtime: &Runtime) {
127+
let metastore = runtime.block_on(async {
128+
let metastore = prepare_metastore("get_tables_with_path_medium").unwrap();
129+
populate_metastore(&metastore, 50, 20).await.unwrap(); // 1,000 tables
130+
metastore
131+
});
132+
133+
c.bench_with_input(
134+
BenchmarkId::new("get_tables_with_path_medium_include_non_ready_true", 50),
135+
&50,
136+
|b, &iterations| {
137+
b.to_async(runtime)
138+
.iter(|| bench_get_tables_with_path(&metastore, true, iterations));
139+
},
140+
);
141+
142+
c.bench_with_input(
143+
BenchmarkId::new("get_tables_with_path_medium_include_non_ready_false", 50),
144+
&50,
145+
|b, &iterations| {
146+
b.to_async(runtime)
147+
.iter(|| bench_get_tables_with_path(&metastore, false, iterations));
148+
},
149+
);
150+
}
151+
152+
fn benchmark_get_tables_with_path_large(c: &mut Criterion, runtime: &Runtime) {
153+
let metastore = runtime.block_on(async {
154+
let metastore = prepare_metastore("get_tables_with_path_large").unwrap();
155+
populate_metastore(&metastore, 25, 1000).await.unwrap(); // 25,000 tables
156+
metastore
157+
});
158+
159+
c.bench_with_input(
160+
BenchmarkId::new("get_tables_with_path_large_include_non_ready_true", 10),
161+
&10,
162+
|b, &iterations| {
163+
b.to_async(runtime)
164+
.iter(|| bench_get_tables_with_path(&metastore, true, iterations));
165+
},
166+
);
167+
168+
c.bench_with_input(
169+
BenchmarkId::new("get_tables_with_path_large_include_non_ready_false", 10),
170+
&10,
171+
|b, &iterations| {
172+
b.to_async(runtime)
173+
.iter(|| bench_get_tables_with_path(&metastore, false, iterations));
174+
},
175+
);
176+
}
177+
178+
fn cold_vs_warm_cache_benchmark(c: &mut Criterion, runtime: &Runtime) {
179+
let metastore = runtime.block_on(async {
180+
let metastore = prepare_metastore("cold_warm_cache").unwrap();
181+
populate_metastore(&metastore, 20, 50).await.unwrap(); // 1,000 tables
182+
metastore
183+
});
184+
185+
// Cold cache benchmark (first call)
186+
c.bench_function("get_tables_with_path_cold_cache", |b| {
187+
b.to_async(runtime).iter(|| async {
188+
let fresh_metastore = prepare_metastore("cold_cache_fresh").unwrap();
189+
populate_metastore(&fresh_metastore, 20, 50).await.unwrap();
190+
let result = fresh_metastore.get_tables_with_path(false).await;
191+
assert!(result.is_ok());
192+
});
193+
});
194+
195+
// Warm cache benchmark (subsequent calls)
196+
c.bench_function("get_tables_with_path_warm_cache", |b| {
197+
b.to_async(runtime).iter(|| async {
198+
let result = metastore.get_tables_with_path(false).await;
199+
assert!(result.is_ok());
200+
});
201+
});
202+
}
203+
204+
fn do_benches(c: &mut Criterion) {
205+
let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
206+
207+
benchmark_get_tables_with_path_small(c, &runtime);
208+
benchmark_get_tables_with_path_medium(c, &runtime);
209+
benchmark_get_tables_with_path_large(c, &runtime);
210+
cold_vs_warm_cache_benchmark(c, &runtime);
211+
}
212+
213+
criterion_group!(benches, do_benches);
214+
criterion_main!(benches);

0 commit comments

Comments
 (0)