This commit is contained in:
DioCrafts
2026-06-10 22:03:49 +02:00
parent 45fae4b27e
commit f678ff414e
12 changed files with 789 additions and 200 deletions
+14
View File
@@ -248,6 +248,12 @@ pub struct StorageConfig {
/// quota fresh for all mutations without recomputing on the request path.
/// Default: 600 (10 min). Env: `OXICLOUD_STORAGE_USAGE_RECONCILE_SECS`.
pub usage_reconcile_secs: u64,
/// Interval (milliseconds) of the background job that drains
/// `storage.tree_etag_dirty` and bumps folder `tree_modified_at`
/// (collection ETags). Write paths only enqueue — this is the upper
/// bound on how stale an ancestor folder's ETag can be after a change.
/// Default: 500. Env: `OXICLOUD_TREE_ETAG_FLUSH_MS`.
pub tree_etag_flush_ms: u64,
/// Which blob storage backend to use (`local`, `s3`, or `azure`).
pub backend: StorageBackendType,
/// S3-compatible backend configuration (used when `backend == S3`).
@@ -390,6 +396,7 @@ impl Default for StorageConfig {
upload_temp_dir: None,
chunk_dir: None,
usage_reconcile_secs: 600, // 10 minutes
tree_etag_flush_ms: 500,
backend: StorageBackendType::Local,
s3: None,
azure: None,
@@ -1291,6 +1298,13 @@ impl AppConfig {
config.storage.usage_reconcile_secs = val;
}
// Tree-ETag dirty-queue flush cadence
if let Ok(ms) = env::var("OXICLOUD_TREE_ETAG_FLUSH_MS").map(|v| v.parse::<u64>())
&& let Ok(val) = ms
{
config.storage.tree_etag_flush_ms = val;
}
// Storage backend selection
if let Ok(backend) = env::var("OXICLOUD_STORAGE_BACKEND") {
match backend.to_lowercase().as_str() {
+21
View File
@@ -657,6 +657,25 @@ impl AppServiceFactory {
service
}
/// Starts the tree-ETag flush job (requires database).
///
/// The statement triggers on `storage.files`/`storage.folders` only
/// enqueue bump requests into `storage.tree_etag_dirty` (so user-facing
/// writes take no folder-row locks); this job is the single drainer that
/// turns them into `tree_modified_at` updates. It must run whenever the
/// database is up — the triggers are always installed, and an undrained
/// queue grows unboundedly while folder ETags freeze. Fire-and-forget on
/// the maintenance pool, like the trash cleanup job.
fn start_tree_etag_flush_job(&self, maintenance_pool: &Arc<PgPool>) {
let service =
crate::infrastructure::services::tree_etag_flush_service::TreeEtagFlushService::new(
maintenance_pool.clone(),
self.config.storage.tree_etag_flush_ms,
);
service.start_flush_job();
tracing::info!("Tree-ETag flush service initialized");
}
/// Builds the complete AppState using all factory services.
///
/// This is the main entry point that replaces all manual logic in `main.rs`.
@@ -745,6 +764,8 @@ impl AppServiceFactory {
storage_usage_service =
Some(self.create_storage_usage_service(&repos, &pool, &maintenance_pool));
self.start_tree_etag_flush_job(&maintenance_pool);
// User-lifecycle dispatcher. Hook order is registration order;
// document dependencies inline if/when any arise. Today:
// 1. AuditLifecycleHook — fires first so the