From 47ea239eb6eed4188b62c181456e3f979417f0f2 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 7 Jun 2026 20:03:56 +0200 Subject: [PATCH] fix(picloud): F-P-003 PICLOUD_DB_MAX_CONNECTIONS env knob, default 32 (matches gate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit init_db hard-coded max_connections(10). The execution gate (ExecutionGate, PICLOUD_MAX_CONCURRENT_EXECUTIONS default 32) lets 32 script executions run concurrently, each doing multiple sequential DB calls — plus the dispatcher tick every 100ms, the cron tick, three GC sweeps, and the auth middleware running per request all draw from the same pool. With max=10 vs gate=32, pool starvation surfaces as acquire_timeout errors and tail-latency spikes under load. - DEFAULT_DB_MAX_CONNECTIONS = 32 (matches gate default). - Env knob: PICLOUD_DB_MAX_CONNECTIONS overrides; invalid/zero → default. - Documented in CLAUDE.md runtime config table. AUDIT.md anchor: F-P-003. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 1 + crates/picloud/src/lib.rs | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index eb708fb..1163224 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -116,6 +116,7 @@ Environment variables consumed by the `picloud` binary: | `PICLOUD_BIND` | `0.0.0.0:8080` | HTTP listen address. Port 8080 is owned by another process on this host — override locally. | | `PICLOUD_MAX_CONCURRENT_EXECUTIONS` | `32` | Global concurrency cap on data-plane script executions. Overflow returns HTTP 503 with `Retry-After: 1` immediately (no queue). | | `DATABASE_URL` | — | Required. Postgres connection string. | +| `PICLOUD_DB_MAX_CONNECTIONS` | `32` | Postgres pool size. Matched to `PICLOUD_MAX_CONCURRENT_EXECUTIONS` so the data plane can't starve background workers. | | `PICLOUD_SESSION_TTL_HOURS` | `24` | Sliding-window session lifetime. | | `PICLOUD_SANDBOX_MAX_*` | conservative defaults | Per-knob admin ceilings on Rhai sandbox overrides. See `manager-core::sandbox::SandboxCeiling`. | | `PICLOUD_FILES_ROOT` | `./data` | Filesystem root for `files::*` blob storage (v1.1.5). Bytes live at `/files////`; metadata in Postgres. | diff --git a/crates/picloud/src/lib.rs b/crates/picloud/src/lib.rs index 54bdbb7..162742d 100644 --- a/crates/picloud/src/lib.rs +++ b/crates/picloud/src/lib.rs @@ -588,11 +588,25 @@ pub async fn build_app( .layer(TraceLayer::new_for_http())) } +/// Default Postgres pool size. Scaled to match the execution gate +/// default (32 concurrent script executions) so a hot pool doesn't +/// starve the data plane. Override with `PICLOUD_DB_MAX_CONNECTIONS`. +/// Sizing relationship: each script run does multiple sequential DB +/// calls (script resolve, SDK calls, log sink, outbox emit) plus the +/// dispatcher tick / cron tick / GC sweeps / auth middleware all draw +/// from the same pool, so this is a floor not a ceiling. +pub const DEFAULT_DB_MAX_CONNECTIONS: u32 = 32; + /// Open a Postgres pool with the binary's standard timeout settings. /// Exposed so tests reach for the same configuration when needed. pub async fn init_db(url: &str) -> anyhow::Result { + let max_connections = std::env::var("PICLOUD_DB_MAX_CONNECTIONS") + .ok() + .and_then(|v| v.trim().parse::().ok()) + .filter(|n| *n > 0) + .unwrap_or(DEFAULT_DB_MAX_CONNECTIONS); let pool = PgPoolOptions::new() - .max_connections(10) + .max_connections(max_connections) .acquire_timeout(Duration::from_secs(5)) .connect(url) .await?;