import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { capitalize } from 'lodash' import { Fragment, useEffect } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import z from 'zod' import { useParams } from 'common' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import AlertError from 'components/ui/AlertError' import { DocsButton } from 'components/ui/DocsButton' import { StringToPositiveNumber } from 'components/ui/Forms/Form.constants' import { FormActions } from 'components/ui/Forms/FormActions' import Panel from 'components/ui/Panel' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useMaxConnectionsQuery } from 'data/database/max-connections-query' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' import { usePoolingConfigurationUpdateMutation } from 'data/database/pooling-configuration-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useDatabaseSettingsStateSnapshot } from 'state/database-settings' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Badge, FormControl_Shadcn_, FormDescription_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, FormLabel_Shadcn_, FormMessage_Shadcn_, Form_Shadcn_, Input_Shadcn_, Listbox, Separator, } from 'ui' import { Admonition } from 'ui-patterns' import { SESSION_MODE_DESCRIPTION, TRANSACTION_MODE_DESCRIPTION } from '../Database.constants' import { POOLING_OPTIMIZATIONS } from './ConnectionPooling.constants' const formId = 'connection-pooling-form' const FormSchema = z.object({ default_pool_size: StringToPositiveNumber, pool_mode: z.union([z.literal('transaction'), z.literal('session')]), max_client_conn: StringToPositiveNumber, }) export const ConnectionPooling = () => { const { ref: projectRef } = useParams() const { project } = useProjectContext() const snap = useDatabaseSettingsStateSnapshot() const { data: addons } = useProjectAddonsQuery({ projectRef }) const computeInstance = addons?.selected_addons.find((addon) => addon.type === 'compute_instance') const poolingOptimizations = POOLING_OPTIMIZATIONS[ (computeInstance?.variant.identifier as keyof typeof POOLING_OPTIMIZATIONS) ?? (project?.infra_compute_size === 'nano' ? 'ci_nano' : 'ci_micro') ] const defaultPoolSize = poolingOptimizations.poolSize ?? 15 const defaultMaxClientConn = poolingOptimizations.maxClientConn ?? 200 const computeSize = computeInstance?.variant.name ?? capitalize(project?.infra_compute_size) ?? 'Micro' const { data: poolingInfo, error, isLoading, isError, isSuccess, } = usePoolingConfigurationQuery({ projectRef }) const { data: authConfig } = useAuthConfigQuery({ projectRef }) const { data: maxConnData } = useMaxConnectionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const poolingConfiguration = poolingInfo?.find((x) => x.database_type === 'PRIMARY') const connectionPoolingUnavailable = poolingConfiguration?.pool_mode === null const canUpdateConnectionPoolingConfiguration = useCheckPermissions( PermissionAction.UPDATE, 'projects', { resource: { project_id: project?.id, }, } ) const form = useForm>({ resolver: zodResolver(FormSchema), defaultValues: { pool_mode: poolingConfiguration?.pool_mode as 'transaction' | 'session', default_pool_size: poolingConfiguration?.default_pool_size as number | undefined, max_client_conn: poolingConfiguration?.max_client_conn as number | undefined, }, }) const { mutate: updateConfiguration, isLoading: isUpdating } = usePoolingConfigurationUpdateMutation({ onSuccess: (data) => { if (data) { form.reset({ pool_mode: data.pool_mode, default_pool_size: data.default_pool_size, max_client_conn: poolingConfiguration?.max_client_conn, }) } toast.success('Successfully saved settings') }, }) const onSubmit: SubmitHandler> = async (data) => { if (!projectRef) return console.error('Project ref is required') if (!poolingInfo) return console.error('Pooling info required') updateConfiguration({ ref: projectRef, default_pool_size: data.default_pool_size as number | undefined, pool_mode: data.pool_mode, }) } useEffect(() => { if (isSuccess) { form.reset({ pool_mode: poolingConfiguration?.pool_mode as 'transaction' | 'session', default_pool_size: poolingConfiguration?.default_pool_size as number | undefined, max_client_conn: poolingConfiguration?.max_client_conn as number | undefined, }) } }, [isSuccess, authConfig]) return (

{connectionPoolingUnavailable ? 'Connection Pooling is not available for this project' : 'Connection pooling configuration'}

Supavisor
} footer={ form.reset()} helper={ !canUpdateConnectionPoolingConfiguration ? 'You need additional permissions to update connection pooling settings' : undefined } /> } > {isLoading && ( {Array.from({ length: 4 }).map((_, i) => (
))}
)} {isError && (
)} {isSuccess && ( <> {connectionPoolingUnavailable && (

Please start a new project to enable this feature.

)}
( Pool Mode field.onChange(value)} >

Transaction mode

{TRANSACTION_MODE_DESCRIPTION}

Session mode

{SESSION_MODE_DESCRIPTION}

{poolingConfiguration?.pool_mode === 'transaction' && ( Pool mode is permanently set to Transaction on port 6543 You can use Session mode by connecting to the pooler on port 5432 instead )} {poolingConfiguration?.pool_mode === 'session' && ( <> {field.value === 'transaction' ? ( ) : ( {/* [Joshen] Can probably remove this after Feb 28 */} )} )}

Specify when a connection can be returned to the pool.{' '} snap.setShowPoolingModeHelper(true)} className="cursor-pointer underline underline-offset-2" > Learn more about pool modes .

)} /> ( Pool Size {maxConnData !== undefined && Number(form.getValues('default_pool_size') ?? 15) > maxConnData.maxConnections * 0.8 && (
Pool size is greater than 80% of the max connections ( {maxConnData.maxConnections}) on your database This may result in instability and unreliability with your database connections.
)} The maximum number of connections made to the underlying Postgres cluster, per user+db combination. Pool size has a default of {defaultPoolSize} based on your compute size of {computeSize}. Please refer to our{' '} documentation {' '} to find out more.
)} /> ( Max Client Connections The maximum number of concurrent client connections allowed. This value is fixed at {defaultMaxClientConn} based on your compute size of {computeSize}{' '} and cannot be changed. Please refer to our{' '} documentation {' '} to find out more. )} />
)}
) }