Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 104 additions & 16 deletions apps/docs/content/guides/platform/upgrading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Existing projects on pg_graphql 1.5.x are not impacted unless they choose to upg

### Ltree indexes require reindexing after upgrade

_Applies when upgrading to Postgres 15.18 or 17.10._
_Applies when upgrading to Postgres 15.19 or 17.11._

<Admonition type="caution">

Expand Down Expand Up @@ -221,29 +221,115 @@ To mitigate this issue:
2. If `reindex_required` is `true`, find the affected indexes:

```sql
select schemaname, tablename, indexname
from pg_indexes
select distinct
n.nspname as schema_name,
cls.relname as table_name,
ic.relname as index_name
from pg_index idx
join pg_class ic on idx.indexrelid = ic.oid
join pg_class cls on idx.indrelid = cls.oid
join pg_namespace n on ic.relnamespace = n.oid
join lateral unnest(idx.indclass::oid[]) with ordinality as k(opclass, pos) on true
join pg_opclass oc on oc.oid = k.opclass
join pg_type ty on ty.oid = oc.opcintype
where
indexname in (
select c.relname
from
pg_index as i
join pg_class as c on i.indexrelid = c.oid
join pg_attribute as a on a.attrelid = i.indrelid and a.attnum = ANY(i.indkey)
join pg_type as t on a.atttypid = t.oid
where t.typname in ('ltree', '_ltree')
);
k.pos <= idx.indnkeyatts -- key columns only, excludes INCLUDE
and ty.typname in ('ltree', '_ltree');
```

3. Reindex each affected index. `REINDEX INDEX CONCURRENTLY` runs online with no downtime:
3. Reindex each affected index using its schema-qualified name. `REINDEX INDEX CONCURRENTLY` runs online with no downtime, but cannot run inside a transaction block:

```sql
REINDEX INDEX CONCURRENTLY <index_name>;
REINDEX INDEX CONCURRENTLY <schema_name>.<index_name>;
```

Separately from the encoding case above, this release also fixes an integer overflow in `ltree` comparisons: values with more than about 14,653 labels could compare incorrectly, which can corrupt B-tree indexes built over them, regardless of your database encoding. The following query lists only the B-tree indexes whose `ltree` column or expression actually contains such values, so they are the ones to reindex (an empty result means no action is needed):

```sql
SELECT s.schema_name || '.' || s.index_name AS index_to_reindex
FROM (
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
ic.relname AS index_name,
min(pg_get_expr(i.indpred, i.indrelid)) AS pred, -- partial-index predicate, if any
string_agg('nlevel(' || pg_get_indexdef(i.indexrelid, k.pos::int, true) || ') > 14653', ' OR ') AS keys_cond
FROM pg_index i
JOIN pg_class ic ON ic.oid = i.indexrelid
JOIN pg_class c ON c.oid = i.indrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_am am ON am.oid = ic.relam
JOIN LATERAL generate_series(1, i.indnkeyatts) AS k(pos) ON true
JOIN pg_attribute ia ON ia.attrelid = i.indexrelid AND ia.attnum = k.pos
JOIN pg_type t ON t.oid = ia.atttypid
WHERE am.amname = 'btree'
AND t.typname = 'ltree'
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
GROUP BY n.nspname, c.relname, ic.relname
) s
WHERE (xpath(
'/row/cnt/text()',
query_to_xml(
format('SELECT count(*) AS cnt FROM %I.%I WHERE %s(%s)',
s.schema_name, s.table_name,
CASE WHEN s.pred IS NOT NULL THEN '(' || s.pred || ') AND ' ELSE '' END,
s.keys_cond),
false, true, ''
)
))[1]::text::bigint > 0
ORDER BY 1;
```

Reindex each index it returns, using the schema-qualified name. `REINDEX INDEX CONCURRENTLY` runs online with no downtime, but cannot run inside a transaction block:

```sql
REINDEX INDEX CONCURRENTLY <schema_name>.<index_name>;
```

### Btree_gist indexes on float columns require reindexing after upgrade

_Applies when upgrading to Postgres 15.19 or 17.11._

<Admonition type="caution">

You are affected only if you have `btree_gist` indexes on `float4` or `float8` columns that may contain `NaN` values.

</Admonition>

This release fixes `NaN` handling in `btree_gist`'s `float4` and `float8` operator classes. Indexes on those columns built under the previous version can return wrong results for rows containing `NaN` until the index is rebuilt.

To mitigate this issue:

1. Find `btree_gist` indexes on float columns:

```sql
select distinct
n.nspname as schema_name,
cls.relname as table_name,
ic.relname as index_name
from pg_index idx
join pg_class ic on idx.indexrelid = ic.oid
join pg_am am on ic.relam = am.oid
join pg_class cls on idx.indrelid = cls.oid
join pg_namespace n on ic.relnamespace = n.oid
join lateral unnest(idx.indclass::oid[]) with ordinality as k(opclass, pos) on true
join pg_opclass oc on oc.oid = k.opclass
join pg_type ty on ty.oid = oc.opcintype
where
am.amname = 'gist'
and k.pos <= idx.indnkeyatts
and ty.typname in ('float4', 'float8');
```

