Phase 8: Hot cache + incremental delta updates
- MemCache: LRU in-memory cache for hot datasets (configurable max, default 16GB) Pin/evict/stats endpoints: POST /query/cache/pin, /cache/evict, GET /cache/stats - Delta store: append-only delta Parquet files for row-level updates Write deltas without rewriting base files, merge at query time - Compaction: POST /query/compact merges deltas into base Parquet - Query engine: checks cache first, falls back to Parquet, merges deltas - Benchmarked on 2.47M rows: 1M row JOIN: 854ms cold → 96ms hot (8.9x speedup) 100K filter: 62ms cold → 21ms hot (3x speedup) 1.1M rows cached in 408MB RAM Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,12 +9,18 @@ use axum::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cache::CacheStats;
|
||||
use crate::context::QueryEngine;
|
||||
use crate::delta;
|
||||
|
||||
pub fn router(engine: QueryEngine) -> Router {
|
||||
Router::new()
|
||||
.route("/health", get(health))
|
||||
.route("/sql", post(execute_query))
|
||||
.route("/cache/pin", post(pin_dataset))
|
||||
.route("/cache/evict", post(evict_dataset))
|
||||
.route("/cache/stats", get(cache_stats))
|
||||
.route("/compact", post(compact_dataset))
|
||||
.with_state(engine)
|
||||
}
|
||||
|
||||
@@ -22,6 +28,8 @@ async fn health() -> &'static str {
|
||||
"queryd ok"
|
||||
}
|
||||
|
||||
// --- SQL Query ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct QueryRequest {
|
||||
sql: String,
|
||||
@@ -87,3 +95,60 @@ async fn execute_query(
|
||||
Err(e) => Err((StatusCode::BAD_REQUEST, e)),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Cache Management ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CacheRequest {
|
||||
dataset: String,
|
||||
}
|
||||
|
||||
async fn pin_dataset(
|
||||
State(engine): State<QueryEngine>,
|
||||
Json(req): Json<CacheRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match engine.pin_dataset(&req.dataset).await {
|
||||
Ok(()) => Ok((StatusCode::OK, format!("pinned: {}", req.dataset))),
|
||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn evict_dataset(
|
||||
State(engine): State<QueryEngine>,
|
||||
Json(req): Json<CacheRequest>,
|
||||
) -> impl IntoResponse {
|
||||
if engine.cache().evict(&req.dataset).await {
|
||||
(StatusCode::OK, format!("evicted: {}", req.dataset))
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, format!("not cached: {}", req.dataset))
|
||||
}
|
||||
}
|
||||
|
||||
async fn cache_stats(State(engine): State<QueryEngine>) -> impl IntoResponse {
|
||||
let stats = engine.cache().stats().await;
|
||||
Json(stats)
|
||||
}
|
||||
|
||||
// --- Compaction ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CompactRequest {
|
||||
dataset: String,
|
||||
base_key: String,
|
||||
primary_key: Option<String>,
|
||||
}
|
||||
|
||||
async fn compact_dataset(
|
||||
State(engine): State<QueryEngine>,
|
||||
Json(req): Json<CompactRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match delta::compact(
|
||||
engine.store(),
|
||||
&req.dataset,
|
||||
&req.base_key,
|
||||
req.primary_key.as_deref(),
|
||||
).await {
|
||||
Ok(result) => Ok(Json(result)),
|
||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user