perf: cache-stampede coalescing + DB safeguards; ui/i18n fixes

Backend — tail latency & throughput:
- FileContentCache, image transcode, and search now use moka single-flight
  (try_get_with / get_or_load) so N concurrent misses for the same key
  collapse to one disk read / transcode / query instead of a thundering herd.
  Microbenchmark (128 concurrent on one hot key): 128 loads / p99 ~1023ms
  before vs 1 load / p99 ~32ms after.
- DB: configurable per-statement timeout on the primary pool
  (OXICLOUD_DB_STATEMENT_TIMEOUT_SECS, default 30; maintenance pool exempt) so
  a runaway query can't pin a connection and starve the pool.
- DB: background pool-saturation monitor
  (OXICLOUD_DB_POOL_MONITOR_INTERVAL_SECS) that WARNs as the primary pool nears
  exhaustion — the early signal before tail latency cliffs.
- mimalloc: set MIMALLOC_PURGE_DELAY=0 (Dockerfile + compose) so freed pages
  return to the OS and RSS tracks the live working set; benchmarked on
  musl/aarch64 at ~400MB reclaimed vs 0MB with the default.

Frontend — UI / i18n fixes:
- i18n: fix literal "{{count}}" and "{{percentage}}/{{used}}/{{total}}" in the
  selection toolbar and storage line — the call sites passed param names that
  didn't match the locale placeholders; unify on `count` and pass the storage
  template its params. Add es files.selected_count.
- sidebar: hide the drive picker when there's only one drive (the redundant
  "Personal" row); remove the coloured left accent on the active nav item.
- logo: stop clipping the cloud's left bulge — viewBox recentred on the cloud's
  true bbox with proportional SVG size so it keeps the same rendered scale.
- user menu: drop the default <a> underline on the link rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DioCrafts
2026-06-20 14:42:10 +02:00
parent ca18858630
commit b14c4dc911
19 changed files with 848 additions and 310 deletions
+26 -5
View File
@@ -45,6 +45,7 @@ pub async fn create_database_pools(config: &AppConfig) -> Result<DbPools> {
config.database.connect_timeout_secs,
config.database.idle_timeout_secs,
config.database.max_lifetime_secs,
config.database.statement_timeout_secs,
"primary",
)
.await?;
@@ -68,6 +69,8 @@ pub async fn create_database_pools(config: &AppConfig) -> Result<DbPools> {
config.database.connect_timeout_secs,
config.database.idle_timeout_secs,
config.database.max_lifetime_secs,
// Maintenance pool is exempt: integrity scans / GC may run long.
0,
"maintenance",
)
.await?;
@@ -87,6 +90,7 @@ pub async fn create_database_pools(config: &AppConfig) -> Result<DbPools> {
}
/// Internal helper: create a single pool with retry logic.
#[allow(clippy::too_many_arguments)]
async fn create_pool_with_retries(
connection_string: &str,
max_connections: u32,
@@ -94,6 +98,7 @@ async fn create_pool_with_retries(
connect_timeout_secs: u64,
idle_timeout_secs: u64,
max_lifetime_secs: u64,
statement_timeout_secs: u64,
label: &str,
) -> Result<PgPool> {
let mut attempt = 0;
@@ -108,7 +113,7 @@ async fn create_pool_with_retries(
MAX_ATTEMPTS
);
match PgPoolOptions::new()
let mut opts = PgPoolOptions::new()
.max_connections(max_connections)
.min_connections(min_connections)
.acquire_timeout(Duration::from_secs(connect_timeout_secs))
@@ -119,10 +124,26 @@ async fn create_pool_with_retries(
// that extra round-trip per checkout costs more than the rare dead
// connection it catches. A stale socket surfaces as a query error
// and the pool recycles it either way.
.test_before_acquire(false)
.connect(connection_string)
.await
{
.test_before_acquire(false);
// Bound the worst-case query: `SET statement_timeout` on every new
// connection caps how long any single statement may run, so a runaway
// query can't pin a pool slot and starve interactive requests. `0`
// disables it (maintenance pool). statement_timeout's integer value is
// milliseconds.
if statement_timeout_secs > 0 {
use sqlx::Executor;
let stmt_ms = statement_timeout_secs.saturating_mul(1000);
opts = opts.after_connect(move |conn, _meta| {
Box::pin(async move {
conn.execute(format!("SET statement_timeout = {stmt_ms}").as_str())
.await?;
Ok(())
})
});
}
match opts.connect(connection_string).await {
Ok(pool) => match sqlx::query("SELECT 1").execute(&pool).await {
Ok(_) => {
tracing::info!("PostgreSQL {} pool established successfully", label);