Skip to content

Commit 5e07956

Browse files
committed
use query result cache
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 90e99d3 commit 5e07956

12 files changed

Lines changed: 529 additions & 66 deletions

File tree

Cargo.lock

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

benchmarks-website/server/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ path = "src/main.rs"
2626
anyhow = { workspace = true }
2727
axum = "0.8"
2828
base64 = "0.22"
29+
dashmap = { workspace = true }
2930
# track vortex-duckdb's bundled engine version (build.rs)
3031
duckdb = { version = "1.10502", features = ["bundled"] }
3132
maud = { version = "0.27", features = ["axum"] }
3233
parking_lot = { workspace = true }
33-
serde = { workspace = true, features = ["derive"] }
34+
serde = { workspace = true, features = ["derive", "rc"] }
3435
serde_json = { workspace = true }
3536
subtle = "2.6"
3637
thiserror = { workspace = true }

benchmarks-website/server/src/api/charts.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! returns a [`ChartResponse`].
1010
1111
use std::collections::BTreeMap;
12+
use std::sync::Arc;
1213

1314
use anyhow::Context as _;
1415
use anyhow::Result;
@@ -100,7 +101,7 @@ pub(crate) fn collect_group_charts(
100101
charts.push(NamedChartResponse {
101102
name: link.name,
102103
slug: link.slug,
103-
chart,
104+
chart: Arc::new(chart),
104105
});
105106
}
106107
if charts.is_empty() {

benchmarks-website/server/src/api/dto.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! ingest side, see [`crate::records`]).
1111
1212
use std::collections::BTreeMap;
13+
use std::sync::Arc;
1314

1415
use serde::Serialize;
1516
use serde_json::Value as JsonValue;
@@ -54,17 +55,21 @@ pub fn group_sort_key(name: &str) -> (usize, &str) {
5455
}
5556

5657
/// Body of `GET /api/groups`: every group with its chart links and summary.
58+
///
59+
/// The inner [`Vec`] is held in an [`Arc`] so [`crate::query_cache::QueryCache`]
60+
/// can serve the same allocation to every concurrent reader without cloning.
61+
/// `Arc<T>` serialises through to `T`, so the wire shape is unchanged.
5762
#[derive(Debug, Serialize)]
5863
pub struct GroupsResponse {
5964
/// Every group surfaced by the discovery passes, in canonical order.
60-
pub groups: Vec<Group>,
65+
pub groups: Arc<Vec<Group>>,
6166
}
6267

