Implement dual DB pools (primary + maintenance) and wire services
This commit is contained in:
@@ -226,6 +226,12 @@ pub struct DatabaseConfig {
|
||||
pub connect_timeout_secs: u64,
|
||||
pub idle_timeout_secs: u64,
|
||||
pub max_lifetime_secs: u64,
|
||||
/// Maximum connections for the maintenance pool (background/batch tasks).
|
||||
/// Defaults to 25% of `max_connections` (minimum 2).
|
||||
pub maintenance_max_connections: u32,
|
||||
/// Minimum connections for the maintenance pool.
|
||||
/// Defaults to 1.
|
||||
pub maintenance_min_connections: u32,
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
@@ -238,6 +244,8 @@ impl Default for DatabaseConfig {
|
||||
connect_timeout_secs: 10,
|
||||
idle_timeout_secs: 300,
|
||||
max_lifetime_secs: 1800,
|
||||
maintenance_max_connections: 5,
|
||||
maintenance_min_connections: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,6 +514,20 @@ impl AppConfig {
|
||||
config.database.min_connections = val;
|
||||
}
|
||||
|
||||
if let Ok(max_conn) =
|
||||
env::var("OXICLOUD_DB_MAINTENANCE_MAX_CONNECTIONS").map(|v| v.parse::<u32>())
|
||||
&& let Ok(val) = max_conn
|
||||
{
|
||||
config.database.maintenance_max_connections = val;
|
||||
}
|
||||
|
||||
if let Ok(min_conn) =
|
||||
env::var("OXICLOUD_DB_MAINTENANCE_MIN_CONNECTIONS").map(|v| v.parse::<u32>())
|
||||
&& let Ok(val) = min_conn
|
||||
{
|
||||
config.database.maintenance_min_connections = val;
|
||||
}
|
||||
|
||||
// Auth configuration
|
||||
if let Ok(jwt_secret) = env::var("OXICLOUD_JWT_SECRET") {
|
||||
config.auth.jwt_secret = jwt_secret;
|
||||
|
||||
+23
-6
@@ -2,6 +2,8 @@ use sqlx::PgPool;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::infrastructure::db::DbPools;
|
||||
|
||||
use crate::application::services::admin_settings_service::AdminSettingsService;
|
||||
use crate::application::services::auth_application_service::AuthApplicationService;
|
||||
|
||||
@@ -89,9 +91,13 @@ impl AppServiceFactory {
|
||||
/// Initializes the core system services.
|
||||
///
|
||||
/// Requires a `PgPool` because `DedupService` stores its index in PostgreSQL.
|
||||
/// The `maintenance_pool` is given to `DedupService` for long-running
|
||||
/// operations (verify_integrity, garbage_collect) so they cannot starve
|
||||
/// the primary pool.
|
||||
pub async fn create_core_services(
|
||||
&self,
|
||||
db_pool: &Arc<PgPool>,
|
||||
maintenance_pool: &Arc<PgPool>,
|
||||
) -> Result<CoreServices, DomainError> {
|
||||
// Path service (still needed for blob storage root + thumbnails)
|
||||
let path_service = Arc::new(PathService::new(self.storage_path.clone()));
|
||||
@@ -139,6 +145,7 @@ impl AppServiceFactory {
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(
|
||||
&self.storage_path,
|
||||
db_pool.clone(),
|
||||
maintenance_pool.clone(),
|
||||
),
|
||||
);
|
||||
dedup_service.initialize().await?;
|
||||
@@ -390,17 +397,21 @@ impl AppServiceFactory {
|
||||
}
|
||||
|
||||
/// Creates the storage usage service (requires database)
|
||||
///
|
||||
/// Uses the `maintenance_pool` for batch operations
|
||||
/// (`update_all_users_storage_usage`) to avoid starving user requests.
|
||||
pub fn create_storage_usage_service(
|
||||
&self,
|
||||
_repos: &RepositoryServices,
|
||||
db_pool: &Arc<PgPool>,
|
||||
maintenance_pool: &Arc<PgPool>,
|
||||
) -> Arc<dyn crate::application::ports::storage_ports::StorageUsagePort> {
|
||||
let user_repository = Arc::new(
|
||||
crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone()),
|
||||
);
|
||||
let service = Arc::new(
|
||||
crate::application::services::storage_usage_service::StorageUsageService::new(
|
||||
db_pool.clone(),
|
||||
maintenance_pool.clone(),
|
||||
user_repository,
|
||||
),
|
||||
);
|
||||
@@ -413,18 +424,21 @@ impl AppServiceFactory {
|
||||
/// This is the main entry point that replaces all manual logic in `main.rs`.
|
||||
pub async fn build_app_state(
|
||||
&self,
|
||||
db_pool: Option<Arc<PgPool>>,
|
||||
db_pools: Option<DbPools>,
|
||||
) -> Result<AppState, DomainError> {
|
||||
// Database is REQUIRED in 100% blob storage model
|
||||
let pool = db_pool.clone().ok_or_else(|| {
|
||||
let pools = db_pools.ok_or_else(|| {
|
||||
DomainError::internal_error(
|
||||
"Database",
|
||||
"PostgreSQL database is required for blob storage model",
|
||||
)
|
||||
})?;
|
||||
|
||||
let pool = Arc::new(pools.primary);
|
||||
let maintenance_pool = Arc::new(pools.maintenance);
|
||||
|
||||
// 1. Core services (PgPool needed for DedupService index)
|
||||
let core = self.create_core_services(&pool).await?;
|
||||
let core = self.create_core_services(&pool, &maintenance_pool).await?;
|
||||
|
||||
// 2. Repository services (requires PgPool for all metadata)
|
||||
let repos = self.create_repository_services(&core, &pool);
|
||||
@@ -456,7 +470,7 @@ impl AppServiceFactory {
|
||||
recent_service = Some(recent.clone());
|
||||
apps.recent_service = Some(recent);
|
||||
|
||||
storage_usage_service = Some(self.create_storage_usage_service(&repos, &pool));
|
||||
storage_usage_service = Some(self.create_storage_usage_service(&repos, &pool, &maintenance_pool));
|
||||
|
||||
// Auth services
|
||||
if self.config.features.enable_auth {
|
||||
@@ -507,7 +521,8 @@ impl AppServiceFactory {
|
||||
core,
|
||||
repositories: repos,
|
||||
applications: apps,
|
||||
db_pool: db_pool.clone(),
|
||||
db_pool: Some(pool.clone()),
|
||||
maintenance_pool: Some(maintenance_pool),
|
||||
auth_service: auth_services,
|
||||
admin_settings_service: None,
|
||||
trash_service,
|
||||
@@ -742,6 +757,8 @@ pub struct AppState {
|
||||
pub repositories: RepositoryServices,
|
||||
pub applications: ApplicationServices,
|
||||
pub db_pool: Option<Arc<PgPool>>,
|
||||
/// Isolated pool for background / batch operations.
|
||||
pub maintenance_pool: Option<Arc<PgPool>>,
|
||||
pub auth_service: Option<AuthServices>,
|
||||
pub admin_settings_service: Option<Arc<AdminSettingsService>>,
|
||||
pub trash_service: Option<Arc<dyn TrashUseCase>>,
|
||||
|
||||
Reference in New Issue
Block a user