This commit is contained in:
+1
-2
@@ -6,7 +6,6 @@ import { onMounted } from 'vue'
|
|||||||
import { useUpdateApp } from './composables/useUpdateApp.js'
|
import { useUpdateApp } from './composables/useUpdateApp.js'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const DEV = import.meta.env.DEV
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
useThemeColor()
|
useThemeColor()
|
||||||
@@ -19,7 +18,7 @@ onMounted(() => {
|
|||||||
<UContainer>
|
<UContainer>
|
||||||
<Header />
|
<Header />
|
||||||
<UMain class="p-4">
|
<UMain class="p-4">
|
||||||
<RouterView :key="route.fullPath" />
|
<RouterView :key="route.path" />
|
||||||
</UMain>
|
</UMain>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
</UApp>
|
</UApp>
|
||||||
|
|||||||
@@ -22,45 +22,115 @@ import {
|
|||||||
DB_SIZE_GREATER_RAM,
|
DB_SIZE_GREATER_RAM,
|
||||||
} from '../utils/constants/configuration'
|
} from '../utils/constants/configuration'
|
||||||
import { validationSchema as schema } from '../utils/validation'
|
import { validationSchema as schema } from '../utils/validation'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { calculateSettings } from '../utils/calculator'
|
||||||
|
|
||||||
const dbVersions = ref(DB_VERSIONS)
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const osTypes = ref([OS_LINUX, OS_WINDOWS, OS_MAC])
|
const query = route.query
|
||||||
|
|
||||||
|
const dbVersions = ref(
|
||||||
|
DB_VERSIONS.map((version) => ({
|
||||||
|
label: String(version),
|
||||||
|
value: version,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
const osTypes = ref([
|
||||||
|
{
|
||||||
|
label: 'Linux',
|
||||||
|
value: OS_LINUX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OS X',
|
||||||
|
value: OS_MAC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Windows',
|
||||||
|
value: OS_WINDOWS,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const dbTypes = ref([
|
const dbTypes = ref([
|
||||||
DB_TYPE_WEB,
|
{
|
||||||
DB_TYPE_OLTP,
|
label: 'Web application',
|
||||||
DB_TYPE_DW,
|
value: DB_TYPE_WEB,
|
||||||
DB_TYPE_DESKTOP,
|
},
|
||||||
DB_TYPE_MIXED,
|
{
|
||||||
|
label: 'Online transaction processing system',
|
||||||
|
value: DB_TYPE_OLTP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Data warehouse',
|
||||||
|
value: DB_TYPE_DW,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Desktop application',
|
||||||
|
value: DB_TYPE_DESKTOP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mixed type of application',
|
||||||
|
value: DB_TYPE_MIXED,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const ramUnits = ref([SIZE_UNIT_TB, SIZE_UNIT_GB, SIZE_UNIT_MB])
|
const ramUnits = ref([SIZE_UNIT_TB, SIZE_UNIT_GB, SIZE_UNIT_MB])
|
||||||
|
|
||||||
const dataStorages = ref([
|
const dataStorages = ref([
|
||||||
HARD_DRIVE_HDD,
|
{
|
||||||
HARD_DRIVE_SSD,
|
label: 'NVMe storage',
|
||||||
HARD_DRIVE_SAN,
|
value: HARD_DRIVE_NVME,
|
||||||
HARD_DRIVE_NVME,
|
},
|
||||||
|
{
|
||||||
|
label: 'SSD storage',
|
||||||
|
value: HARD_DRIVE_SSD,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Network (SAN) storage',
|
||||||
|
value: HARD_DRIVE_SAN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HDD storage',
|
||||||
|
value: HARD_DRIVE_HDD,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const dataSizes = ref([DB_SIZE_LESS_RAM, DB_SIZE_MID_RAM, DB_SIZE_GREATER_RAM])
|
const dataSizes = ref([
|
||||||
|
{
|
||||||
|
label: 'Less than RAM',
|
||||||
|
value: DB_SIZE_LESS_RAM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1x - 3x RAM',
|
||||||
|
value: DB_SIZE_MID_RAM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'More than 3x RAM',
|
||||||
|
value: DB_SIZE_GREATER_RAM,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dbVersion: 18,
|
dbVersion: query?.dbVersion ? parseInt(query.dbVersion) : 18,
|
||||||
osType: OS_LINUX,
|
osType: query?.osType ?? OS_LINUX,
|
||||||
dbType: DB_TYPE_WEB,
|
dbType: query?.dbType ?? DB_TYPE_WEB,
|
||||||
totalMemory: undefined,
|
totalMemory: query?.totalMemory ? parseInt(query.totalMemory) : undefined,
|
||||||
totalMemoryUnit: SIZE_UNIT_GB,
|
totalMemoryUnit: query?.totalMemoryUnit ?? SIZE_UNIT_GB,
|
||||||
cpuNum: 1,
|
cpuNum: query?.cpuNum ? parseInt(query.cpuNum) : 1,
|
||||||
connectionNum: 100,
|
connectionNum: query?.connectionNum ? parseInt(query.connectionNum) : 100,
|
||||||
hdType: HARD_DRIVE_NVME,
|
hdType: query?.hdType ?? HARD_DRIVE_NVME,
|
||||||
dbSize: DB_SIZE_LESS_RAM,
|
dbSize: query?.dbSize ?? DB_SIZE_LESS_RAM,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
await router.push({ name: 'Index', query: { ...state } })
|
||||||
|
console.log(calculateSettings({ ...state }))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UForm class="space-y-4" :state :schema>
|
<UForm class="space-y-4" :state :schema @submit="onSubmit">
|
||||||
<UFormField
|
<UFormField
|
||||||
label="DB version"
|
label="DB version"
|
||||||
help="PostgreSQL version (find out via 'SELECT version();')"
|
help="PostgreSQL version (find out via 'SELECT version();')"
|
||||||
|
|||||||
@@ -0,0 +1,565 @@
|
|||||||
|
import {
|
||||||
|
DEFAULT_DB_VERSION,
|
||||||
|
OS_LINUX,
|
||||||
|
OS_WINDOWS,
|
||||||
|
OS_MAC,
|
||||||
|
DB_TYPE_WEB,
|
||||||
|
DB_TYPE_OLTP,
|
||||||
|
DB_TYPE_DW,
|
||||||
|
DB_TYPE_DESKTOP,
|
||||||
|
DB_TYPE_MIXED,
|
||||||
|
SIZE_UNIT_GB,
|
||||||
|
HARD_DRIVE_SSD,
|
||||||
|
HARD_DRIVE_HDD,
|
||||||
|
HARD_DRIVE_SAN,
|
||||||
|
HARD_DRIVE_NVME,
|
||||||
|
DB_SIZE_LESS_RAM,
|
||||||
|
DB_SIZE_MID_RAM,
|
||||||
|
DB_SIZE_GREATER_RAM,
|
||||||
|
} from './constants/configuration'
|
||||||
|
|
||||||
|
const SIZE_UNIT_MAP = {
|
||||||
|
KB: 1024,
|
||||||
|
MB: 1048576,
|
||||||
|
GB: 1073741824,
|
||||||
|
TB: 1099511627776,
|
||||||
|
PB: 1125899906842624,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DB_SETTINGS = {
|
||||||
|
default: {
|
||||||
|
['max_worker_processes']: 8,
|
||||||
|
['max_parallel_workers_per_gather']: 2,
|
||||||
|
['max_parallel_workers']: 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateSettings({
|
||||||
|
dbVersion = DEFAULT_DB_VERSION,
|
||||||
|
osType = OS_LINUX,
|
||||||
|
dbType = DB_TYPE_WEB,
|
||||||
|
totalMemory = null,
|
||||||
|
totalMemoryUnit = SIZE_UNIT_GB,
|
||||||
|
cpuNum = null,
|
||||||
|
connectionNum = null,
|
||||||
|
hdType = HARD_DRIVE_SSD,
|
||||||
|
dbSize = DB_SIZE_MID_RAM,
|
||||||
|
} = {}) {
|
||||||
|
const totalMemoryInBytes = totalMemory * SIZE_UNIT_MAP[totalMemoryUnit]
|
||||||
|
const totalMemoryInKb = totalMemoryInBytes / SIZE_UNIT_MAP['KB']
|
||||||
|
const dbDefaultValues =
|
||||||
|
DEFAULT_DB_SETTINGS[dbVersion] || DEFAULT_DB_SETTINGS.default
|
||||||
|
const isConfigured = !!totalMemory
|
||||||
|
const maxConnections = connectionNum
|
||||||
|
? connectionNum
|
||||||
|
: {
|
||||||
|
[DB_TYPE_WEB]: 200,
|
||||||
|
[DB_TYPE_OLTP]: 300,
|
||||||
|
[DB_TYPE_DW]: 40,
|
||||||
|
[DB_TYPE_DESKTOP]: 20,
|
||||||
|
[DB_TYPE_MIXED]: 100,
|
||||||
|
}[dbType]
|
||||||
|
const effectiveCacheSize = {
|
||||||
|
[DB_TYPE_WEB]: Math.floor((totalMemoryInKb * 3) / 4),
|
||||||
|
[DB_TYPE_OLTP]: Math.floor((totalMemoryInKb * 3) / 4),
|
||||||
|
[DB_TYPE_DW]: Math.floor((totalMemoryInKb * 3) / 4),
|
||||||
|
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryInKb / 4),
|
||||||
|
[DB_TYPE_MIXED]: Math.floor((totalMemoryInKb * 3) / 4),
|
||||||
|
}[dbType]
|
||||||
|
const checkpointSegments = [
|
||||||
|
{
|
||||||
|
key: 'min_wal_size',
|
||||||
|
value: {
|
||||||
|
[DB_TYPE_WEB]: (1024 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_OLTP]: (2048 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_DW]: (4096 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_DESKTOP]: (100 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_MIXED]: (1024 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
}[dbType],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'max_wal_size',
|
||||||
|
value: {
|
||||||
|
[DB_TYPE_WEB]: (4096 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_OLTP]: (8192 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_DW]: (16384 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_DESKTOP]: (2048 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
[DB_TYPE_MIXED]: (4096 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'],
|
||||||
|
}[dbType],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const checkpointCompletionTarget = 0.9
|
||||||
|
const defaultStatisticsTarget = {
|
||||||
|
[DB_TYPE_WEB]: 100,
|
||||||
|
[DB_TYPE_OLTP]: 100,
|
||||||
|
[DB_TYPE_DW]: 500,
|
||||||
|
[DB_TYPE_DESKTOP]: 100,
|
||||||
|
[DB_TYPE_MIXED]: 100,
|
||||||
|
}[dbType]
|
||||||
|
const walLevel =
|
||||||
|
dbType === DB_TYPE_DESKTOP
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: 'wal_level',
|
||||||
|
value: 'minimal',
|
||||||
|
},
|
||||||
|
// max_wal_senders must be 0 when wal_level=minimal
|
||||||
|
{
|
||||||
|
key: 'max_wal_senders',
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
const jit =
|
||||||
|
dbVersion >= 12 &&
|
||||||
|
[DB_TYPE_WEB, DB_TYPE_OLTP, DB_TYPE_MIXED].includes(dbType)
|
||||||
|
? 'off'
|
||||||
|
: null
|
||||||
|
const sharedBuffers = selectSharedBuffers(
|
||||||
|
totalMemoryInKb,
|
||||||
|
dbType,
|
||||||
|
osType,
|
||||||
|
dbVersion,
|
||||||
|
)
|
||||||
|
const hugePages = selectHugePages(sharedBuffers, osType)
|
||||||
|
const maintenanceWorkMem = selectMaintenanceWorkMem(
|
||||||
|
totalMemoryInKb,
|
||||||
|
dbType,
|
||||||
|
osType,
|
||||||
|
dbVersion,
|
||||||
|
)
|
||||||
|
const walBuffers = selectWalBuffers(sharedBuffers)
|
||||||
|
const randomPageCost = selectRandomPageCost(hdType, dbSize, dbType)
|
||||||
|
const effectiveIoConcurrency = selectEffectiveIoConcurrency(osType, hdType)
|
||||||
|
const parallelSettings = selectParallelSettings(dbVersion, dbType, cpuNum)
|
||||||
|
const workMem = selectWorkMem(
|
||||||
|
totalMemoryInKb,
|
||||||
|
sharedBuffers,
|
||||||
|
maxConnections,
|
||||||
|
parallelSettings,
|
||||||
|
dbDefaultValues,
|
||||||
|
dbType,
|
||||||
|
osType,
|
||||||
|
dbVersion,
|
||||||
|
dbSize,
|
||||||
|
)
|
||||||
|
const walCompression = selectWalCompression(dbVersion)
|
||||||
|
const autovacuumMaxWorkers = selectAutovacuumMaxWorkers(cpuNum)
|
||||||
|
const autovacuumWorkMem = selectAutovacuumWorkMem(
|
||||||
|
maintenanceWorkMem,
|
||||||
|
osType,
|
||||||
|
dbVersion,
|
||||||
|
)
|
||||||
|
const ioMethod = selectIoMethod(dbVersion, osType)
|
||||||
|
const ioWorkers = selectIoWorkers(dbVersion, cpuNum, ioMethod)
|
||||||
|
const warningInfoMessages = selectWarningInfoMessages(
|
||||||
|
totalMemoryInBytes,
|
||||||
|
walCompression,
|
||||||
|
ioMethod,
|
||||||
|
dbType,
|
||||||
|
hdType,
|
||||||
|
dbSize,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalMemoryInBytes,
|
||||||
|
totalMemoryInKb,
|
||||||
|
dbDefaultValues,
|
||||||
|
isConfigured,
|
||||||
|
maxConnections,
|
||||||
|
effectiveCacheSize,
|
||||||
|
checkpointSegments,
|
||||||
|
checkpointCompletionTarget,
|
||||||
|
defaultStatisticsTarget,
|
||||||
|
walLevel,
|
||||||
|
jit,
|
||||||
|
sharedBuffers,
|
||||||
|
hugePages,
|
||||||
|
walBuffers,
|
||||||
|
randomPageCost,
|
||||||
|
effectiveIoConcurrency,
|
||||||
|
parallelSettings,
|
||||||
|
workMem,
|
||||||
|
walCompression,
|
||||||
|
autovacuumMaxWorkers,
|
||||||
|
autovacuumWorkMem,
|
||||||
|
ioMethod,
|
||||||
|
ioWorkers,
|
||||||
|
warningInfoMessages,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectSharedBuffers(totalMemoryKb, dbType, osType, dbVersion) {
|
||||||
|
let sharedBuffersValue = {
|
||||||
|
[DB_TYPE_WEB]: Math.floor(totalMemoryKb / 4),
|
||||||
|
[DB_TYPE_OLTP]: Math.floor(totalMemoryKb / 4),
|
||||||
|
[DB_TYPE_DW]: Math.floor(totalMemoryKb / 4),
|
||||||
|
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryKb / 16),
|
||||||
|
[DB_TYPE_MIXED]: Math.floor(totalMemoryKb / 4),
|
||||||
|
}[dbType]
|
||||||
|
if (dbVersion < 10 && OS_WINDOWS === osType) {
|
||||||
|
// Limit shared_buffers to 512MB on Windows
|
||||||
|
const winMemoryLimit = (512 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (sharedBuffersValue > winMemoryLimit) {
|
||||||
|
sharedBuffersValue = winMemoryLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sharedBuffersValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectHugePages(sharedBuffersValue, osType) {
|
||||||
|
// macOS does not support PostgreSQL huge pages
|
||||||
|
if (osType === OS_MAC) {
|
||||||
|
return 'off'
|
||||||
|
}
|
||||||
|
const hugePageThreshold = (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
return sharedBuffersValue >= hugePageThreshold ? 'try' : 'off'
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMaintenanceWorkMem(totalMemoryKb, dbType, osType, dbVersion) {
|
||||||
|
let maintenanceWorkMemValue = {
|
||||||
|
[DB_TYPE_WEB]: Math.floor(totalMemoryKb / 16),
|
||||||
|
[DB_TYPE_OLTP]: Math.floor(totalMemoryKb / 16),
|
||||||
|
[DB_TYPE_DW]: Math.floor(totalMemoryKb / 8),
|
||||||
|
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryKb / 16),
|
||||||
|
[DB_TYPE_MIXED]: Math.floor(totalMemoryKb / 16),
|
||||||
|
}[dbType]
|
||||||
|
// Default cap is 8GB on modern servers with lots of memory
|
||||||
|
let memoryLimit = (8 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
|
||||||
|
// Windows 64-bit has a strict 2GB limit up to PostgreSQL 17
|
||||||
|
if (OS_WINDOWS === osType && dbVersion <= 17) {
|
||||||
|
memoryLimit = (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maintenanceWorkMemValue >= memoryLimit) {
|
||||||
|
if (OS_WINDOWS === osType && dbVersion <= 17) {
|
||||||
|
// 2048MB (2 GB) will raise an error on Windows PG <= 17, so we need to remove 1 MB from it
|
||||||
|
maintenanceWorkMemValue =
|
||||||
|
memoryLimit - (1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
} else {
|
||||||
|
maintenanceWorkMemValue = memoryLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maintenanceWorkMemValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWalBuffers(sharedBuffersValue) {
|
||||||
|
// Follow auto-tuning guideline for wal_buffers added in 9.1, where it's
|
||||||
|
// set to 3% of shared_buffers up to a maximum of 16MB.
|
||||||
|
let walBuffersValue = Math.floor((3 * sharedBuffersValue) / 100)
|
||||||
|
const maxWalBuffer = (16 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (walBuffersValue > maxWalBuffer) {
|
||||||
|
walBuffersValue = maxWalBuffer
|
||||||
|
}
|
||||||
|
// It's nice of wal_buffers is an even 16MB if it's near that number. Since
|
||||||
|
// that is a common case on Windows, where shared_buffers is clipped to 512MB,
|
||||||
|
// round upwards in that situation
|
||||||
|
const walBufferNearValue = (14 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (walBuffersValue > walBufferNearValue && walBuffersValue < maxWalBuffer) {
|
||||||
|
walBuffersValue = maxWalBuffer
|
||||||
|
}
|
||||||
|
// if less, than 32 kb, than set it to minimum
|
||||||
|
if (walBuffersValue < 32) {
|
||||||
|
walBuffersValue = 32
|
||||||
|
}
|
||||||
|
return walBuffersValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectRandomPageCost(hdType, dbSize, dbType) {
|
||||||
|
// If the entire database fits in RAM, all read requests will be served
|
||||||
|
// from the OS page cache. Disk seek time (even for HDD) becomes irrelevant.
|
||||||
|
if (dbSize === DB_SIZE_LESS_RAM) {
|
||||||
|
return 1.1
|
||||||
|
}
|
||||||
|
|
||||||
|
// HDD always gets 4.0
|
||||||
|
if (hdType === HARD_DRIVE_HDD) {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Data Warehouses that don't fit in RAM, a low random_page_cost is dangerous.
|
||||||
|
// It causes the planner to pick index scans for large data retrievals, which
|
||||||
|
// are much slower on physical SSDs than sequential scans.
|
||||||
|
// Return 4.0 (the Postgres default) to prevent this harm
|
||||||
|
if (dbType === DB_TYPE_DW) {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.1
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectEffectiveIoConcurrency(osType, hdType) {
|
||||||
|
if (osType !== OS_LINUX) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
[HARD_DRIVE_HDD]: 2,
|
||||||
|
[HARD_DRIVE_SSD]: 200,
|
||||||
|
[HARD_DRIVE_SAN]: 300,
|
||||||
|
[HARD_DRIVE_NVME]: 1000,
|
||||||
|
}[hdType]
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectParallelSettings(dbVersion, dbType, cpuNum) {
|
||||||
|
if (!cpuNum || cpuNum < 4) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let workersPerGather = Math.ceil(cpuNum / 2)
|
||||||
|
|
||||||
|
if (dbType !== DB_TYPE_DW && workersPerGather > 4) {
|
||||||
|
workersPerGather = 4 // no clear evidence, that each new worker will provide big benefit for each noew core
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = [
|
||||||
|
{
|
||||||
|
key: 'max_worker_processes',
|
||||||
|
value: cpuNum,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'max_parallel_workers_per_gather',
|
||||||
|
value: workersPerGather,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (dbVersion >= 10) {
|
||||||
|
config.push({
|
||||||
|
key: 'max_parallel_workers',
|
||||||
|
value: cpuNum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbVersion >= 11) {
|
||||||
|
let parallelMaintenanceWorkers = Math.ceil(cpuNum / 2)
|
||||||
|
|
||||||
|
if (parallelMaintenanceWorkers > 4) {
|
||||||
|
parallelMaintenanceWorkers = 4 // no clear evidence, that each new worker will provide big benefit for each noew core
|
||||||
|
}
|
||||||
|
|
||||||
|
config.push({
|
||||||
|
key: 'max_parallel_maintenance_workers',
|
||||||
|
value: parallelMaintenanceWorkers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWorkMem(
|
||||||
|
totalMemoryKb,
|
||||||
|
sharedBuffersValue,
|
||||||
|
maxConnectionsValue,
|
||||||
|
parallelSettingsValue,
|
||||||
|
dbDefaultValues,
|
||||||
|
dbType,
|
||||||
|
osType,
|
||||||
|
dbVersion,
|
||||||
|
dbSize,
|
||||||
|
) {
|
||||||
|
const parallelForWorkMem = (() => {
|
||||||
|
if (parallelSettingsValue.length) {
|
||||||
|
const maxWorkerProcesses = parallelSettingsValue.find(
|
||||||
|
(param) => param['key'] === 'max_worker_processes',
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
maxWorkerProcesses &&
|
||||||
|
maxWorkerProcesses['value'] &&
|
||||||
|
maxWorkerProcesses['value'] > 0
|
||||||
|
) {
|
||||||
|
return maxWorkerProcesses['value']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
dbDefaultValues['max_worker_processes'] &&
|
||||||
|
dbDefaultValues['max_worker_processes'] > 0
|
||||||
|
) {
|
||||||
|
return dbDefaultValues['max_worker_processes']
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
})()
|
||||||
|
|
||||||
|
// work_mem is assigned any time a query calls for a sort, or a hash, or any other structure that needs a space allocation, which can happen multiple times per query. So you're better off assuming max_connections * 2 or max_connections * 3 is the amount of RAM that will actually use in reality. At the very least, you need to subtract shared_buffers from the amount you're distributing to connections in work_mem.
|
||||||
|
// The other thing to consider is that there's no reason to run on the edge of available memory. If you do that, there's a very high risk the out-of-memory killer will come along and start killing PostgreSQL backends. Always leave a buffer of some kind in case of spikes in memory usage. So your maximum amount of memory available in work_mem should be (RAM - shared_buffers) / ((max_connections + max_worker_processes) * 3).
|
||||||
|
const workMemValue =
|
||||||
|
(totalMemoryKb - sharedBuffersValue) /
|
||||||
|
((maxConnectionsValue + parallelForWorkMem) * 3)
|
||||||
|
|
||||||
|
let workMemResult = {
|
||||||
|
[DB_TYPE_WEB]: Math.floor(workMemValue),
|
||||||
|
[DB_TYPE_OLTP]: Math.floor(workMemValue),
|
||||||
|
[DB_TYPE_DW]: Math.floor(workMemValue / 2),
|
||||||
|
[DB_TYPE_DESKTOP]: Math.floor(workMemValue / 6),
|
||||||
|
[DB_TYPE_MIXED]: Math.floor(workMemValue / 2),
|
||||||
|
}[dbType]
|
||||||
|
|
||||||
|
if (dbSize === DB_SIZE_LESS_RAM) {
|
||||||
|
// OS Cache is underutilized because the DB fits entirely in RAM.
|
||||||
|
// We can safely boost work_mem by 30% to speed up sorts/hashes.
|
||||||
|
workMemResult = Math.floor(workMemResult * 1.3)
|
||||||
|
} else if (dbSize === DB_SIZE_GREATER_RAM) {
|
||||||
|
// OS Cache is under massive pressure. We must protect memory.
|
||||||
|
// Reduce work_mem by 10% to provide a wider safety net against OOM spikes.
|
||||||
|
workMemResult = Math.floor(workMemResult * 0.9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modern floor limit: 4 MB minimum to prevent disk-spill for simple queries
|
||||||
|
const minWorkMem = (4 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (workMemResult < minWorkMem) {
|
||||||
|
workMemResult = minWorkMem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows 64-bit has a strict 2GB limit up to PostgreSQL 17
|
||||||
|
if (osType === OS_WINDOWS && dbVersion <= 17) {
|
||||||
|
const winMemoryLimit =
|
||||||
|
(2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB'] -
|
||||||
|
(1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (workMemResult > winMemoryLimit) {
|
||||||
|
workMemResult = winMemoryLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return workMemResult
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWalCompression(dbVersion) {
|
||||||
|
// PG 15+ introduced lz4 and zstd support. lz4 is significantly faster
|
||||||
|
// and lighter on CPU than the native pglz algorithm. Standard package
|
||||||
|
// installations compile with lz4 support by default.
|
||||||
|
if (dbVersion >= 15) {
|
||||||
|
return 'lz4'
|
||||||
|
}
|
||||||
|
// For PG 10 through 14, 'on' safely enables pglz compression.
|
||||||
|
if (dbVersion >= 10) {
|
||||||
|
return 'on'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAutovacuumMaxWorkers(cpuNum) {
|
||||||
|
// PG default is 3. We only safely scale it up on heavier servers.
|
||||||
|
if (!cpuNum) return null
|
||||||
|
if (cpuNum >= 32) return 5
|
||||||
|
if (cpuNum >= 16) return 4
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAutovacuumWorkMem(maintenanceWorkMemValue, osType, dbVersion) {
|
||||||
|
let autovacuumWorkMemValue = null
|
||||||
|
|
||||||
|
// By default, autovacuum_work_mem is -1 (uses maintenance_work_mem).
|
||||||
|
// Because we safely raised maintenance_work_mem to up to 8GB, letting 3-5 background
|
||||||
|
// workers each consume 8GB would cause severe OOM risks. We explicitly cap
|
||||||
|
// background vacuum memory to 2GB max per worker.
|
||||||
|
const threshold = (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (maintenanceWorkMemValue >= threshold) {
|
||||||
|
autovacuumWorkMemValue = threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows 64-bit has a strict 2GB limit up to PostgreSQL 17
|
||||||
|
if (
|
||||||
|
autovacuumWorkMemValue !== null &&
|
||||||
|
osType === OS_WINDOWS &&
|
||||||
|
dbVersion <= 17
|
||||||
|
) {
|
||||||
|
const winMemoryLimit =
|
||||||
|
(2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB'] -
|
||||||
|
(1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
|
||||||
|
if (autovacuumWorkMemValue > winMemoryLimit) {
|
||||||
|
autovacuumWorkMemValue = winMemoryLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return autovacuumWorkMemValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectIoMethod(dbVersion, osType) {
|
||||||
|
// Asynchronous I/O was introduced in PostgreSQL 18
|
||||||
|
if (dbVersion < 18) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we assume standard modern packages (compiled with --with-liburing),
|
||||||
|
// io_uring is the absolute best performing AIO method for Linux.
|
||||||
|
if (osType === OS_LINUX) {
|
||||||
|
return 'io_uring'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows and macOS do not support io_uring natively, so we fall back
|
||||||
|
// to the safe cross-platform 'worker' processes.
|
||||||
|
return 'worker'
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectIoWorkers(dbVersion, cpuNum, ioMethod) {
|
||||||
|
// io_workers was introduced in PG18 for the new Asynchronous I/O subsystem
|
||||||
|
if (dbVersion < 18 || !cpuNum || ioMethod === 'io_uring') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default is 3. We conservatively scale it to ~25% of CPU cores to avoid
|
||||||
|
// lock contention on the AIO queue, capped at PostgreSQL's hard max of 32
|
||||||
|
const ioWorkersValue = Math.min(32, Math.max(3, Math.floor(cpuNum / 4)))
|
||||||
|
|
||||||
|
// Only explicitly configure it if we are recommending more than the default
|
||||||
|
return ioWorkersValue > 3 ? ioWorkersValue : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWarningInfoMessages(
|
||||||
|
totalMemory,
|
||||||
|
walCompression,
|
||||||
|
ioMethod,
|
||||||
|
dbType,
|
||||||
|
hdType,
|
||||||
|
dbSize,
|
||||||
|
) {
|
||||||
|
let warnings = []
|
||||||
|
|
||||||
|
// Memory warnings
|
||||||
|
if (totalMemory < 256 * SIZE_UNIT_MAP['MB']) {
|
||||||
|
warnings.push('this tool not being optimal', 'for low memory systems')
|
||||||
|
} else if (totalMemory > 100 * SIZE_UNIT_MAP['GB']) {
|
||||||
|
warnings.push('this tool not being optimal', 'for very high memory systems')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced features compilation warnings
|
||||||
|
if (walCompression === 'lz4') {
|
||||||
|
if (warnings.length > 0) warnings.push('')
|
||||||
|
|
||||||
|
warnings.push(
|
||||||
|
'wal_compression = lz4 requires PostgreSQL',
|
||||||
|
'to be compiled with --with-lz4',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioMethod === 'io_uring') {
|
||||||
|
if (warnings.length > 0) warnings.push('')
|
||||||
|
|
||||||
|
warnings.push(
|
||||||
|
'io_method = io_uring requires PostgreSQL',
|
||||||
|
'to be compiled with --with-liburing',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// I/O Cost Warning for Analytical DBs
|
||||||
|
if (
|
||||||
|
dbType === DB_TYPE_DW &&
|
||||||
|
hdType !== HARD_DRIVE_HDD &&
|
||||||
|
dbSize !== DB_SIZE_LESS_RAM
|
||||||
|
) {
|
||||||
|
const driveName = {
|
||||||
|
[HARD_DRIVE_SSD]: 'SSDs',
|
||||||
|
[HARD_DRIVE_NVME]: 'NVMe drives',
|
||||||
|
[HARD_DRIVE_SAN]: 'SAN storage',
|
||||||
|
}[hdType]
|
||||||
|
|
||||||
|
if (warnings.length > 0) warnings.push('')
|
||||||
|
|
||||||
|
warnings.push(
|
||||||
|
`Cost parameters for Data Warehouses on ${driveName} are left at defaults`,
|
||||||
|
'to avoid catastrophic index scan selections',
|
||||||
|
'Monitor query planner behavior and adjust random_page_cost if necessary',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings.length > 0 ? ['WARNING', ...warnings] : []
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user