6368
/// One group: a display name, a slug for the group permalink, and the chart
6469
/// links inside it. Optionally carries a v2-compatible rollup summary and a
6570
/// short editorial description (rendered as a hover tooltip on the
6671
/// disclosure title).
67-
#[derive(Debug, Serialize)]
72+
#[derive(Debug, Clone, Serialize)]
6873
pub struct Group {
6974
/// Human-readable group label rendered in the disclosure header.
7075
pub name: String,
@@ -98,7 +103,7 @@ pub struct GroupChartsResponse {
98103
}
99104

100105
/// Server-computed group summary, matching the v2 metadata contract.
101-
#[derive(Debug, Serialize)]
106+
#[derive(Debug, Clone, Serialize)]
102107
#[serde(tag = "type")]
103108
pub enum Summary {
104109
/// Random-access format ranking for the latest populated random-access chart.
@@ -161,7 +166,7 @@ pub enum Summary {
161166
}
162167

163168
/// One random-access summary row.
164-
#[derive(Debug, Serialize)]
169+
#[derive(Debug, Clone, Serialize)]
165170
pub struct RandomAccessRanking {
166171
/// Series name, normally the physical format.
167172
pub name: String,
@@ -172,7 +177,7 @@ pub struct RandomAccessRanking {
172177
}
173178

174179
/// One query benchmark summary row.
175-
#[derive(Debug, Serialize)]
180+
#[derive(Debug, Clone, Serialize)]
176181
pub struct QueryRanking {
177182
/// Series name, normally `engine:format`.
178183
pub name: String,
@@ -186,6 +191,10 @@ pub struct QueryRanking {
186191
/// A single chart inside a [`GroupChartsResponse`]. `name` is the chart's
187192
/// short label inside the group (e.g. `Q1`); `slug` round-trips through
188193
/// `/api/chart/{slug}`.
194+
///
195+
/// `chart` is held in an [`Arc`] so the cache and the landing-page builder
196+
/// share the same allocation; `Arc<T>` serialises as `T`, so the wire shape
197+
/// is identical to a plain `ChartResponse`.
189198
#[derive(Debug, Serialize)]
190199
pub struct NamedChartResponse {
191200
/// Chart label rendered in the chart-card title (e.g. `Q1`).
@@ -194,12 +203,12 @@ pub struct NamedChartResponse {
194203
pub slug: String,
195204
/// Inlined chart payload — same shape as `/api/chart/{slug}`.
196205
#[serde(flatten)]
197-
pub chart: ChartResponse,
206+
pub chart: Arc<ChartResponse>,
198207
}
199208

200209
/// One chart's short label inside a group (e.g. `Q1`) plus the slug that
201210
/// resolves to its `/api/chart/{slug}` payload.
202-
#[derive(Debug, Serialize)]
211+
#[derive(Debug, Clone, Serialize)]
203212
pub struct ChartLink {
204213
/// Chart label rendered in the chart-card title (e.g. `Q1`).
205214
pub name: String,

benchmarks-website/server/src/api/mod.rs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ use crate::slug::GroupKey;
6767

6868
/// Handler for `GET /api/groups`.
6969
pub async fn groups(State(state): State<AppState>) -> Result<impl IntoResponse, ApiError> {
70-
let groups = db::run_blocking(&state.db, |conn| collect_groups(conn)).await?;
70+
let groups = cached_groups(&state).await?;
7171
Ok(Json(GroupsResponse { groups }))
7272
}
7373

@@ -80,8 +80,7 @@ pub async fn chart(
8080
let key = ChartKey::from_slug(&slug)
8181
.map_err(|e| ApiError::BadRequest(format!("invalid slug: {e}")))?;
8282
let window = q.window();
83-
let response =
84-
db::run_blocking(&state.db, move |conn| chart_payload(conn, &key, &window)).await?;
83+
let response = cached_chart_payload(&state, &slug, &key, &window).await?;
8584
let response =
8685
response.ok_or_else(|| ApiError::NotFound(format!("no data for slug {slug:?}")))?;
8786
Ok(Json(response))
@@ -96,15 +95,74 @@ pub async fn group(
9695
let key = GroupKey::from_slug(&slug)
9796
.map_err(|e| ApiError::BadRequest(format!("invalid group slug: {e}")))?;
9897
let window = q.window();
99-
let response = db::run_blocking(&state.db, move |conn| {
100-
collect_group_charts(conn, &key, &window)
101-
})
102-
.await?;
98+
let response = cached_group_charts(&state, &slug, &key, &window).await?;
10399
let response =
104100
response.ok_or_else(|| ApiError::NotFound(format!("no data for group slug {slug:?}")))?;
105101
Ok(Json(response))
106102
}
107103

104+
/// Cache-aware wrapper around `collect_groups`.
105+
pub async fn cached_groups(state: &AppState) -> Result<std::sync::Arc<Vec<Group>>> {
106+
let db = state.db.clone();
107+
state
108+
.cache
109+
.groups(move || async move { db::run_blocking(&db, |conn| collect_groups(conn)).await })
110+
.await
111+
}
112+
113+
/// Cache-aware wrapper around [`collect_filter_universe`].
114+
pub async fn cached_filter_universe(state: &AppState) -> Result<std::sync::Arc<FilterUniverse>> {
115+
let db = state.db.clone();
116+
state
117+
.cache
118+
.filter_universe(move || async move {
119+
db::run_blocking(&db, |conn| collect_filter_universe(conn)).await
120+
})
121+
.await
122+
}
123+
124+
/// Cache-aware wrapper around `chart_payload`.
125+
pub async fn cached_chart_payload(
126+
state: &AppState,
127+
slug: &str,
128+
key: &ChartKey,
129+
window: &CommitWindow,
130+
) -> Result<Option<std::sync::Arc<ChartResponse>>> {
131+
let db = state.db.clone();
132+
let key_for_compute = key.clone();
133+
let window_for_compute = *window;
134+
state
135+
.cache
136+
.chart_payload(slug, window, move || async move {
137+
db::run_blocking(&db, move |conn| {
138+
chart_payload(conn, &key_for_compute, &window_for_compute)
139+
})
140+
.await
141+
})
142+
.await
143+
}
144+
145+
/// Cache-aware wrapper around `collect_group_charts`.
146+
pub async fn cached_group_charts(
147+
state: &AppState,
148+
slug: &str,
149+
key: &GroupKey,
150+
window: &CommitWindow,
151+
) -> Result<Option<std::sync::Arc<GroupChartsResponse>>> {
152+
let db = state.db.clone();
153+
let key_for_compute = key.clone();
154+
let window_for_compute = *window;
155+
state
156+
.cache
157+
.group_charts(slug, window, move || async move {
158+
db::run_blocking(&db, move |conn| {
159+
collect_group_charts(conn, &key_for_compute, &window_for_compute)
160+
})
161+
.await
162+
})
163+
.await
164+
}
165+
108166
/// Handler for `GET /health`.
109167
pub async fn health(State(state): State<AppState>) -> Result<impl IntoResponse, ApiError> {
110168
let path = state.db_path.display().to_string();

benchmarks-website/server/src/api/window.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use super::dto::DEFAULT_COMMIT_WINDOW;
1717
///
1818
/// `Last(n)` keeps the most recent `n` commits by `commits.timestamp`; `All`
1919
/// returns every commit ever ingested.
20-
#[derive(Debug, Clone, Copy)]
20+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2121
pub enum CommitWindow {
2222
/// Keep the most recent `n` commits.
2323
Last(NonZeroU32),

benchmarks-website/server/src/app.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::db::DbHandle;
3030
use crate::db::{self};
3131
use crate::html;
3232
use crate::ingest;
33+
use crate::query_cache::QueryCache;
3334

3435
/// Shared state for all handlers. Cheap to clone (everything is `Arc`-shaped
3536
/// or a small `String`).
@@ -41,6 +42,9 @@ pub struct AppState {
4142
pub bearer_token: Arc<String>,
4243
/// On-disk path of the DuckDB file. Surfaced on `/health`.
4344
pub db_path: Arc<PathBuf>,
45+
/// In-memory cache of every read-side query result. Cleared by
46+
/// [`crate::ingest`] after a successful commit. See [`crate::query_cache`].
47+
pub cache: Arc<QueryCache>,
4448
}
4549

4650
impl AppState {
@@ -52,6 +56,7 @@ impl AppState {
5256
db,
5357
bearer_token: Arc::new(bearer_token),
5458
db_path: Arc::new(path),
59+
cache: Arc::new(QueryCache::new()),
5560
})
5661
}
5762
}

0 commit comments

Comments
 (0)