This commit is contained in:
2026-09-06 04:04:59 +03:00
commit 83a975ba3c
44 changed files with 6558 additions and 0 deletions
+479
View File
@@ -0,0 +1,479 @@
import { defineStore } from 'pinia'
import { reactive, computed } from 'vue'
import { useSettingsStore } from './useSettingsStore'
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 '/src/utils/constants/configuration.js'
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,
},
}
const initialState = {
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 selectCheckpointCompletionTarget = 0.9
export const useConfigurationStore = defineStore('configuration', () => {
const state = reactive({ ...initialState })
const getConfiguration = computed(() => ({ ...state }))
const getDBVersion = computed(() => getConfiguration.value.dbVersion)
const getOSType = computed(() => getConfiguration.value.osType)
const getDBType = computed(() => getConfiguration.value.dbType)
const getTotalMemory = computed(() => getConfiguration.value.totalMemory)
const getTotalMemoryUnit = computed(
() => getConfiguration.value.totalMemoryUnit,
)
const getCPUNum = computed(() => getConfiguration.value.cpuNum || null)
const getConnectionNum = computed(
() => getConfiguration.value.connectionNum || null,
)
const getHDType = computed(() => getConfiguration.value.hdType)
const getDBSize = computed(() => getConfiguration.value.dbSize)
const getTotalMemoryInBytes = computed(
() => getTotalMemory.value * SIZE_UNIT_MAP[getTotalMemoryUnit.value],
)
const getTotalMemoryInKb = computed(
() => getTotalMemoryInBytes.value / SIZE_UNIT_MAP['KB'],
)
const getDBDefaultValues = computed(
() => DEFAULT_DB_SETTINGS[getDBVersion] || DEFAULT_DB_VERSION.default,
)
const getIsConfigured = computed(() => !!getTotalMemory.value)
const getMaxConnections = computed(
() =>
getConnectionNum.value ??
{
[DB_TYPE_WEB]: 200,
[DB_TYPE_OLTP]: 300,
[DB_TYPE_DW]: 40,
[DB_TYPE_DESKTOP]: 20,
[DB_TYPE_MIXED]: 100,
}[getDBType.value],
)
const getSharedBuffers = computed(() => {
const totalMemoryKb = getTotalMemoryInKb.value
const val = {
[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),
}[getDBType.value]
return getDBVersion.value < 10 && OS_WINDOWS === getOSType.value
? Math.min(val, (512 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB'])
: val
})
const getHugePages = computed(() =>
getOSType.value !== OS_MAC &&
getSharedBuffers.value >= (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
? 'try'
: 'off',
)
const getEffectiveCacheSize = computed(() => {
const totalMemoryKb = getTotalMemoryInKb.value
return {
[DB_TYPE_WEB]: Math.floor((totalMemoryKb * 3) / 4),
[DB_TYPE_OLTP]: Math.floor((totalMemoryKb * 3) / 4),
[DB_TYPE_DW]: Math.floor((totalMemoryKb * 3) / 4),
[DB_TYPE_DESKTOP]: Math.floor(totalMemoryKb / 4),
[DB_TYPE_MIXED]: Math.floor((totalMemoryKb * 3) / 4),
}[getDBType.value]
})
const getMaintenanceWorkMem = computed(() => {
const totalMemoryKb = getTotalMemoryInKb.value
// 1. Вычисляем базовое значение по типу БД
const baseMemValue = {
[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),
}[getDBType.value]
// 2. Определяем, действует ли строгое ограничение для Windows
const isLegacyWindows =
OS_WINDOWS === getOSType.value && getDBVersion.value <= 17
// 3. Задаем лимит памяти (2GB для старых Windows, иначе 8GB)
const maxLimitGb = isLegacyWindows ? 2 : 8
const memoryLimit = (maxLimitGb * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
// 4. Если превышен лимит на Windows, вычитаем 1MB, иначе просто возвращаем минимум
return baseMemValue >= memoryLimit && isLegacyWindows
? memoryLimit - (1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
: Math.min(baseMemValue, memoryLimit)
})
const getCheckpointSegment = computed(() => [
{
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'],
}[getDBType.value],
},
{
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'],
}[getDBType.value],
},
])
const getWalBuffers = computed(() => {
const KB_IN_MB = SIZE_UNIT_MAP['MB'] / SIZE_UNIT_MAP['KB']
const baseWal = Math.floor((3 * sharedBuffersValue) / 100)
return Math.max(
32,
baseWal > 14 * KB_IN_MB
? 16 * KB_IN_MB
: Math.min(baseWal, 16 * KB_IN_MB),
)
})
const getDefaultStatisticsTarget = computed(
() =>
({
[DB_TYPE_WEB]: 100,
[DB_TYPE_OLTP]: 100,
[DB_TYPE_DW]: 500,
[DB_TYPE_DESKTOP]: 100,
[DB_TYPE_MIXED]: 100,
})[getDBType.value],
)
const getRandomPageCost = computed(() => {
// Если база помещается в RAM, поиск на диске не важен
if (getDBSize.value === DB_SIZE_LESS_RAM) return 1.1
// HDD или аналитические БД (DW), не помещающиеся в RAM, требуют дефолтной стоимости (4.0)
if (getHDType.value === HARD_DRIVE_HDD || getDBType.value === DB_TYPE_DW)
return 4
return 1.1
})
const getEffectiveIOConcurrency = computed(() =>
getOSType.value === OS_LINUX
? {
[HARD_DRIVE_HDD]: 2,
[HARD_DRIVE_SSD]: 200,
[HARD_DRIVE_SAN]: 300,
[HARD_DRIVE_NVME]: 1000,
}[getHDType.value]
: null,
)
const getParallelSettings = computed(() => {
const cpuNum = getCPUNum.value
const dbVersion = getDBVersion.value
const dbType = getDBType.value
if (!cpuNum || cpuNum < 4) return []
const halfCpu = Math.ceil(cpuNum / 2)
const cappedWorkers = Math.min(4, halfCpu) // Ограничение в 4 воркера
const config = [
{ key: 'max_worker_processes', value: cpuNum },
{
key: 'max_parallel_workers_per_gather',
value: dbType !== DB_TYPE_DW ? cappedWorkers : halfCpu,
},
...(dbVersion >= 10
? [{ key: 'max_parallel_workers', value: cpuNum }]
: []),
...(dbVersion >= 11
? [{ key: 'max_parallel_maintenance_workers', value: cappedWorkers }]
: []),
]
return config
})
const getWorkMem = computed(() => {
const dbDefaultValues = getDBDefaultValues.value
const maxWorkerProcesses = getParallelSettings.value.find(
(param) => param['key'] === 'max_worker_processes',
)
const parallelForWorkMem =
maxWorkerProcesses?.value > 0
? maxWorkerProcesses.value
: dbDefaultValues['max_worker_processes'] > 0
? dbDefaultValues['max_worker_processes']
: 1
// Базовая формула расчёта work_mem
const baseWorkMem =
(getTotalMemoryInKb.value - sharedBuffersValue) /
((maxConnectionsValue + parallelForWorkMem) * 3)
// Коэффициенты для разных типов БД
const dbTypeCoefficients = {
[DB_TYPE_WEB]: 1,
[DB_TYPE_OLTP]: 1,
[DB_TYPE_DW]: 0.5,
[DB_TYPE_DESKTOP]: 1 / 6,
[DB_TYPE_MIXED]: 0.5,
}
// Базовая формула с коэффициентом типа БД
const baseWorkMemResult = Math.floor(
baseWorkMem * dbTypeCoefficients[dbType],
)
// Корректировка в зависимости от размера БД относительно RAM
const sizeAdjustedWorkMem =
dbSize === DB_SIZE_LESS_RAM
? Math.floor(baseWorkMemResult * 1.3)
: dbSize === DB_SIZE_GREATER_RAM
? Math.floor(baseWorkMemResult * 0.9)
: baseWorkMemResult
// Применяем минимальное значение 4 MB
const minWorkMem = 4 * 1024 // 4 MB в KB
const workMemWithMin = Math.max(sizeAdjustedWorkMem, minWorkMem)
// Применяем ограничение для Windows 64-bit до PostgreSQL 17
const winMemoryLimit = 2 * 1024 * 1024 - 1024 // ~2GB - 1MB в KB
const workMemResult =
osType === OS_WINDOWS && dbVersion <= 17
? Math.min(workMemWithMin, winMemoryLimit)
: workMemWithMin
return workMemResult
})
const getWalLevel = computed(() =>
getDBType.value === 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 getJit = computed(() =>
getDBVersion.value >= 12 &&
[DB_TYPE_WEB, DB_TYPE_OLTP, DB_TYPE_MIXED].includes(getDBType.value)
? 'off'
: null,
)
const getWalCompression = computed(() => {
const ver = getDBVersion.value
return ver >= 15 ? 'lz4' : ver >= 10 ? 'on' : null
})
const getAutovacuumMaxWorkers = computed(() => {
const cpuNum = getCPUNum.value
if (!cpuNum) return null
if (cpuNum >= 32) return 5
if (cpuNum >= 16) return 4
return null
})
const getAutovacuumWorkMem = computed(() => {
const threshold = (2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB']
// Windows 64-bit has a strict 2GB limit up to PostgreSQL 17
const winMemoryLimit =
(2 * SIZE_UNIT_MAP['GB']) / SIZE_UNIT_MAP['KB'] -
(1 * SIZE_UNIT_MAP['MB']) / SIZE_UNIT_MAP['KB']
const isWindowsWithLowLimit =
getOSType.value === OS_WINDOWS && getDBVersion.value <= 17
if (getMaintenanceWorkMem.value < threshold) {
return null
}
if (isWindowsWithLowLimit) {
return Math.min(threshold, winMemoryLimit)
}
return threshold
})
const getIOMethod = computed(() =>
getDBVersion.value < 18
? null
: getOSType.value === OS_LINUX
? 'io_uring'
: 'worker',
)
const getIOWorkers = computed(() => {
if (dbVersion < 18 || !cpuNum || ioMethod === 'io_uring') return null
return Math.max(4, Math.min(32, Math.floor(cpuNum / 4))) || null
})
const getWarningInfoMessages = computed(() => {
const warnings = []
const totalMemory = getTotalMemoryInBytes.value
// 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 (getWalCompression.value === 'lz4') {
if (warnings.length > 0) warnings.push('')
warnings.push(
'wal_compression = lz4 requires PostgreSQL',
'to be compiled with --with-lz4',
)
}
if (getIOMethod.value === '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 (
getDBType.value === DB_TYPE_DW &&
getHDType.value !== HARD_DRIVE_HDD &&
getDBSize.value !== 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] : []
})
function setState(payload) {
const settingsStore = useSettingsStore()
state.value = {
dbVersion: parseFloat(payload.dbVersion),
osType: payload.osType,
dbType: payload.dbType,
totalMemory: parseInt(payload.totalMemory, 10),
totalMemoryUnit: payload.totalMemoryUnit,
cpuNum: payload.cpuNum ? parseInt(payload.cpuNum, 10) : null,
connectionNum: payload.connectionNum
? parseInt(payload.connectionNum, 10)
: null,
hdType: payload.hdType,
dbSize: payload.dbSize,
}
settingsStore.onSubmitConfiguration()
}
const $reset = () => ({ ...initialState })
return {
state,
// Getters
getDBVersion,
getOSType,
getDBType,
getTotalMemory,
getTotalMemoryUnit,
getCPUNum,
getConnectionNum,
getHDType,
getDBSize,
getTotalMemoryInBytes,
getTotalMemoryInKb,
getDBDefaultValues,
getIsConfigured,
getMaxConnections,
getSharedBuffers,
getHugePages,
getEffectiveCacheSize,
getMaintenanceWorkMem,
getCheckpointSegment,
getWalBuffers,
getDefaultStatisticsTarget,
getRandomPageCost,
getEffectiveIOConcurrency,
getParallelSettings,
getWorkMem,
getWalLevel,
getJit,
getWalCompression,
getAutovacuumMaxWorkers,
getAutovacuumWorkMem,
getIOMethod,
getIOWorkers,
getWarningInfoMessages,
// Actions
setState,
$reset,
}
})
+42
View File
@@ -0,0 +1,42 @@
import { defineStore } from 'pinia'
import { reactive, computed } from 'vue'
import { TAB_CONFIG, APP_THEMES_LIGHT } from '/src/utils/constants/settings.js'
const initialState = {
tabState: TAB_CONFIG,
theme: APP_THEMES_LIGHT,
}
export const useSettingsStore = defineStore('settings', () => {
const state = reactive({ ...initialState })
const getSettings = computed(() => state)
const getThemeSettings = computed(() => state.theme)
const getTabSettings = computed(() => state.tabState)
function toggleTheme() {
state.theme =
APP_THEMES_LIGHT === state.theme ? APP_THEMES_DARK : APP_THEMES_LIGHT
}
function onSubmitConfiguration() {
state.tabState = TAB_CONFIG
}
function $reset() {
state = { ...initialState }
}
return {
// STATE
state,
// GETTERS
getSettings,
getThemeSettings,
getTabSettings,
toggleTheme,
// ACTIONS
onSubmitConfiguration,
$reset,
}
})
+8
View File
@@ -0,0 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useConfigurationStore = defineStore('sw', () => {
const state = ref()
return { state }
})