feat(postgres): make embedded connection cap configurable
This commit is contained in:
7
.changeset/embedded-postgres-connection-cap.md
Normal file
7
.changeset/embedded-postgres-connection-cap.md
Normal 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.
|
||||||
@@ -36,6 +36,12 @@ describe("settings defaults invariants", () => {
|
|||||||
expect(isGlobalOnlySettingsKey("localNetworkDiscoveryEnabled")).toBe(true);
|
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", () => {
|
it("defaults dashboard keyboard shortcuts globally", () => {
|
||||||
expect(DEFAULT_GLOBAL_SETTINGS.dashboardKeyboardShortcuts).toEqual({
|
expect(DEFAULT_GLOBAL_SETTINGS.dashboardKeyboardShortcuts).toEqual({
|
||||||
quickChat: "Space",
|
quickChat: "Space",
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ const log = createLogger("postgres-connection");
|
|||||||
* The embedded mode may use an even smaller pool. These can be tuned via
|
* The embedded mode may use an even smaller pool. These can be tuned via
|
||||||
* environment variables if needed.
|
* 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_CONNECT_TIMEOUT_SECONDS = 10;
|
||||||
const DEFAULT_IDLE_TIMEOUT_SECONDS = 20;
|
const DEFAULT_IDLE_TIMEOUT_SECONDS = 20;
|
||||||
|
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ async function isEmptyNonUtf8Cluster(db: PostgresJsDatabase<Record<string, never
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bootSchemaBackend(
|
async function bootSchemaBackend(
|
||||||
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax">,
|
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax" | "globalSettingsDir">,
|
||||||
bypassProjectIsolation = false,
|
bypassProjectIsolation = false,
|
||||||
): Promise<SchemaBackendBootResult> {
|
): Promise<SchemaBackendBootResult> {
|
||||||
try {
|
try {
|
||||||
@@ -326,7 +326,7 @@ async function bootSchemaBackend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bootSchemaBackendOnce(
|
async function bootSchemaBackendOnce(
|
||||||
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax">,
|
options: Pick<CreateTaskStoreForBackendOptions, "env" | "backend" | "embeddedPgRequested" | "embeddedDataDir" | "poolMax" | "globalSettingsDir">,
|
||||||
bypassProjectIsolation = false,
|
bypassProjectIsolation = false,
|
||||||
): Promise<SchemaBackendBootResult> {
|
): Promise<SchemaBackendBootResult> {
|
||||||
const env = options.env ?? process.env;
|
const env = options.env ?? process.env;
|
||||||
@@ -347,12 +347,23 @@ async function bootSchemaBackendOnce(
|
|||||||
if (backend.mode === "embedded") {
|
if (backend.mode === "embedded") {
|
||||||
const { EmbeddedPostgresLifecycle, defaultEmbeddedDataDir, DEFAULT_EMBEDDED_DATABASE } =
|
const { EmbeddedPostgresLifecycle, defaultEmbeddedDataDir, DEFAULT_EMBEDDED_DATABASE } =
|
||||||
await import("./embedded-lifecycle.js");
|
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());
|
const dataDir = resolve(options.embeddedDataDir ?? defaultEmbeddedDataDir());
|
||||||
embeddedDataDir = dataDir;
|
embeddedDataDir = dataDir;
|
||||||
log.log(`startup-factory: starting embedded PostgreSQL (data dir ${dataDir})`);
|
log.log(`startup-factory: starting embedded PostgreSQL (data dir ${dataDir})`);
|
||||||
embeddedLifecycle = new EmbeddedPostgresLifecycle({
|
embeddedLifecycle = new EmbeddedPostgresLifecycle({
|
||||||
dataDir,
|
dataDir,
|
||||||
database: DEFAULT_EMBEDDED_DATABASE,
|
database: DEFAULT_EMBEDDED_DATABASE,
|
||||||
|
postgresFlags: ["-c", `max_connections=${maxConnections}`],
|
||||||
onLog: (message) => log.log(message),
|
onLog: (message) => log.log(message),
|
||||||
onError: (error) => log.error(String(error)),
|
onError: (error) => log.error(String(error)),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ type ProjectSettingsSchema = Omit<ProjectSettings, MovedProjectSettingsKey | Non
|
|||||||
|
|
||||||
/** Default values for global (user-level) settings. */
|
/** Default values for global (user-level) settings. */
|
||||||
export const DEFAULT_GLOBAL_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:
|
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.
|
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.
|
||||||
|
|||||||
@@ -2440,6 +2440,8 @@ export interface BackupSettingsMigrationConflict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalSettings {
|
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". */
|
/** Theme mode preference: dark, light, or system (follows OS). Default: "dark". */
|
||||||
themeMode?: ThemeMode;
|
themeMode?: ThemeMode;
|
||||||
/** Color theme preference for accent colors and styling. Default: "shadcn-ember"; "default" and "ocean" remain valid explicit legacy selections. */
|
/** Color theme preference for accent colors and styling. Default: "shadcn-ember"; "default" and "ocean" remain valid explicit legacy selections. */
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ the list for the "Reset this menu" feature.
|
|||||||
*/
|
*/
|
||||||
export const GLOBAL_SECTION_KEYS: Record<string, ReadonlySet<string>> = {
|
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. */
|
/* 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([
|
appearance: new Set([
|
||||||
"themeMode",
|
"themeMode",
|
||||||
"colorTheme",
|
"colorTheme",
|
||||||
|
|||||||
@@ -44,6 +44,24 @@ export function DatabaseBackupsSection({ form, setForm, backupInfo, backupLoadin
|
|||||||
</div>)}
|
</div>)}
|
||||||
</div>}
|
</div>}
|
||||||
<h4 className="settings-section-heading">{t("settings.backups.databaseBackups", "Database Backups")}</h4>
|
<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
|
<SettingsToggleRow
|
||||||
descriptor={{
|
descriptor={{
|
||||||
key: "autoBackupEnabled",
|
key: "autoBackupEnabled",
|
||||||
|
|||||||
Reference in New Issue
Block a user