Merge pull request #14619 from supabase/feat/update-disk-size

Feat/update disk size
This commit is contained in:
Alaister Young authored and GitHub committed 2023-05-30 16:15:07 +10:00
commit fc4983e8a3
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>