fix(picloud): F-P-003 PICLOUD_DB_MAX_CONNECTIONS env knob, default 32 (matches gate)

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) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 20:03:56 +02:00
parent c63c5cc275
commit 47ea239eb6
2 changed files with 16 additions and 1 deletions

View File

@@ -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 `<root>/files/<app_id>/<collection>/<id[0:2]>/<id>`; metadata in Postgres. |

View File

@@ -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<PgPool> {
let max_connections = std::env::var("PICLOUD_DB_MAX_CONNECTIONS")
.ok()
.and_then(|v| v.trim().parse::<u32>().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?;