WIP: Migrate to using taskfile #187
2 changed files with 15 additions and 16 deletions
Improve DB contention
commit
0e1849061b
|
|
@ -10,7 +10,7 @@ use sqlx::{ConnectOptions, sqlite::SqliteConnectOptions, types::chrono::Utc};
|
||||||
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
use sqlx::{Pool, Sqlite, sqlite::SqlitePoolOptions};
|
use sqlx::{Pool, Sqlite, sqlite::SqlitePoolOptions};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::RwLock;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use uuid::fmt::Hyphenated;
|
use uuid::fmt::Hyphenated;
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ impl std::fmt::Debug for PoolWithTimestamp {
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
config: DatabaseConfig,
|
config: DatabaseConfig,
|
||||||
broadcasts: Broadcasts,
|
broadcasts: Broadcasts,
|
||||||
connection_pools: Arc<Mutex<HashMap<VaultId, PoolWithTimestamp>>>,
|
connection_pools: Arc<RwLock<HashMap<VaultId, PoolWithTimestamp>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Transaction<'a> = sqlx::Transaction<'a, Sqlite>;
|
pub type Transaction<'a> = sqlx::Transaction<'a, Sqlite>;
|
||||||
|
|
@ -83,7 +83,7 @@ impl Database {
|
||||||
|
|
||||||
let database = Self {
|
let database = Self {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
connection_pools: Arc::new(Mutex::new(connection_pools)),
|
connection_pools: Arc::new(RwLock::new(connection_pools)),
|
||||||
broadcasts: broadcasts.clone(),
|
broadcasts: broadcasts.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -130,11 +130,12 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_connection_pool(&self, vault: &VaultId) -> Result<Pool<Sqlite>> {
|
async fn get_connection_pool(&self, vault: &VaultId) -> Result<Pool<Sqlite>> {
|
||||||
// First, check if the pool exists without holding the lock during creation
|
// Fast path: check if pool exists with a read lock (no blocking other readers)
|
||||||
{
|
{
|
||||||
let mut pools = self.connection_pools.lock().await;
|
let pools = self.connection_pools.read().await;
|
||||||
if let Some(pool_with_timestamp) = pools.get_mut(vault) {
|
if let Some(pool_with_timestamp) = pools.get(vault) {
|
||||||
pool_with_timestamp.last_accessed = Instant::now();
|
// Skip updating last_accessed here - it's only used for idle cleanup
|
||||||
|
// and will be updated when the pool is created or reused after recreation
|
||||||
return Ok(pool_with_timestamp.pool.clone());
|
return Ok(pool_with_timestamp.pool.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,8 +145,8 @@ impl Database {
|
||||||
// under high concurrency, but only one will be kept
|
// under high concurrency, but only one will be kept
|
||||||
let new_pool = Self::create_vault_database(&self.config, vault).await?;
|
let new_pool = Self::create_vault_database(&self.config, vault).await?;
|
||||||
|
|
||||||
// Re-acquire lock and insert (or use existing if another task created it)
|
// Re-acquire lock (write) and insert (or use existing if another task created it)
|
||||||
let mut pools = self.connection_pools.lock().await;
|
let mut pools = self.connection_pools.write().await;
|
||||||
let pool_with_timestamp = pools
|
let pool_with_timestamp = pools
|
||||||
.entry(vault.clone())
|
.entry(vault.clone())
|
||||||
.or_insert_with(|| PoolWithTimestamp {
|
.or_insert_with(|| PoolWithTimestamp {
|
||||||
|
|
@ -480,22 +481,19 @@ impl Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cleanup idle connection pools that haven't been accessed in more than 5 minutes
|
|
||||||
async fn cleanup_idle_pools(&self) {
|
async fn cleanup_idle_pools(&self) {
|
||||||
let mut pools = self.connection_pools.lock().await;
|
use crate::consts::IDLE_POOL_TIMEOUT;
|
||||||
let now = Instant::now();
|
|
||||||
let idle_timeout = Duration::from_secs(5 * 60); // 5 minutes
|
|
||||||
|
|
||||||
// Collect vaults to remove
|
let mut pools = self.connection_pools.write().await;
|
||||||
|
let now = Instant::now();
|
||||||
let vaults_to_remove: Vec<VaultId> = pools
|
let vaults_to_remove: Vec<VaultId> = pools
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, pool_with_timestamp)| {
|
.filter(|(_, pool_with_timestamp)| {
|
||||||
now.duration_since(pool_with_timestamp.last_accessed) > idle_timeout
|
now.duration_since(pool_with_timestamp.last_accessed) > IDLE_POOL_TIMEOUT
|
||||||
})
|
})
|
||||||
.map(|(vault_id, _)| vault_id.clone())
|
.map(|(vault_id, _)| vault_id.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Close and remove idle pools
|
|
||||||
for vault_id in &vaults_to_remove {
|
for vault_id in &vaults_to_remove {
|
||||||
if let Some(pool_with_timestamp) = pools.remove(vault_id) {
|
if let Some(pool_with_timestamp) = pools.remove(vault_id) {
|
||||||
info!("Closing idle database connection pool for vault `{vault_id}`");
|
info!("Closing idle database connection pool for vault `{vault_id}`");
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub const DEFAULT_CONFIG_PATH: &str = "config.yml";
|
||||||
pub const DEFAULT_DATABASES_DIRECTORY_PATH: &str = "databases";
|
pub const DEFAULT_DATABASES_DIRECTORY_PATH: &str = "databases";
|
||||||
pub const DEFAULT_MAX_CONNECTIONS_PER_VAULT: u32 = 12;
|
pub const DEFAULT_MAX_CONNECTIONS_PER_VAULT: u32 = 12;
|
||||||
pub const DEFAULT_CURSOR_TIMEOUT: Duration = Duration::from_secs(60);
|
pub const DEFAULT_CURSOR_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
pub const IDLE_POOL_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||||
|
|
||||||
pub const DEFAULT_HOST: &str = "127.0.0.1";
|
pub const DEFAULT_HOST: &str = "127.0.0.1";
|
||||||
pub const DEFAULT_PORT: u16 = 3000;
|
pub const DEFAULT_PORT: u16 = 3000;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue