This commit is contained in:
+1
-2
@@ -6,7 +6,6 @@ import { onMounted } from 'vue'
|
||||
import { useUpdateApp } from './composables/useUpdateApp.js'
|
||||
|
||||
const route = useRoute()
|
||||
const DEV = import.meta.env.DEV
|
||||
|
||||
onMounted(() => {
|
||||
useThemeColor()
|
||||
@@ -19,7 +18,7 @@ onMounted(() => {
|
||||
<UContainer>
|
||||
<Header />
|
||||
<UMain class="p-4">
|
||||
<RouterView :key="route.fullPath" />
|
||||
<RouterView :key="route.path" />
|
||||
</UMain>
|
||||
</UContainer>
|
||||
</UApp>
|
||||
|
||||
@@ -22,45 +22,115 @@ import {
|
||||
DB_SIZE_GREATER_RAM,
|
||||
} from '../utils/constants/configuration'
|
||||
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([
|
||||
DB_TYPE_WEB,
|
||||
DB_TYPE_OLTP,
|
||||
DB_TYPE_DW,
|
||||
DB_TYPE_DESKTOP,
|
||||
DB_TYPE_MIXED,
|
||||
{
|
||||
label: 'Web application',
|
||||
value: DB_TYPE_WEB,
|
||||
},
|
||||
{
|
||||
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 dataStorages = ref([
|
||||
HARD_DRIVE_HDD,
|
||||
HARD_DRIVE_SSD,
|
||||
HARD_DRIVE_SAN,
|
||||
HARD_DRIVE_NVME,
|
||||
{
|
||||
label: 'NVMe storage',
|
||||
value: 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({
|
||||
dbVersion: 18,
|
||||
osType: OS_LINUX,
|
||||
dbType: DB_TYPE_WEB,
|
||||
totalMemory: undefined,
|
||||
totalMemoryUnit: SIZE_UNIT_GB,
|
||||
cpuNum: 1,
|
||||
connectionNum: 100,
|
||||
hdType: HARD_DRIVE_NVME,
|
||||
dbSize: DB_SIZE_LESS_RAM,
|
||||
dbVersion: query?.dbVersion ? parseInt(query.dbVersion) : 18,
|
||||
osType: query?.osType ?? OS_LINUX,
|
||||
dbType: query?.dbType ?? DB_TYPE_WEB,
|
||||
totalMemory: query?.totalMemory ? parseInt(query.totalMemory) : undefined,
|
||||
totalMemoryUnit: query?.totalMemoryUnit ?? SIZE_UNIT_GB,
|
||||
cpuNum: query?.cpuNum ? parseInt(query.cpuNum) : 1,
|
||||
connectionNum: query?.connectionNum ? parseInt(query.connectionNum) : 100,
|
||||
hdType: query?.hdType ?? HARD_DRIVE_NVME,
|
||||
dbSize: query?.dbSize ?? DB_SIZE_LESS_RAM,
|
||||
})
|
||||
|
||||
const onSubmit = async () => {
|
||||
await router.push({ name: 'Index', query: { ...state } })
|
||||
console.log(calculateSettings({ ...state }))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm class="space-y-4" :state :schema>
|
||||
<UForm class="space-y-4" :state :schema @submit="onSubmit">
|
||||
<UFormField
|
||||
label="DB 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