feat(postgres): make embedded connection cap configurable

This commit is contained in:
gsxdsm
2026-07-18 10:00:49 -07:00
parent 2b455672c6
commit 2c17fa70ab
8 changed files with 55 additions and 4 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": minor
---
summary: Let operators set the embedded PostgreSQL connection cap in Advanced Settings.
category: feature
dev: External PostgreSQL runtime pools now default to 3 connections; embedded PostgreSQL reads the global cap on restart.

View File

@@ -36,6 +36,12 @@ describe("settings defaults invariants", () => {
expect(isGlobalOnlySettingsKey("localNetworkDiscoveryEnabled")).toBe(true);
});
it("keeps the embedded PostgreSQL connection cap global and high by default", () => {
expect(DEFAULT_GLOBAL_SETTINGS.embeddedPostgresMaxConnections).toBe(500);
expect(GLOBAL_SETTINGS_KEYS).toContain("embeddedPostgresMaxConnections");
expect(PROJECT_SETTINGS_KEYS).not.toContain("embeddedPostgresMaxConnections");
});
it("defaults dashboard keyboard shortcuts globally", () => {
expect(DEFAULT_GLOBAL_SETTINGS.dashboardKeyboardShortcuts).toEqual({
quickChat: "Space",

View File

@@ -43,7 +43,10 @@ const log = createLogger("postgres-connection");
* The embedded mode may use an even smaller pool. These can be tuned via
* environment variables if needed.
*/
const DEFAULT_POOL_MAX = 10;
// External databases frequently impose a low connection quota. Each Fusion
// store owns a runtime pool plus a migration session, so keep the default
// deliberately small; callers with a known capacity can still override it.
const DEFAULT_POOL_MAX = 3;
const DEFAULT_CONNECT_TIMEOUT_SECONDS = 10;
const DEFAULT_IDLE_TIMEOUT_SECONDS = 20;

View File

@@ -308,7 +308,7 @@ async function isEmptyNonUtf8Cluster(db: PostgresJsDatabase<Record<string, never
}
async function bootSchemaBackend(
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax">,
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax" | "globalSettingsDir">,
bypassProjectIsolation = false,
): Promise<SchemaBackendBootResult> {
try {
@@ -326,7 +326,7 @@ async function bootSchemaBackend(
}
async function bootSchemaBackendOnce(
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax">,
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax" | "globalSettingsDir">,
bypassProjectIsolation = false,
): Promise<SchemaBackendBootResult> {
const env = options.env ?? process.env;
@@ -347,12 +347,23 @@ async function bootSchemaBackendOnce(
if (backend.mode === "embedded") {
const { EmbeddedPostgresLifecycle, defaultEmbeddedDataDir, DEFAULT_EMBEDDED_DATABASE } =
await import("./embedded-lifecycle.js");
const { GlobalSettingsStore } = await import("../global-settings.js");
// Tests that boot an embedded backend intentionally do not have an
// operator global-settings directory. Production always reads the shared
// global preference before it starts the server.
const configuredMaxConnections = process.env.VITEST === "true" && !options.globalSettingsDir
? undefined
: (await new GlobalSettingsStore(options.globalSettingsDir).getSettings()).embeddedPostgresMaxConnections;
const maxConnections = Number.isInteger(configuredMaxConnections)
? Math.min(2_000, Math.max(32, configuredMaxConnections!))
: 500;
const dataDir = resolve(options.embeddedDataDir ?? defaultEmbeddedDataDir());
embeddedDataDir = dataDir;
log.log(`startup-factory: starting embedded PostgreSQL (data dir ${dataDir})`);
embeddedLifecycle = new EmbeddedPostgresLifecycle({
dataDir,
database: DEFAULT_EMBEDDED_DATABASE,
postgresFlags: ["-c", `max_connections=${maxConnections}`],
onLog: (message) => log.log(message),
onError: (error) => log.error(String(error)),
});

View File

@@ -72,6 +72,10 @@ type ProjectSettingsSchema = Omit<ProjectSettings, MovedProjectSettingsKey | Non
/** Default values for global (user-level) settings. */
export const DEFAULT_GLOBAL_SETTINGS = {
// Embedded PostgreSQL is shared by all local Fusion projects and processes.
// Keep this well above the conservative external-pool budget while still
// bounded for a local machine.
embeddedPostgresMaxConnections: 500,
/*
FNXC:DashboardTheming 2026-07-03-00:00:
Fresh installs must follow the operating system theme until the user explicitly chooses Light, Dark, or System. Keep this global default aligned with dashboard and desktop pre-hydration fallbacks.

View File

@@ -2440,6 +2440,8 @@ export interface BackupSettingsMigrationConflict {
}
export interface GlobalSettings {
/** Maximum PostgreSQL server connections for Fusion's embedded database. Applied on the next Fusion restart. */
embeddedPostgresMaxConnections?: number;
/** Theme mode preference: dark, light, or system (follows OS). Default: "dark". */
themeMode?: ThemeMode;
/** Color theme preference for accent colors and styling. Default: "shadcn-ember"; "default" and "ocean" remain valid explicit legacy selections. */

View File

@@ -101,7 +101,7 @@ the list for the "Reset this menu" feature.
*/
export const GLOBAL_SECTION_KEYS: Record<string, ReadonlySet<string>> = {
/* FNXC:SettingsBackups 2026-07-16-14:35: shared PostgreSQL backup policy may only write through its global Database Backups section. */
"backups-global": new Set(["autoBackupEnabled", "autoBackupSchedule", "autoBackupRetention", "autoBackupDir"]),
"backups-global": new Set(["autoBackupEnabled", "autoBackupSchedule", "autoBackupRetention", "autoBackupDir", "embeddedPostgresMaxConnections"]),
appearance: new Set([
"themeMode",
"colorTheme",

View File

@@ -44,6 +44,24 @@ export function DatabaseBackupsSection({ form, setForm, backupInfo, backupLoadin
</div>)}
</div>}
<h4 className="settings-section-heading">{t("settings.backups.databaseBackups", "Database Backups")}</h4>
<details className="settings-advanced-disclosure">
<summary>{t("settings.database.advanced", "Advanced database settings")}</summary>
<SettingsNumberRow
descriptor={{
key: "embeddedPostgresMaxConnections",
label: t("settings.database.embeddedConnectionCap", "Embedded PostgreSQL connection cap"),
help: t("settings.database.embeddedConnectionCapHelp", "Maximum server connections for Fusion's embedded PostgreSQL. Applies after restarting Fusion. Range: 32–2,000. Default: 500. External PostgreSQL uses its provider's connection limit."),
scope: "global",
min: 32,
max: 2000,
}}
value={form.embeddedPostgresMaxConnections ?? 500}
onChange={(v) => setForm((f) => ({ ...f, embeddedPostgresMaxConnections: v ?? 500 }))}
error={form.embeddedPostgresMaxConnections !== undefined && (form.embeddedPostgresMaxConnections < 32 || form.embeddedPostgresMaxConnections > 2000)
? t("settings.database.embeddedConnectionCapError", "Enter a value between 32 and 2,000.")
: undefined}
/>
</details>
<SettingsToggleRow
descriptor={{
key: "autoBackupEnabled",