mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
Merge pull request #14619 from supabase/feat/update-disk-size
Feat/update disk size
This commit is contained in:
5 files changed
+298
-9
No files matched your search
@@ -9,7 +9,7 @@ export const meta = {
|
||||
Database size refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily. As you read this document we will refer to "database size" and "disk size":
|
||||
|
||||
- "Database size" is the total size of used storage from your database.
|
||||
- "Disk size" describes the size of the underlying available storage.
|
||||
- "Disk Storage size" describes the size of the underlying available storage.
|
||||
|
||||
## Database space management
|
||||
|
||||
@@ -23,7 +23,7 @@ select
|
||||
from pg_database;
|
||||
```
|
||||
|
||||
This value is reported in the [database settings page](https://app.supabase.com/project/_/settings/database).
|
||||
This value is reported in the [database report page](https://app.supabase.com/project/_/reports/database).
|
||||
|
||||
Database Space is consumed primarily by your data, indexes, and materialized views. You can reduce your disk size by removing any of these and running a Vacuum operation.
|
||||
|
||||
@@ -61,16 +61,18 @@ Supabase uses network-attached storage to balance performance with scalability.
|
||||
|
||||
Pro and Enterprise projects have auto-scaling Disk Storage.
|
||||
|
||||
Disk storage expands automatically when the database reaches 90% of the disk size. The disk is expanded to be 50% larger (e.g., 8GB -> 12GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode.
|
||||
Disk Storage expands automatically when the database reaches 90% of the disk size. The disk is expanded to be 50% larger (e.g., 8GB -> 12GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode.
|
||||
|
||||
<Admonition type="caution">
|
||||
The maximum Disk Storage Size for Pro Tier is 64TB. If you need more than this, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
|
||||
|
||||
If you intend to import a lot of data into your database which requires multiple disk expansions then [reach out to our team](https://app.supabase.com/support/new). For example, uploading more than 1.5x the current size of your database storage will put your database into [read-only mode](#read-only-mode).
|
||||
The Disk Storage Size can also be manually expanded in [Database settings page](https://app.supabase.com/project/_/settings/database). The maximum default you can expand the Disk Storage to is 200GB. If you wish to manually expand it any more than 200B, please [contact us](https://app.supabase.com/support/new) to discuss expanding the 200GB limit.
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
You may want to import a lot of data into your database which requires multiple disk expansions; for example, uploading more than 1.5x the current size of your database storage will put your database into [read-only mode](#read-only-mode). If so, it is highly recommended you increase the Disk Storage Size manually on the [Database settings page](https://app.supabase.com/project/_/settings/database).
|
||||
|
||||
</Admonition>
|
||||
|
||||
The maximum Disk Size for Pro Tier is 64TB. If you need more than this, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
|
||||
|
||||
### Free Tier Behavior
|
||||
|
||||
Free Tier projects enter [read-only](#read-only-mode) mode when you exceed the 500mb limit. Once in read-only mode, you have several options:
|
||||
|
||||
@@ -89,7 +89,7 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => {
|
||||
<p className="text-sm text-scale-1000">
|
||||
When this project exceeds its included usage quotas, it may become unresponsive.
|
||||
{currentPlan?.id === 'free'
|
||||
? 'If you wish to exceed the included usage, it is advised you upgrade to a paid plan.'
|
||||
? 'If you wish to exceed the included usage, you should upgrade to a paid plan.'
|
||||
: 'You can change the Cost Control settings if you plan on exceeding the included usage quotas.'}
|
||||
</p>
|
||||
</Alert>
|
||||
@@ -98,7 +98,7 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => {
|
||||
type="horizontal"
|
||||
value={daysWithinCycle - daysToCycleEnd}
|
||||
max={daysWithinCycle}
|
||||
barClass="bg-scale-1100"
|
||||
barClass="bg-scale-1200"
|
||||
labelBottom={`Current billing cycle (${billingCycleStart.format(
|
||||
'MMM DD'
|
||||
)} - ${billingCycleEnd.format('MMM DD')})`}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useParams } from 'common/hooks'
|
||||
import { FormHeader } from 'components/ui/Forms'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
|
||||
import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
|
||||
import { useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Alert, Button, Form, InputNumber, Modal } from 'ui'
|
||||
import { number, object } from 'yup'
|
||||
|
||||
export interface DiskSizeConfigurationProps {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) => {
|
||||
const { ui } = useStore()
|
||||
const { ref: projectRef } = useParams()
|
||||
|
||||
const canUpdateDiskSizeConfig = checkPermissions(PermissionAction.UPDATE, 'projects')
|
||||
|
||||
const [showResetDbPass, setShowResetDbPass] = useState<boolean>(false)
|
||||
const [isUpdatingDiskSize, setIsUpdatingDiskSize] = useState<boolean>(false)
|
||||
|
||||
const { data: projectUsage } = useProjectUsageQuery({ projectRef })
|
||||
const { data: projectSubscriptionData } = useProjectSubscriptionQuery({ projectRef })
|
||||
const { mutateAsync: updateProjectUsage } = useProjectDiskResizeMutation()
|
||||
|
||||
useEffect(() => {
|
||||
if (showResetDbPass) {
|
||||
setIsUpdatingDiskSize(false)
|
||||
}
|
||||
}, [showResetDbPass])
|
||||
|
||||
const confirmResetDbPass = async (values: { [prop: string]: any }) => {
|
||||
const volumeSize = values['new-disk-size']
|
||||
if (!projectRef) return
|
||||
|
||||
try {
|
||||
setIsUpdatingDiskSize(true)
|
||||
await updateProjectUsage({ projectRef, volumeSize })
|
||||
ui.setNotification({
|
||||
category: 'success',
|
||||
message: `Successfully updated disk size to ${values['new-disk-size']} GB`,
|
||||
})
|
||||
setIsUpdatingDiskSize(false)
|
||||
setShowResetDbPass(false)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({ category: 'error', message: error.message })
|
||||
setIsUpdatingDiskSize(false)
|
||||
}
|
||||
}
|
||||
|
||||
const currentDiskSize = projectUsage?.disk_volume_size_gb ?? 0
|
||||
// to do, update with max_disk_volume_size_gb
|
||||
const maxDiskSize = 200
|
||||
|
||||
const INITIAL_VALUES = {
|
||||
'new-disk-size': currentDiskSize,
|
||||
}
|
||||
|
||||
const diskSizeValidationSchema = object({
|
||||
'new-disk-size': number()
|
||||
.required('Please enter a GB amount you want to resize the disk up to.')
|
||||
.moreThan(Number(currentDiskSize ?? 0), `Must be more than ${currentDiskSize} GB`)
|
||||
// to do, update with max_disk_volume_size_gb
|
||||
.lessThan(Number(maxDiskSize), 'Must be no more than 200 GB'),
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormHeader title="Disk management" />
|
||||
{projectSubscriptionData?.tier.supabase_prod_id != 'tier_free' ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Panel className="!m-0">
|
||||
<Panel.Content>
|
||||
<div className="grid grid-cols-1 items-center lg:grid-cols-3">
|
||||
<div className="col-span-2 space-y-1">
|
||||
{projectUsage?.disk_volume_size_gb && (
|
||||
<span className="text-scale-1100 flex gap-2 items-baseline">
|
||||
<span className="text-scale-1200">Current Disk Storage Size:</span>
|
||||
<span className="text-scale-1200 text-xl">
|
||||
{currentDiskSize}
|
||||
<span className="text-scale-1200 text-sm">GB</span>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<p className="text-sm opacity-50">
|
||||
Supabase employs auto-scaling storage and allows for manual disk size <br />{' '}
|
||||
adjustments when necessary
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-end justify-end">
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
type="default"
|
||||
disabled={!canUpdateDiskSizeConfig || disabled}
|
||||
onClick={() => setShowResetDbPass(true)}
|
||||
>
|
||||
Increase disk size
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateDiskSizeConfig && (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to increase the disk size
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
<Alert withIcon variant="info" title={'Importing a lot of data?'}>
|
||||
<p className=" max-w-2xl">
|
||||
We auto-scale your disk as you need more storage, but can only do this every 6 hours.
|
||||
If you upload more than 1.5x the current size of your storage, your database will go
|
||||
into read-only mode. If you know how big your database is going to be, you can
|
||||
manually increase the size here.
|
||||
</p>
|
||||
|
||||
<p className="mt-4">
|
||||
Read more about{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://supabase.com/docs/guides/platform/database-size#disk-management"
|
||||
>
|
||||
disk management
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</Alert>
|
||||
</div>
|
||||
) : (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="info"
|
||||
title={'Disk size configuration is not available for projects on the Free tier'}
|
||||
actions={
|
||||
<Link href={`/project/${projectRef}/settings/billing/subscription`}>
|
||||
<Button type="default">Upgrade subscription</Button>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
If you are intending to use more than 500MB of disk space, then you will need to upgrade
|
||||
to at least the Pro tier.
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
header={<h5 className="text-sm text-scale-1200">Increase Disk Storage Size</h5>}
|
||||
size="small"
|
||||
visible={showResetDbPass}
|
||||
loading={isUpdatingDiskSize}
|
||||
onCancel={() => setShowResetDbPass(false)}
|
||||
hideFooter
|
||||
>
|
||||
<Form
|
||||
name={`disk-resize-form`}
|
||||
initialValues={INITIAL_VALUES}
|
||||
validationSchema={diskSizeValidationSchema}
|
||||
onSubmit={confirmResetDbPass}
|
||||
>
|
||||
{({ isSubmitting }: { isSubmitting: boolean }) =>
|
||||
currentDiskSize >= maxDiskSize ? (
|
||||
<>
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title={'Maximum manual disk size increase reached'}
|
||||
>
|
||||
You cannot manually expand the disk size any more than {maxDiskSize} GB. If you
|
||||
need more than this, contact us to learn more about the Enterprise plan.
|
||||
</Alert>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="w-full space-y-8 py-8">
|
||||
<InputNumber
|
||||
id="new-disk-size"
|
||||
label="New disk size"
|
||||
labelOptional="GB"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex space-x-2 justify-between pb-2">
|
||||
<Button type="default" onClick={() => setShowResetDbPass(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" type="primary" loading={isSubmitting}>
|
||||
Update disk size
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DiskSizeConfiguration
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { usageKeys } from '../usage/keys'
|
||||
|
||||
export type ProjectDiskResizeVariables = {
|
||||
projectRef: string
|
||||
volumeSize: number
|
||||
}
|
||||
|
||||
export type ProjectDiskResizeResponse = {
|
||||
error?: any
|
||||
}
|
||||
|
||||
export async function resizeProjectDisk({ projectRef, volumeSize }: ProjectDiskResizeVariables) {
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
|
||||
const payload = { volume_size_gb: volumeSize }
|
||||
|
||||
const response = (await post(
|
||||
`${API_URL}/projects/${projectRef}/resize`,
|
||||
payload
|
||||
)) as ProjectDiskResizeResponse
|
||||
if (response.error) throw response.error
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
type ProjectDiskResizeData = Awaited<ReturnType<typeof resizeProjectDisk>>
|
||||
|
||||
export const useProjectDiskResizeMutation = ({
|
||||
onSuccess,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<ProjectDiskResizeData, unknown, ProjectDiskResizeVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<ProjectDiskResizeData, unknown, ProjectDiskResizeVariables>(
|
||||
(vars) => resizeProjectDisk(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
|
||||
queryClient.setQueriesData(usageKeys.usage(projectRef), (prev) => {
|
||||
if (!prev) return prev
|
||||
|
||||
return {
|
||||
...prev,
|
||||
disk_volume_size_gb: variables.volumeSize,
|
||||
}
|
||||
})
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from 'components/interfaces/Settings/Database'
|
||||
|
||||
import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration'
|
||||
import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
|
||||
|
||||
const ProjectSettings: NextPageWithLayout = () => {
|
||||
const { ref: projectRef } = useParams()
|
||||
@@ -21,6 +22,7 @@ const ProjectSettings: NextPageWithLayout = () => {
|
||||
<DatabaseSettings projectRef={projectRef} />
|
||||
<ConnectionPooling />
|
||||
<SSLConfiguration />
|
||||
<DiskSizeConfiguration />
|
||||
{networkRestrictions && <NetworkRestrictions />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user