refactor(cache): redesign cache system with structured keys and improved performance
- Add repo_path parameter to cached_response and cached_vec_response functions - Implement structured cache key format with namespace, repo_path, and request proto - Replace global cache with Moka in-memory cache using weight-based eviction - Set 256MB memory cap with 10-minute TTL and 2-minute TTI policy - Add metrics collection for cache operations and evictions - Implement efficient repo-scoped invalidation using key structure - Add detailed documentation comments explaining cache architecture - Remove outdated dependencies and update dependency versions - Add error handling for encoding failures in cache operations - Optimize Vec responses with length-delimited encoding and pre-allocation
This commit is contained in:
+88
-124
@@ -59,24 +59,12 @@ struct MetricsInner {
|
||||
hook_count: DashMap<String, AtomicU64>,
|
||||
/// Counter: slow requests by method
|
||||
slow_request_count: DashMap<String, AtomicU64>,
|
||||
|
||||
raft_term: AtomicU64,
|
||||
/// Gauge: current Raft commit index
|
||||
raft_commit_index: AtomicU64,
|
||||
/// Gauge: current Raft last applied index
|
||||
raft_last_applied: AtomicU64,
|
||||
/// Gauge: whether this node is the Raft leader (1 = yes, 0 = no)
|
||||
raft_is_leader: AtomicU64,
|
||||
/// Gauge: number of entries in the Raft log
|
||||
raft_log_entries: AtomicU64,
|
||||
/// Counter: total AppendEntries RPCs sent
|
||||
raft_append_entries_total: AtomicU64,
|
||||
/// Counter: successful AppendEntries RPCs
|
||||
raft_append_entries_success: AtomicU64,
|
||||
/// Counter: total elections triggered
|
||||
raft_elections_total: AtomicU64,
|
||||
/// Counter: elections won
|
||||
raft_elections_won: AtomicU64,
|
||||
/// Counter: cache evictions by (cause, namespace)
|
||||
cache_eviction_count: DashMap<String, AtomicU64>,
|
||||
/// Counter: cache hits by namespace
|
||||
cache_hit_by_namespace: DashMap<String, AtomicU64>,
|
||||
/// Counter: cache misses by namespace
|
||||
cache_miss_by_namespace: DashMap<String, AtomicU64>,
|
||||
}
|
||||
|
||||
static METRICS: OnceLock<Arc<MetricsInner>> = OnceLock::new();
|
||||
@@ -108,16 +96,9 @@ fn metrics() -> &'static Arc<MetricsInner> {
|
||||
hook_duration_buckets: DashMap::new(),
|
||||
hook_count: DashMap::new(),
|
||||
slow_request_count: DashMap::new(),
|
||||
// Raft metrics
|
||||
raft_term: AtomicU64::new(0),
|
||||
raft_commit_index: AtomicU64::new(0),
|
||||
raft_last_applied: AtomicU64::new(0),
|
||||
raft_is_leader: AtomicU64::new(0),
|
||||
raft_log_entries: AtomicU64::new(0),
|
||||
raft_append_entries_total: AtomicU64::new(0),
|
||||
raft_append_entries_success: AtomicU64::new(0),
|
||||
raft_elections_total: AtomicU64::new(0),
|
||||
raft_elections_won: AtomicU64::new(0),
|
||||
cache_eviction_count: DashMap::new(),
|
||||
cache_hit_by_namespace: DashMap::new(),
|
||||
cache_miss_by_namespace: DashMap::new(),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -243,6 +224,37 @@ pub fn record_cache_op(cache: &str, result: &str, duration: Duration) {
|
||||
record_duration_bucket(&m.cache_op_duration_buckets, cache, duration_ms);
|
||||
}
|
||||
|
||||
/// Record a cache entry eviction.
|
||||
pub fn record_cache_eviction(namespace: &str, cause: &str) {
|
||||
let m = metrics();
|
||||
let key = format!("{cause}:{namespace}");
|
||||
m.cache_eviction_count
|
||||
.entry(key)
|
||||
.or_insert_with(|| AtomicU64::new(0))
|
||||
.value()
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Record a per-namespace cache hit.
|
||||
pub fn record_cache_hit_ns(namespace: &str) {
|
||||
metrics()
|
||||
.cache_hit_by_namespace
|
||||
.entry(namespace.to_string())
|
||||
.or_insert_with(|| AtomicU64::new(0))
|
||||
.value()
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Record a per-namespace cache miss.
|
||||
pub fn record_cache_miss_ns(namespace: &str) {
|
||||
metrics()
|
||||
.cache_miss_by_namespace
|
||||
.entry(namespace.to_string())
|
||||
.or_insert_with(|| AtomicU64::new(0))
|
||||
.value()
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Record a hook execution.
|
||||
pub fn record_hook_execution(hook_type: &str, result: &str, duration: Duration) {
|
||||
let m = metrics();
|
||||
@@ -258,41 +270,6 @@ pub fn record_hook_execution(hook_type: &str, result: &str, duration: Duration)
|
||||
record_duration_bucket(&m.hook_duration_buckets, hook_type, duration_ms);
|
||||
}
|
||||
|
||||
pub fn set_raft_state(
|
||||
term: u64,
|
||||
commit_index: u64,
|
||||
last_applied: u64,
|
||||
is_leader: bool,
|
||||
log_entries: u64,
|
||||
) {
|
||||
let m = metrics();
|
||||
m.raft_term.store(term, Ordering::Relaxed);
|
||||
m.raft_commit_index.store(commit_index, Ordering::Relaxed);
|
||||
m.raft_last_applied.store(last_applied, Ordering::Relaxed);
|
||||
m.raft_is_leader
|
||||
.store(if is_leader { 1 } else { 0 }, Ordering::Relaxed);
|
||||
m.raft_log_entries.store(log_entries, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Record an AppendEntries RPC attempt.
|
||||
pub fn inc_raft_append_entries(success: bool) {
|
||||
let m = metrics();
|
||||
m.raft_append_entries_total.fetch_add(1, Ordering::Relaxed);
|
||||
if success {
|
||||
m.raft_append_entries_success
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Record an election trigger.
|
||||
pub fn inc_raft_election(won: bool) {
|
||||
let m = metrics();
|
||||
m.raft_elections_total.fetch_add(1, Ordering::Relaxed);
|
||||
if won {
|
||||
m.raft_elections_won.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape a string for use as a Prometheus label value.
|
||||
/// Replaces `\` → `\\`, `"` → `\"`, `\n` → `\n` per the Prometheus spec.
|
||||
fn prom_escape(value: &str) -> String {
|
||||
@@ -447,6 +424,33 @@ pub fn render_metrics() -> String {
|
||||
&m.cache_op_duration_buckets,
|
||||
);
|
||||
|
||||
// Cache evictions by cause and namespace
|
||||
render_counter_map(
|
||||
&mut out,
|
||||
"gitks_cache_evictions_total",
|
||||
"Cache evictions by cause and namespace",
|
||||
&m.cache_eviction_count,
|
||||
&["cause", "namespace"],
|
||||
);
|
||||
|
||||
// Per-namespace cache hits
|
||||
render_counter_map(
|
||||
&mut out,
|
||||
"gitks_cache_hits_by_namespace_total",
|
||||
"Cache hits by namespace",
|
||||
&m.cache_hit_by_namespace,
|
||||
&["namespace"],
|
||||
);
|
||||
|
||||
// Per-namespace cache misses
|
||||
render_counter_map(
|
||||
&mut out,
|
||||
"gitks_cache_misses_by_namespace_total",
|
||||
"Cache misses by namespace",
|
||||
&m.cache_miss_by_namespace,
|
||||
&["namespace"],
|
||||
);
|
||||
|
||||
// Hook execution
|
||||
render_counter_map(
|
||||
&mut out,
|
||||
@@ -462,59 +466,6 @@ pub fn render_metrics() -> String {
|
||||
&m.hook_duration_buckets,
|
||||
);
|
||||
|
||||
// Raft consensus metrics
|
||||
let raft_term = m.raft_term.load(Ordering::Relaxed);
|
||||
let raft_commit = m.raft_commit_index.load(Ordering::Relaxed);
|
||||
let raft_applied = m.raft_last_applied.load(Ordering::Relaxed);
|
||||
let raft_leader = m.raft_is_leader.load(Ordering::Relaxed);
|
||||
let raft_entries = m.raft_log_entries.load(Ordering::Relaxed);
|
||||
let raft_ae_total = m.raft_append_entries_total.load(Ordering::Relaxed);
|
||||
let raft_ae_success = m.raft_append_entries_success.load(Ordering::Relaxed);
|
||||
let raft_elections = m.raft_elections_total.load(Ordering::Relaxed);
|
||||
let raft_elections_won = m.raft_elections_won.load(Ordering::Relaxed);
|
||||
|
||||
out.push_str("# HELP gitks_raft_term Current Raft term\n");
|
||||
out.push_str("# TYPE gitks_raft_term gauge\n");
|
||||
out.push_str(&format!("gitks_raft_term {raft_term}\n\n"));
|
||||
|
||||
out.push_str("# HELP gitks_raft_commit_index Current Raft commit index\n");
|
||||
out.push_str("# TYPE gitks_raft_commit_index gauge\n");
|
||||
out.push_str(&format!("gitks_raft_commit_index {raft_commit}\n\n"));
|
||||
|
||||
out.push_str("# HELP gitks_raft_last_applied Current Raft last applied index\n");
|
||||
out.push_str("# TYPE gitks_raft_last_applied gauge\n");
|
||||
out.push_str(&format!("gitks_raft_last_applied {raft_applied}\n\n"));
|
||||
|
||||
out.push_str("# HELP gitks_raft_is_leader Whether this node is the Raft leader\n");
|
||||
out.push_str("# TYPE gitks_raft_is_leader gauge\n");
|
||||
out.push_str(&format!("gitks_raft_is_leader {raft_leader}\n\n"));
|
||||
|
||||
out.push_str("# HELP gitks_raft_log_entries Number of entries in the Raft log\n");
|
||||
out.push_str("# TYPE gitks_raft_log_entries gauge\n");
|
||||
out.push_str(&format!("gitks_raft_log_entries {raft_entries}\n\n"));
|
||||
|
||||
out.push_str("# HELP gitks_raft_append_entries_total Total AppendEntries RPCs sent\n");
|
||||
out.push_str("# TYPE gitks_raft_append_entries_total counter\n");
|
||||
out.push_str(&format!(
|
||||
"gitks_raft_append_entries_total {raft_ae_total}\n\n"
|
||||
));
|
||||
|
||||
out.push_str("# HELP gitks_raft_append_entries_success Successful AppendEntries RPCs\n");
|
||||
out.push_str("# TYPE gitks_raft_append_entries_success counter\n");
|
||||
out.push_str(&format!(
|
||||
"gitks_raft_append_entries_success {raft_ae_success}\n\n"
|
||||
));
|
||||
|
||||
out.push_str("# HELP gitks_raft_elections_total Total elections triggered\n");
|
||||
out.push_str("# TYPE gitks_raft_elections_total counter\n");
|
||||
out.push_str(&format!("gitks_raft_elections_total {raft_elections}\n\n"));
|
||||
|
||||
out.push_str("# HELP gitks_raft_elections_won Elections won by this node\n");
|
||||
out.push_str("# TYPE gitks_raft_elections_won counter\n");
|
||||
out.push_str(&format!(
|
||||
"gitks_raft_elections_won {raft_elections_won}\n\n"
|
||||
));
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
@@ -548,21 +499,35 @@ impl Service<Request<Incoming>> for Router {
|
||||
}
|
||||
|
||||
fn json_response(status: u16, body: &str) -> Response<Full<Bytes>> {
|
||||
Response::builder()
|
||||
match Response::builder()
|
||||
.status(status)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Connection", "close")
|
||||
.body(Full::new(Bytes::from(body.to_string())))
|
||||
.unwrap()
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
tracing::error!(error = %err, "failed to build JSON response");
|
||||
Response::new(Full::new(Bytes::from_static(
|
||||
br#"{"error":"response build failed"}"#,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn text_response(status: u16, content_type: &str, body: String) -> Response<Full<Bytes>> {
|
||||
Response::builder()
|
||||
match Response::builder()
|
||||
.status(status)
|
||||
.header("Content-Type", content_type)
|
||||
.header("Connection", "close")
|
||||
.body(Full::new(Bytes::from(body)))
|
||||
.unwrap()
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
tracing::error!(error = %err, "failed to build text response");
|
||||
Response::new(Full::new(Bytes::from_static(b"response build failed")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(req: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||
@@ -592,10 +557,9 @@ async fn handle_request(req: Request<Incoming>) -> Result<Response<Full<Bytes>>,
|
||||
};
|
||||
json_response(200, &format!(r#"{{"log_level":"{msg}"}}"#))
|
||||
}
|
||||
(Method::PUT, "/debug/log-level") => match handle_log_level_update(req).await {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => json_response(400, &format!(r#"{{"error":"{e}"}}"#)),
|
||||
},
|
||||
(Method::PUT, "/debug/log-level") => handle_log_level_update(req)
|
||||
.await
|
||||
.unwrap_or_else(|e| json_response(400, &format!(r#"{{"error":"{e}"}}"#))),
|
||||
(Method::GET, "/debug/config") => {
|
||||
let threshold = metrics().slow_request_threshold_ms.load(Ordering::Relaxed);
|
||||
let ready = metrics().ready.load(Ordering::Relaxed);
|
||||
|
||||
Reference in New Issue
Block a user