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:
zhenyi
2026-06-12 12:53:23 +08:00
parent a40da90ef9
commit 934858bebf
82 changed files with 1273 additions and 4969 deletions
+88 -124
View File
@@ -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);