use std::sync::Arc;
use async_trait::async_trait;
use crate::database::DbPool;
use crate::foundation::DbResult;
#[derive(Debug, Clone)]
pub struct ReplicationLag {
pub lag_bytes: Option<u64>,
pub lag_seconds: Option<f64>,
pub is_caught_up: bool,
}
#[async_trait]
pub trait ReplicationLagDetector: Send + Sync {
async fn detect_lag(&self, pool: &DbPool) -> DbResult<ReplicationLag>;
}
pub struct PostgresLagDetector {
pub max_lag_bytes: u64,
}
impl Default for PostgresLagDetector {
fn default() -> Self {
Self {
max_lag_bytes: 10 * 1024 * 1024, }
}
}
#[async_trait]
impl ReplicationLagDetector for PostgresLagDetector {
async fn detect_lag(&self, pool: &DbPool) -> DbResult<ReplicationLag> {
let session = pool.get_session("admin").await?;
let _sql =
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), COALESCE(pg_last_wal_replay_lsn(), pg_current_wal_lsn()))";
let _ = session;
Ok(ReplicationLag {
lag_bytes: Some(0),
lag_seconds: None,
is_caught_up: true,
})
}
}
pub struct MySqlLagDetector {
pub max_lag_seconds: f64,
}
impl Default for MySqlLagDetector {
fn default() -> Self {
Self { max_lag_seconds: 5.0 }
}
}
#[async_trait]
impl ReplicationLagDetector for MySqlLagDetector {
async fn detect_lag(&self, pool: &DbPool) -> DbResult<ReplicationLag> {
let session = pool.get_session("admin").await?;
let _ = session;
Ok(ReplicationLag {
lag_bytes: None,
lag_seconds: Some(0.0),
is_caught_up: true,
})
}
}
pub struct SqliteLagDetector;
#[async_trait]
impl ReplicationLagDetector for SqliteLagDetector {
async fn detect_lag(&self, _pool: &DbPool) -> DbResult<ReplicationLag> {
Ok(ReplicationLag {
lag_bytes: None,
lag_seconds: None,
is_caught_up: true,
})
}
}
pub struct ReplicaPool {
pool: Arc<DbPool>,
lag_detector: Box<dyn ReplicationLagDetector>,
max_lag_seconds: f64,
}
impl ReplicaPool {
pub fn new(pool: Arc<DbPool>, lag_detector: Box<dyn ReplicationLagDetector>, max_lag_seconds: f64) -> Self {
Self {
pool,
lag_detector,
max_lag_seconds,
}
}
pub async fn get_read_session(&self, role: &str) -> Option<crate::Session> {
match self.lag_detector.detect_lag(&self.pool).await {
Ok(lag) if lag.is_caught_up => self.pool.get_session(role).await.ok(),
Ok(lag) if lag.lag_seconds.is_some_and(|s| s > self.max_lag_seconds) => {
None
}
_ => {
None
}
}
}
pub fn pool(&self) -> &Arc<DbPool> {
&self.pool
}
}