#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WorkloadType {
Olap,
Oltp,
Mixed,
}
impl WorkloadType {
pub fn as_str(&self) -> &'static str {
match self {
WorkloadType::Olap => "OLAP",
WorkloadType::Oltp => "OLTP",
WorkloadType::Mixed => "Mixed",
}
}
}
#[derive(Debug, Clone)]
pub struct RouteTarget {
pub replica: String,
pub workload: WorkloadType,
pub degraded: bool,
}
#[derive(Debug)]
pub struct WorkloadRouter {
olap_replicas: Vec<String>,
oltp_primary: String,
olap_available: bool,
}
impl WorkloadRouter {
pub fn new(oltp_primary: impl Into<String>, olap_replicas: Vec<String>) -> Self {
Self {
olap_replicas,
oltp_primary: oltp_primary.into(),
olap_available: true,
}
}
pub fn set_olap_available(&mut self, available: bool) {
self.olap_available = available;
}
pub fn classify(&self, sql: &str) -> WorkloadType {
let upper = sql.to_uppercase();
let has_group_by = upper.contains("GROUP BY");
let has_aggregate = ["SUM(", "COUNT(", "AVG(", "MIN(", "MAX("]
.iter()
.any(|agg| upper.contains(agg));
let has_window = upper.contains("OVER(") || upper.contains("OVER (");
if has_group_by || has_aggregate || has_window {
WorkloadType::Olap
} else if upper.starts_with("SELECT") {
WorkloadType::Oltp
} else {
WorkloadType::Mixed
}
}
pub fn route(&self, workload: WorkloadType) -> RouteTarget {
match workload {
WorkloadType::Olap => {
if self.olap_available && !self.olap_replicas.is_empty() {
RouteTarget {
replica: self.olap_replicas[0].clone(),
workload: WorkloadType::Olap,
degraded: false,
}
} else {
RouteTarget {
replica: self.oltp_primary.clone(),
workload: WorkloadType::Olap,
degraded: true,
}
}
}
WorkloadType::Oltp => RouteTarget {
replica: self.oltp_primary.clone(),
workload: WorkloadType::Oltp,
degraded: false,
},
WorkloadType::Mixed => RouteTarget {
replica: self.oltp_primary.clone(),
workload: WorkloadType::Mixed,
degraded: false,
},
}
}
pub fn classify_and_route(&self, sql: &str) -> RouteTarget {
let workload = self.classify(sql);
self.route(workload)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn router() -> WorkloadRouter {
WorkloadRouter::new(
"primary",
vec!["olap_replica_1".into(), "olap_replica_2".into()],
)
}
#[test]
fn classify_olap_with_group_by() {
let r = router();
assert_eq!(
r.classify("SELECT dept, SUM(amount) FROM sales GROUP BY dept"),
WorkloadType::Olap
);
}
#[test]
fn classify_oltp_simple_select() {
let r = router();
assert_eq!(
r.classify("SELECT * FROM users WHERE id = ?"),
WorkloadType::Oltp
);
}
#[test]
fn route_olap_to_replica() {
let r = router();
let target = r.route(WorkloadType::Olap);
assert_eq!(target.replica, "olap_replica_1");
assert!(!target.degraded);
}
#[test]
fn route_olap_degrades_when_unavailable() {
let mut r = router();
r.set_olap_available(false);
let target = r.route(WorkloadType::Olap);
assert_eq!(target.replica, "primary");
assert!(target.degraded);
}
}