2. If any indexes are returned and those columns may contain `NaN` values, reindex them using the schema-qualified name. `REINDEX INDEX CONCURRENTLY` runs online with no downtime, but cannot run inside a transaction block:

```sql
REINDEX INDEX CONCURRENTLY <schema_name>.<index_name>;
```

### Custom operator selectivity estimators

_Applies when upgrading to Postgres 15.18 or 17.10._
_Applies when upgrading to Postgres 15.19 or 17.11._

Attaching a non-built-in (extension- or user-provided) selectivity estimator function to an operator now requires superuser. Existing operators continue to work — the check only fires when an operator is (re)created, most commonly during `pg_dump` / `pg_restore`, a logical restore, or a branch.

Expand All @@ -256,7 +342,9 @@ ERROR: must be superuser to specify a non-built-in restriction estimator functio
Most projects are not affected. To check whether your database has any user-defined operators that reference a non-built-in estimator:

```sql
SELECT n.nspname AS schema, o.oprname AS operator
SELECT n.nspname AS schema, o.oprname AS operator,
o.oprrest::regproc AS restrict_estimator,
o.oprjoin::regproc AS join_estimator
FROM pg_operator o
JOIN pg_namespace n ON o.oprnamespace = n.oid
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,8 @@ export const FeaturePreviewModal = () => {
? allFeaturePreviews.filter((x) => x.category === undefined)
: allFeaturePreviews.filter((x) => x.category === category)
return (
<AccordionItem
key={category}
value={category}
className="data-[state=open]:border-b-0"
>
<AccordionTrigger className="text-xs font-mono uppercase tracking-tight px-4 text-foreground-lighter py-2">
<AccordionItem key={category} value={category}>
<AccordionTrigger className="text-xs font-mono uppercase tracking-tight px-4 text-foreground-lighter py-2 bg-tertiary dark:bg-transparent">
{category}
</AccordionTrigger>
<AccordionContent className="[&>div]:pb-0">
Expand Down Expand Up @@ -320,8 +316,10 @@ const FeaturePreviewItem = ({
key={feature.key}
onClick={() => selectFeaturePreview(feature.key)}
className={cn(
'w-full! flex-1 flex items-center justify-between p-4 border-b cursor-pointer bg transition',
selectedFeature?.key === feature.key ? 'bg-accent' : 'bg-card',
'w-full! flex-1 flex items-center justify-between p-4 cursor-pointer bg transition',
selectedFeature?.key === feature.key
? 'bg-muted dark:bg-accent text-foreground'
: 'bg-card text-foreground-light',
className
)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ListGitHubConnectionsResponse = platformComponents['schemas']['ListGitHubCo
type GetGitHubConnectionConfigResponse =
platformComponents['schemas']['GetGitHubConnectionConfigResponse']
type BranchResponse = apiV1Components['schemas']['BranchResponse']
type V2ProjectConfigResponse = apiV2Components['schemas']['V2ProjectConfigResponse']
type V2ProjectConfigResponse = apiV2Components['schemas']['V2ProjectConfigResponse_Output']

const PROJECT_REF = 'default'
const ORGANIZATION_ID = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export function DiskSection({
<DocsButton href={`${DOCS_URL}/guides/platform/database-size`} />
</PageSectionAside>
</PageSectionMeta>

<HighAvailabilityDisabledSectionNotice title="Disk management is unavailable for High Availability projects" />

<PageSectionContent ref={settingsRef} className="flex flex-col gap-4 scroll-mt-24">
{isAws && <DiskSpaceBar form={form} />}

Expand Down Expand Up @@ -255,7 +258,7 @@ export function AdvancedSection({
<PageSectionContent className="flex flex-col gap-4">
<Card ref={autoscaleSettingsRef} className="scroll-mt-24">
<CardContent className="flex flex-col gap-y-8">
<AutoScaleFields form={form} />
<AutoScaleFields form={form} disableInput={disableDiskInputs && disableDiskSizeInput} />
</CardContent>
</Card>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ import { AddonVariantId } from '@/data/subscriptions/types'
import { useResourceWarningsQuery } from '@/data/usage/resource-warnings-query'
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useHighAvailability } from '@/hooks/misc/useHighAvailability'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import {
useIsAwsCloudProvider,
useIsAwsK8sCloudProvider,
useIsAwsNimbusCloudProvider,
useIsHighAvailability,
useSelectedProjectQuery,
} from '@/hooks/misc/useSelectedProject'
import { GB, PROJECT_STATUS } from '@/lib/constants'
Expand Down Expand Up @@ -102,7 +102,7 @@ export function DiskManagementForm({
const isAws = useIsAwsCloudProvider()
const isAwsK8s = useIsAwsK8sCloudProvider()
const isAwsNimbus = useIsAwsNimbusCloudProvider()
const { isHighAvailability } = useHighAvailability()
const isHighAvailability = useIsHighAvailability()

const { can: canUpdateDiskConfiguration, isSuccess: isPermissionsLoaded } =
useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', {
Expand Down Expand Up @@ -209,9 +209,10 @@ export function DiskManagementForm({
const usedPercentage = (usedSize / totalSize) * 100

const disableIopsThroughputConfig =
modifiedComputeSize &&
!isSpendCapEnabled &&
RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)
isHighAvailability ||
(modifiedComputeSize &&
!isSpendCapEnabled &&
RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize))

const watchedTotalSize = useWatch({ control: form.control, name: 'totalSize' }) ?? 0
const watchedStorageType = useWatch({ control: form.control, name: 'storageType' })
Expand All @@ -231,10 +232,11 @@ export function DiskManagementForm({
isRequestingChanges ||
isPlanUpgradeRequired ||
isWithinCooldownWindow ||
isHighAvailability ||
!canUpdateDiskConfiguration ||
!isAws

const disableDiskInputs = disableDiskSizeInput || isSpendCapEnabled
const disableDiskInputs = disableDiskSizeInput || isSpendCapEnabled || isHighAvailability

// Compute resizing is not supported for High Availability projects during Alpha
const disableComputeInputs = isPlanUpgradeRequired || isHighAvailability
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import { useDiskAutoscaleCustomConfigQuery } from '@/data/config/disk-autoscale-

type AutoScaleFieldProps = {
form: UseFormReturn<DiskStorageSchemaType>
disableInput?: boolean
}

export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
export const AutoScaleFields = ({ form, disableInput = false }: AutoScaleFieldProps) => {
const { ref: projectRef } = useParams()
const {
control,
Expand Down Expand Up @@ -76,7 +77,7 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
id={field.name}
type="number"
value={field.value ?? undefined}
disabled={isError}
disabled={disableInput || isError}
onChange={(e) => {
setValue(
'growthPercent',
Expand Down Expand Up @@ -123,7 +124,7 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
id={field.name}
type="number"
value={field.value ?? undefined}
disabled={isError}
disabled={disableInput || isError}
onChange={(e) => {
setValue(
'minIncrementGb',
Expand Down Expand Up @@ -165,7 +166,7 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
id={field.name}
type="number"
value={field.value ?? undefined}
disabled={isError}
disabled={disableInput || isError}
onChange={(e) => {
setValue('maxSizeGb', e.target.value === '' ? null : e.target.valueAsNumber, {
shouldDirty: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ vi.mock('@/hooks/misc/useSelectedProject', () => ({
data: { ref: 'default', connectionString: 'postgres://localhost' },
}),
useIsOrioleDb: () => false,
useIsHighAvailability: () => false,
}))

vi.mock('common', async (importOriginal) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ vi.mock('@/hooks/misc/useSelectedProject', () => ({
useSelectedProjectQuery: () => ({
data: { ref: 'default', connectionString: 'postgres://localhost' },
}),
useIsHighAvailability: () => false,
}))

vi.mock('@/hooks/useProtectedSchemas', () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useParams } from 'common'
import { IS_PLATFORM, useParams } from 'common'
import { Check, Plus } from 'lucide-react'
import Link from 'next/link'
import {
Expand All @@ -13,6 +13,7 @@ import { getAddReadReplicaPath } from '@/components/interfaces/Settings/Infrastr
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { formatDatabaseID, formatDatabaseRegion } from '@/data/read-replicas/replicas.utils'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useIsHighAvailability } from '@/hooks/misc/useSelectedProject'

/** The label a database row/summary shows: the primary, or a replica by region + id. */
function databaseLabel(identifier: string, region: string, projectRef: string | undefined) {
Expand All @@ -28,6 +29,7 @@ export const DatabaseParametersSubMenu = ({
onIdentifierChange: (identifier: string) => void
}) => {
const { ref: projectRef } = useParams()
const isHighAvailability = useIsHighAvailability()
const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas'])

const { data } = useReadReplicasQuery({ projectRef })
Expand Down Expand Up @@ -68,7 +70,7 @@ export const DatabaseParametersSubMenu = ({
</DropdownMenuItem>
)
})}
{infrastructureReadReplicas && (
{IS_PLATFORM && infrastructureReadReplicas && !isHighAvailability && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem asChild className="gap-x-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ vi.mock('@/hooks/misc/useHighAvailability', () => ({

vi.mock('@/hooks/misc/useSelectedProject', () => ({
useSelectedProjectQuery: mockUseSelectedProjectQuery,
useIsHighAvailability: () => mockUseHighAvailability().isHighAvailability ?? false,
}))

vi.mock('@/data/database/max-connections-query', () => ({
Expand Down
Loading
Loading