Merge branch 'release/1.0.0'
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
name: development
|
||||
type: docker
|
||||
kind: pipeline
|
||||
|
||||
volumes:
|
||||
- name: bun_cache
|
||||
host:
|
||||
path: /var/lib/cache/docker/bun
|
||||
- name: node_modules
|
||||
host:
|
||||
path: /var/lib/cache/docker/node_modules/pgtune
|
||||
- name: dist
|
||||
host:
|
||||
path: /var/lib/docker/volumes/pgtune/_data
|
||||
|
||||
steps:
|
||||
- name: build and deploy static
|
||||
image: dockerhub.timeweb.cloud/oven/bun:alpine
|
||||
volumes:
|
||||
- name: bun_cache
|
||||
path: /root/.bun/install/cache
|
||||
- name: node_modules
|
||||
path: /drone/src/node_modules
|
||||
- name: dist
|
||||
path: /dist
|
||||
commands:
|
||||
- apk add --no-cache rsync
|
||||
- bun install --frozen_lockfile
|
||||
- bun --bun run build
|
||||
- rsync -cavhP --delete-after ./dist/ /dist/
|
||||
|
||||
image_pull_secrets:
|
||||
- GiteaRegistryConfig
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- develop
|
||||
@@ -26,3 +26,7 @@ dist-ssr
|
||||
# Auto-generated type declarations
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
||||
dev-dist
|
||||
|
||||
pgtune.tar.zst
|
||||
@@ -1,5 +1,11 @@
|
||||
# Vue 3 + Vite
|
||||
# [PgTune](https://pgtune.mudrov.tech/)
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
[](https://pgtune.mudrov.tech/)
|
||||
|
||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
||||
Tuning PostgreSQL config by your hardware. Based on original React version of [pgtune](https://github.com/le0pard/pgtune). Rewritten in Vue 3.
|
||||
|
||||
### Build status
|
||||
|
||||
| Branch | Status |
|
||||
| --- | --- |
|
||||
| Develop | [](https://drone.minis.mudrov.tech/administrator/pgtune) |
|
||||
@@ -21,7 +21,6 @@
|
||||
"eslint-config-vue": "^2.0.2",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-vue": "^10.10.0",
|
||||
"pinia": "^4.0.3",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"vite": "^8.2.2",
|
||||
@@ -1246,8 +1245,6 @@
|
||||
|
||||
"picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
|
||||
|
||||
"pinia": ["pinia@4.0.3", "", { "dependencies": { "nostics": "^1.1.4" }, "peerDependencies": { "@vue/devtools-api": "^8.1.5", "typescript": ">=5.6.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// If the loader is already loaded, just stop.
|
||||
if (!self.define) {
|
||||
let registry = {};
|
||||
|
||||
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||
let nextDefineUri;
|
||||
|
||||
const singleRequire = (uri, parentUri) => {
|
||||
uri = new URL(uri + ".js", parentUri).href;
|
||||
return registry[uri] || (
|
||||
|
||||
new Promise(resolve => {
|
||||
if ("document" in self) {
|
||||
const script = document.createElement("script");
|
||||
script.src = uri;
|
||||
script.onload = resolve;
|
||||
document.head.appendChild(script);
|
||||
} else {
|
||||
nextDefineUri = uri;
|
||||
importScripts(uri);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
let promise = registry[uri];
|
||||
if (!promise) {
|
||||
throw new Error(`Module ${uri} didn’t register its module`);
|
||||
}
|
||||
return promise;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
self.define = (depsNames, factory) => {
|
||||
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||
if (registry[uri]) {
|
||||
// Module is already loading or loaded.
|
||||
return;
|
||||
}
|
||||
let exports = {};
|
||||
const require = depUri => singleRequire(depUri, uri);
|
||||
const specialDeps = {
|
||||
module: { uri },
|
||||
exports,
|
||||
require
|
||||
};
|
||||
registry[uri] = Promise.all(depsNames.map(
|
||||
depName => specialDeps[depName] || require(depName)
|
||||
)).then(deps => {
|
||||
factory(...deps);
|
||||
return exports;
|
||||
});
|
||||
};
|
||||
}
|
||||
define(['./workbox-f0c192c2'], (function (workbox) { 'use strict';
|
||||
|
||||
self.addEventListener('message', event => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The precacheAndRoute() method efficiently caches and responds to
|
||||
* requests for URLs in the manifest.
|
||||
* See https://goo.gl/S9QRab
|
||||
*/
|
||||
workbox.precacheAndRoute([{
|
||||
"url": "index.html",
|
||||
"revision": "0.kapsgh4ngf8"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
allowlist: [/^\/$/]
|
||||
}));
|
||||
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="keywords" content="pgtune, postgresql, postgres, tuning, config, configuration, free, open source" />
|
||||
<meta name="description" content="PgTune - Tuning PostgreSQL config by your hardware" />
|
||||
<link type="text/plain" rel="author" href="/humans.txt" />
|
||||
<title>PGTune - calculate configuration for PostgreSQL based on the maximum performance for a given hardware configuration</title>
|
||||
<title>PGTune - рассчёт конфигурации PostgreSQL, исходя из максимальной производительности для данной конфигурации оборудования</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="isolate"></div>
|
||||
|
||||
+1
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pgtune",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -25,7 +25,6 @@
|
||||
"eslint-config-vue": "^2.0.2",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-vue": "^10.10.0",
|
||||
"pinia": "^4.0.3",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"vite": "^8.2.2",
|
||||
|
||||
Binary file not shown.
+1
-5
@@ -5,10 +5,6 @@ import { useThemeColor } from './composables/useThemeColor.js'
|
||||
import { onMounted } from 'vue'
|
||||
import { useUpdateApp } from './composables/useUpdateApp.js'
|
||||
|
||||
/*
|
||||
const register = registerSW()
|
||||
*/
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(() => {
|
||||
@@ -22,7 +18,7 @@ onMounted(() => {
|
||||
<UContainer>
|
||||
<Header />
|
||||
<UMain class="p-4">
|
||||
<RouterView :key="route.fullPath" />
|
||||
<RouterView :key="route.path" />
|
||||
</UMain>
|
||||
</UContainer>
|
||||
</UApp>
|
||||
|
||||
@@ -1,104 +1,200 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { reactive, ref } from 'vue'
|
||||
import {
|
||||
DB_TYPE_WEB,
|
||||
DB_TYPE_OLTP,
|
||||
DB_TYPE_DW,
|
||||
DB_TYPE_DESKTOP,
|
||||
DB_TYPE_MIXED,
|
||||
DB_VERSIONS,
|
||||
OS_LINUX,
|
||||
OS_MAC,
|
||||
OS_WINDOWS,
|
||||
SIZE_UNIT_TB,
|
||||
SIZE_UNIT_GB,
|
||||
SIZE_UNIT_MB,
|
||||
HARD_DRIVE_SSD,
|
||||
HARD_DRIVE_NVME,
|
||||
HARD_DRIVE_SAN,
|
||||
HARD_DRIVE_HDD,
|
||||
DB_SIZE_LESS_RAM,
|
||||
DB_SIZE_MID_RAM,
|
||||
DB_SIZE_GREATER_RAM,
|
||||
} from '../utils/constants/configuration'
|
||||
import { validationSchema as schema } from '../utils/validation'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const dbVersions = ref(['18', '17', '16', '15', '14', '13', '12', '11', '10'])
|
||||
const dbVersion = ref('18')
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const osTypes = ref(['Linux', 'Windows', 'OS X'])
|
||||
const osType = ref('Linux')
|
||||
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([
|
||||
'Web application',
|
||||
'Online transaction processing system',
|
||||
'Data warehouse',
|
||||
'Desktop application',
|
||||
'Mixed type of application',
|
||||
{
|
||||
label: 'Web-приложение',
|
||||
value: DB_TYPE_WEB,
|
||||
},
|
||||
{
|
||||
label: 'Система обработки онлайн-транзакций',
|
||||
value: DB_TYPE_OLTP,
|
||||
},
|
||||
{
|
||||
label: 'Хранилище данных',
|
||||
value: DB_TYPE_DW,
|
||||
},
|
||||
{
|
||||
label: 'Десктопное приложение',
|
||||
value: DB_TYPE_DESKTOP,
|
||||
},
|
||||
{
|
||||
label: 'Приложение смешанного типа',
|
||||
value: DB_TYPE_MIXED,
|
||||
},
|
||||
])
|
||||
const dbType = ref('Web application')
|
||||
|
||||
const totalRam = ref()
|
||||
const ramUnits = ref(['TB', 'GB', 'MB'])
|
||||
const ramUnit = ref('GB')
|
||||
|
||||
const totalCPUs = ref(1)
|
||||
|
||||
const totalConnections = ref(100)
|
||||
const ramUnits = ref([SIZE_UNIT_TB, SIZE_UNIT_GB, SIZE_UNIT_MB])
|
||||
|
||||
const dataStorages = ref([
|
||||
'NVME storage',
|
||||
'SSD storage',
|
||||
'Network (SAN) storage',
|
||||
'HDD storage',
|
||||
{
|
||||
label: 'Хранилище NVMe',
|
||||
value: HARD_DRIVE_NVME,
|
||||
},
|
||||
{
|
||||
label: 'Хранилище SSD',
|
||||
value: HARD_DRIVE_SSD,
|
||||
},
|
||||
{
|
||||
label: 'Сетевое хранилище (SAN)',
|
||||
value: HARD_DRIVE_SAN,
|
||||
},
|
||||
{
|
||||
label: 'Хранилище HDD',
|
||||
value: HARD_DRIVE_HDD,
|
||||
},
|
||||
])
|
||||
const dataStorage = ref('SSD storage')
|
||||
|
||||
const dataSizes = ref(['Less than RAM', 'RAM - 3xRAM', 'More than 3xRAM'])
|
||||
const dataSize = ref('Less than RAM')
|
||||
const dataSizes = ref([
|
||||
{
|
||||
label: 'Меньше RAM',
|
||||
value: DB_SIZE_LESS_RAM,
|
||||
},
|
||||
{
|
||||
label: '1x - 3x RAM',
|
||||
value: DB_SIZE_MID_RAM,
|
||||
},
|
||||
{
|
||||
label: 'Более 3xRAM',
|
||||
value: DB_SIZE_GREATER_RAM,
|
||||
},
|
||||
])
|
||||
|
||||
const state = reactive({
|
||||
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 } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm class="space-y-4">
|
||||
<UFormField
|
||||
label="DB version"
|
||||
help="PostgreSQL version (find out via 'SELECT version();')"
|
||||
>
|
||||
<USelect v-model="dbVersion" :items="dbVersions" class="w-full" />
|
||||
<UForm class="space-y-4" :state :schema @submit="onSubmit">
|
||||
<UFormField label="Версия БД" help="Версия PostgreSQL" name="dbVersion">
|
||||
<USelect v-model="state.dbVersion" :items="dbVersions" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="OS Type"
|
||||
help="Operation system of the PostgreSQL server host"
|
||||
label="Тип ОС"
|
||||
help="Операционная система, запускающая PostgreSQL"
|
||||
name="osType"
|
||||
>
|
||||
<USelect v-model="osType" :items="osTypes" class="w-full" />
|
||||
<USelect v-model="state.osType" :items="osTypes" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="DB type"
|
||||
help="What type of application is PostgreSQL used for"
|
||||
label="Тип БД"
|
||||
help="Тип приложения, которое обслуживает PostgreSQL"
|
||||
name="dbType"
|
||||
>
|
||||
<USelect v-model="dbType" :items="dbTypes" class="w-full" />
|
||||
<USelect v-model="state.dbType" :items="dbTypes" class="w-full" />
|
||||
</UFormField>
|
||||
<div class="flex items-start gap-4">
|
||||
<UFormField
|
||||
label="Оперативная память (RAM)"
|
||||
help="Объём оперативной памяти, которую PostgreSQL может использовать"
|
||||
required
|
||||
name="totalMemory"
|
||||
class="grow"
|
||||
>
|
||||
<UInputNumber v-model="state.totalMemory" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField required name="totalMemoryUnit" label="Е.И.">
|
||||
<USelect v-model="state.totalMemoryUnit" :items="ramUnits" />
|
||||
</UFormField>
|
||||
</div>
|
||||
<UFormField
|
||||
label="Количество CPU"
|
||||
help="Количество потоков CPU, которые PostgreSQL может использовать. CPU = № потоков на ядро * № ядер на сокет * № сокетов"
|
||||
name="cpuNum"
|
||||
>
|
||||
<UInputNumber v-model="state.cpuNum" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Total memory (RAM)"
|
||||
help="How much memory can PostgreSQL use"
|
||||
required
|
||||
label="Количество соединений"
|
||||
help="Максимальное количество клиентских соединений"
|
||||
name="connectionNum"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInputNumber
|
||||
v-model="totalRam"
|
||||
class="grow"
|
||||
placeholder="Total memory (RAM) (* required)"
|
||||
/>
|
||||
<USelect v-model="ramUnit" :items="ramUnits" />
|
||||
</div>
|
||||
<UInputNumber v-model="state.connectionNum" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Number of CPUs"
|
||||
help="Number of CPUs, which PostgreSQL can use. CPUs = N threads per core * N cores per socket * N sockets"
|
||||
label="Хранилище данных"
|
||||
help="Тип устройства хранения данных"
|
||||
name="hdType"
|
||||
>
|
||||
<UInputNumber
|
||||
v-model="totalCPUs"
|
||||
class="w-full"
|
||||
placeholder="Number of CPUs"
|
||||
/>
|
||||
<USelect v-model="state.hdType" :items="dataStorages" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Number of connections"
|
||||
help="Maximum number of PostgreSQL client connections"
|
||||
label="Объём базы данных"
|
||||
help="Приблизительное отношение объёма хранимых данных к объёму оперативной памяти сервера"
|
||||
name="dbSize"
|
||||
>
|
||||
<UInputNumber
|
||||
v-model="totalConnections"
|
||||
class="w-full"
|
||||
placeholder="Number of connections"
|
||||
/>
|
||||
<USelect v-model="state.dbSize" :items="dataSizes" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="Data storage" help="Type of data storage device">
|
||||
<USelect v-model="dataStorage" :items="dataStorages" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Total data size"
|
||||
help="Estimated total size of your database compared to total server RAM"
|
||||
>
|
||||
<USelect v-model="dataSize" :items="dataSizes" class="w-full" />
|
||||
</UFormField>
|
||||
<UButton size="xl" label="GENERATE" block type="submit" class="h-16" />
|
||||
<UButton
|
||||
size="xl"
|
||||
label="Рассчитать"
|
||||
block
|
||||
type="submit"
|
||||
class="h-16"
|
||||
icon="lucide:calculator"
|
||||
/>
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<script setup>
|
||||
import { useRoute } from 'vue-router'
|
||||
import { computed, ref } from 'vue'
|
||||
import { calculateSettings } from '../utils/calculator'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
|
||||
const KB_UNIT_MAP = {
|
||||
KB_PER_MB: 1024,
|
||||
KB_PER_GB: 1048576,
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
||||
const tab = ref('0')
|
||||
const tabs = ref([
|
||||
{
|
||||
label: 'postgresql.conf',
|
||||
},
|
||||
{
|
||||
label: 'ALTER SYSTEM',
|
||||
},
|
||||
])
|
||||
|
||||
const isAlterSystem = computed(() => tab.value === '1')
|
||||
const query = computed(() => route.query)
|
||||
const res = computed(() => calculateSettings({ ...query.value }))
|
||||
const config = computed(() => {
|
||||
const hwConfig = [
|
||||
['DB Version', res.value.dbVersion],
|
||||
['OS Type', res.value.osType],
|
||||
['DB Type', res.value.dbType],
|
||||
[
|
||||
'Total Memory (RAM)',
|
||||
`${res.value.totalMemory} ${res.value.totalMemoryUnit}`,
|
||||
],
|
||||
['CPUs num', res.value.cpuNum],
|
||||
['Connections num', res.value.connectionNum],
|
||||
['Data Storage', res.value.hdType],
|
||||
]
|
||||
.filter((item) => !!item[1])
|
||||
.map((item) => `${isAlterSystem.value ? '--' : '#'} ${item[0]}: ${item[1]}`)
|
||||
.join('\n')
|
||||
|
||||
const pgConfig = [
|
||||
['max_connections', res.value.maxConnections],
|
||||
['shared_buffers', formatValue(res.value.sharedBuffers)],
|
||||
['effective_cache_size', formatValue(res.value.effectiveCacheSize)],
|
||||
['maintenance_work_mem', formatValue(res.value.maintenanceWorkMem)],
|
||||
['checkpoint_completion_target', res.value.checkpointCompletionTarget],
|
||||
['wal_buffers', formatValue(res.value.walBuffers)],
|
||||
['default_statistics_target', res.value.defaultStatisticsTarget],
|
||||
['random_page_cost', res.value.randomPageCost],
|
||||
['effective_io_concurrency', res.value.effectiveIoConcurrency],
|
||||
['work_mem', formatValue(res.value.workMem)],
|
||||
['huge_pages', res.value.hugePages],
|
||||
['jit', res.value.jit],
|
||||
['wal_compression', res.value.walCompression],
|
||||
['autovacuum_max_workers', res.value.autovacuumMaxWorkers],
|
||||
[
|
||||
'autovacuum_work_mem',
|
||||
res.value.autovacuumWorkMem
|
||||
? formatValue(res.value.autovacuumWorkMem)
|
||||
: null,
|
||||
],
|
||||
['io_method', res.value.ioMethod],
|
||||
['io_workers', res.value.ioWorkers],
|
||||
]
|
||||
.concat(
|
||||
res.value.checkpointSegments.map((item) => {
|
||||
if (item.key === 'checkpoint_segments') {
|
||||
return [item.key, item.value]
|
||||
}
|
||||
return [item.key, formatValue(item.value)]
|
||||
}),
|
||||
)
|
||||
.concat(res.value.parallelSettings.map((item) => [item.key, item.value]))
|
||||
.concat(res.value.walLevel.map((item) => [item.key, item.value]))
|
||||
.filter((item) => !!item[1])
|
||||
.map((item) =>
|
||||
isAlterSystem.value
|
||||
? `ALTER SYSTEM SET ${item[0]} = '${item[1]}';`
|
||||
: `${item[0]} = ${item[1]}`,
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
const warningInfo = res.value.warningInfoMessages
|
||||
.map((item) => `${isAlterSystem.value ? '--' : '#'} ${item}`)
|
||||
.join('\n')
|
||||
|
||||
let config = [hwConfig, '', pgConfig]
|
||||
|
||||
if (res.value.warningInfoMessages.length > 0) {
|
||||
config = [warningInfo, '', ...config]
|
||||
}
|
||||
|
||||
return config.join('\n')
|
||||
})
|
||||
|
||||
const { copy } = useClipboard({ source: config })
|
||||
|
||||
const formatValue = (value) => {
|
||||
const result = (() => {
|
||||
if (value % KB_UNIT_MAP['KB_PER_GB'] === 0) {
|
||||
return {
|
||||
value: Math.floor(value / KB_UNIT_MAP['KB_PER_GB']),
|
||||
unit: 'GB',
|
||||
}
|
||||
}
|
||||
if (value % KB_UNIT_MAP['KB_PER_MB'] === 0) {
|
||||
return {
|
||||
value: Math.floor(value / KB_UNIT_MAP['KB_PER_MB']),
|
||||
unit: 'MB',
|
||||
}
|
||||
}
|
||||
return {
|
||||
value,
|
||||
unit: 'kB',
|
||||
}
|
||||
})()
|
||||
|
||||
// return formatted
|
||||
return `${result.value}${result.unit}`
|
||||
}
|
||||
|
||||
const onCopyConfig = () => {
|
||||
copy(config)
|
||||
toast.add({
|
||||
title: 'Скопировано',
|
||||
description: 'Конфигурация скопирована успешно',
|
||||
duration: 7500,
|
||||
icon: 'lucide:copy',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="space-y-4 min-w-0 w-full">
|
||||
<UTabs v-model="tab" :content="false" :items="tabs" class="w-full" />
|
||||
<pre class="w-full overflow-x-auto whitespace-pre">{{ config }}</pre>
|
||||
<UButton
|
||||
label="Копировать конфигурацию"
|
||||
block
|
||||
icon="lucide:copy"
|
||||
class="h-12"
|
||||
@click="onCopyConfig"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,52 +1,60 @@
|
||||
<template>
|
||||
<article class="prose dark:prose-invert max-w-full">
|
||||
<article class="prose prose-sm dark:prose-invert max-w-full">
|
||||
<p>
|
||||
Please provide basic information about the configuration of your hardware
|
||||
on which the PostgreSQL database is running. Results will be calculated
|
||||
after clicking the "Generate" button
|
||||
Пожалуйста, предоставьте основную информацию о конфигурации вашего
|
||||
оборудования, на котором работает база данных PostgreSQL. Результаты будут
|
||||
рассчитаны после нажатия кнопки "Рассчитать".
|
||||
</p>
|
||||
|
||||
<p>More information about "DB Type" setting:</p>
|
||||
<p>Дополнительная информация о настройке "Тип БД":</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Web Application (web)</p>
|
||||
<p>Web-приложение</p>
|
||||
<ul>
|
||||
<li>Typically CPU-bound</li>
|
||||
<li>DB much smaller than RAM</li>
|
||||
<li>90% or more simple queries</li>
|
||||
<li>Как правило, зависит от процессора</li>
|
||||
<li>БД намного меньше оперативной памяти</li>
|
||||
<li>Более 90% запросов - простые выборки</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Online Transaction Processing (oltp)</p>
|
||||
<p>Система обработки онлайн-транзакций</p>
|
||||
<ul>
|
||||
<li>Typically CPU- or I/O-bound</li>
|
||||
<li>DB slightly larger than RAM to 1TB</li>
|
||||
<li>20-40% small data write queries</li>
|
||||
<li>Some long transactions and complex read queries</li>
|
||||
<li>Как правило, зависит от процессора или системы ввода-вывода</li>
|
||||
<li>Размер БД - от размера оперативной памяти до 1 ТБ</li>
|
||||
<li>20-40% запросов - запись небольших объёмов данных</li>
|
||||
<li>Встречаются длительные транзакции и сложные запросы на чтение</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Data Warehouse (dw)</p>
|
||||
<p>Хранилище данных</p>
|
||||
<ul>
|
||||
<li>Typically I/O- or RAM-bound</li>
|
||||
<li>Large bulk loads of data</li>
|
||||
<li>Large complex reporting queries</li>
|
||||
<li>Also called "Decision Support" or "Business Intelligence"</li>
|
||||
<li>
|
||||
Как правило, зависит от системы ввода-вывода или оперативной памяти
|
||||
</li>
|
||||
<li>Большие массивы данных</li>
|
||||
<li>Сложные запросы построения отчётов</li>
|
||||
<li>
|
||||
Также называется "Системой принятия решений" или "Системой
|
||||
бизнес-аналитики"
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Desktop application</p>
|
||||
<p>Десктопное приложение</p>
|
||||
<ul>
|
||||
<li>Not a dedicated database</li>
|
||||
<li>A general workstation, perhaps for a developer</li>
|
||||
<li>Неспециализированная база данных</li>
|
||||
<li>Обычная рабочая станция, возможно - для разработки</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Mixed type of application</p>
|
||||
<p>Приложение смешанного типа</p>
|
||||
<ul>
|
||||
<li>Mixed DW and OLTP characteristics</li>
|
||||
<li>wide mixture of queries</li>
|
||||
<li>
|
||||
Смешанные характеристики хранилища данных и системы обработки
|
||||
онлайн-транзакций
|
||||
</li>
|
||||
<li>Широкий спектр запросов</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -4,11 +4,11 @@ import Logo from '/favicon.svg?url'
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
label: 'Home',
|
||||
label: 'Главная',
|
||||
to: '/',
|
||||
},
|
||||
{
|
||||
label: 'How it works',
|
||||
label: 'Как это работает?',
|
||||
to: '/about',
|
||||
},
|
||||
])
|
||||
@@ -32,7 +32,7 @@ const items = ref([
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="https://gitea.minis.mudrov.tech"
|
||||
to="https://gitea.minis.mudrov.tech/administrator/pgtune"
|
||||
target="_blank"
|
||||
icon="simple-icons:gitea"
|
||||
aria-label="Gitea"
|
||||
|
||||
@@ -14,37 +14,34 @@ export function useUpdateApp() {
|
||||
})
|
||||
const toast = useToast()
|
||||
|
||||
if (import.meta.env.DEV) console.log({ SWNeedRefresh: needRefresh.value })
|
||||
|
||||
async function close(e) {
|
||||
needRefresh.value = false
|
||||
e?.stopPropagation()
|
||||
}
|
||||
|
||||
// New version
|
||||
|
||||
watch(
|
||||
() => needRefresh,
|
||||
needRefresh,
|
||||
(n) => {
|
||||
if (import.meta.env.DEV) console.log('NeedRefresh:', n)
|
||||
if (n) {
|
||||
toast.add({
|
||||
title: 'Update',
|
||||
title: 'Обновление',
|
||||
description:
|
||||
'A newer version of the app is available. Click the “Update” button to install.',
|
||||
'Доступна более новая версия приложения. Нажмите кнопку “Обновить”, чтобы установить его.',
|
||||
duration: 0,
|
||||
icon: 'lucide:cloud-download',
|
||||
close: false,
|
||||
actions: [
|
||||
{
|
||||
icon: 'lucide:arrow-big-up-dash',
|
||||
label: 'Update',
|
||||
label: 'Обновить',
|
||||
onClick: updateServiceWorker,
|
||||
color: 'error',
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'lucide:x',
|
||||
label: 'Dismiss',
|
||||
label: 'Отложить',
|
||||
onClick: close,
|
||||
variant: 'outline',
|
||||
},
|
||||
@@ -53,5 +50,6 @@ export function useUpdateApp() {
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
}
|
||||
|
||||
+2
-8
@@ -1,14 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import '/src/assets/styles/app.css'
|
||||
import App from './App.vue'
|
||||
import { router } from '/router.js'
|
||||
import { router } from './router'
|
||||
import ui from '@nuxt/ui/vue-plugin'
|
||||
import { createHead } from '@unhead/vue/client'
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.use(createPinia())
|
||||
.use(ui)
|
||||
.use(createHead())
|
||||
.mount('#app')
|
||||
createApp(App).use(router).use(ui).use(createHead()).mount('#app')
|
||||
|
||||
+9
-9
@@ -6,19 +6,19 @@ import HeroImage from '/src/assets/images/logo.svg'
|
||||
<article class="prose dark:prose-invert max-w-full">
|
||||
<img :src="HeroImage" class="w-full h-auto rounded-2xl" alt="PGTune" />
|
||||
<p>
|
||||
<strong>PGTune</strong> calculate configuration for PostgreSQL based on
|
||||
the maximum performance for a given hardware configuration. It isn't a
|
||||
<strong>PGTune</strong> рассчитывает конфигурацию PostgreSQL, исходя из
|
||||
максимальной производительности для данной конфигурации оборудования. Это
|
||||
не
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/No_Silver_Bullet"
|
||||
href="https://ru.wikipedia.org/wiki/Серебряной_пули_нет"
|
||||
target="_blank"
|
||||
class="font-medium underline"
|
||||
>silver bullet</a
|
||||
>универсальное решение</a
|
||||
>
|
||||
for the optimization settings of PostgreSQL. Many settings depend not only
|
||||
on the hardware configuration, but also on the size of the database, the
|
||||
number of clients and the complexity of queries. An optimal configuration
|
||||
of the database can only be made given all these parameters are taken into
|
||||
account.
|
||||
для оптимизации настроек PostgreSQL. Многие настройки зависят не только от
|
||||
конфигурации оборудования, но и от размера базы данных, количества
|
||||
клиентов и сложности запросов. Оптимальная конфигурация базы данных может
|
||||
быть создана только с учётом этих параметров.
|
||||
</p>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
+26
-3
@@ -1,14 +1,37 @@
|
||||
<script setup>
|
||||
import { computed, defineAsyncComponent } from 'vue'
|
||||
import ConfigurationForm from '../components/configurationForm.vue'
|
||||
import DefaultDescription from '../components/defaultDescription.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const shouldShowResultView = computed(
|
||||
() =>
|
||||
Object.hasOwn(route.query, 'dbVersion') &&
|
||||
Object.hasOwn(route.query, 'osType') &&
|
||||
Object.hasOwn(route.query, 'dbType') &&
|
||||
Object.hasOwn(route.query, 'totalMemory') &&
|
||||
Object.hasOwn(route.query, 'totalMemoryUnit') &&
|
||||
Object.hasOwn(route.query, 'cpuNum') &&
|
||||
Object.hasOwn(route.query, 'connectionNum') &&
|
||||
Object.hasOwn(route.query, 'hdType') &&
|
||||
Object.hasOwn(route.query, 'dbSize'),
|
||||
)
|
||||
|
||||
const DefaultDescription = defineAsyncComponent(
|
||||
() => import('../components/defaultDescription.vue'),
|
||||
)
|
||||
const CFV = defineAsyncComponent(
|
||||
() => import('../components/configurationFormView.vue'),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid lg:grid-cols-2 gap-16">
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-xl font-bold">Parameters of your system</h3>
|
||||
<h3 class="text-xl font-bold">Параметры системы</h3>
|
||||
<ConfigurationForm />
|
||||
</div>
|
||||
<DefaultDescription />
|
||||
<CFV v-if="shouldShowResultView" />
|
||||
<DefaultDescription v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,479 +0,0 @@
|
||||
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,
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
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,
|
||||
}
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useConfigurationStore = defineStore('sw', () => {
|
||||
const state = ref()
|
||||
|
||||
return { state }
|
||||
})
|
||||
@@ -0,0 +1,576 @@
|
||||
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 {
|
||||
dbVersion,
|
||||
osType,
|
||||
dbType,
|
||||
totalMemory,
|
||||
totalMemoryUnit,
|
||||
cpuNum,
|
||||
connectionNum,
|
||||
hdType,
|
||||
maxConnections,
|
||||
hugePages,
|
||||
sharedBuffers,
|
||||
effectiveCacheSize,
|
||||
maintenanceWorkMem,
|
||||
checkpointSegments,
|
||||
checkpointCompletionTarget,
|
||||
walBuffers,
|
||||
defaultStatisticsTarget,
|
||||
randomPageCost,
|
||||
effectiveIoConcurrency,
|
||||
parallelSettings,
|
||||
workMem,
|
||||
warningInfoMessages,
|
||||
walLevel,
|
||||
jit,
|
||||
walCompression,
|
||||
autovacuumMaxWorkers,
|
||||
autovacuumWorkMem,
|
||||
ioMethod,
|
||||
ioWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
'этот инструмент не предназначен',
|
||||
'для систем с низким количеством ОЗУ',
|
||||
)
|
||||
} else if (totalMemory > 100 * SIZE_UNIT_MAP['GB']) {
|
||||
warnings.push(
|
||||
'этот инструмент не предназначен',
|
||||
'для систем с очень большим количеством ОЗУ',
|
||||
)
|
||||
}
|
||||
|
||||
// Advanced features compilation warnings
|
||||
if (walCompression === 'lz4') {
|
||||
if (warnings.length > 0) warnings.push('')
|
||||
|
||||
warnings.push(
|
||||
'wal_compression = lz4 требует, чтобы PostgreSQL',
|
||||
'был скомпилирован с флагом --with-lz4',
|
||||
)
|
||||
}
|
||||
|
||||
if (ioMethod === 'io_uring') {
|
||||
if (warnings.length > 0) warnings.push('')
|
||||
|
||||
warnings.push(
|
||||
'io_method = io_uring требует, чтобы PostgreSQL',
|
||||
'был скомпилирован с флагом --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]: 'SSD',
|
||||
[HARD_DRIVE_NVME]: 'NVME',
|
||||
[HARD_DRIVE_SAN]: 'SAN',
|
||||
}[hdType]
|
||||
|
||||
if (warnings.length > 0) warnings.push('')
|
||||
|
||||
warnings.push(
|
||||
`Параметры стоимости для хранилищ данных на ${driveName} оставлены со значениями по умолчанию,`,
|
||||
'чтобы избежать катастрофического выбора при сканировании индексов.',
|
||||
'Следите за поведением планировщика запросов и при необходимости корректируйте random_page_cost.',
|
||||
)
|
||||
}
|
||||
|
||||
return warnings.length > 0 ? ['ВНИМАНИЕ', ...warnings] : []
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ export default defineConfig({
|
||||
description: 'PgTune - Tuning PostgreSQL config by your hardware',
|
||||
display: 'standalone',
|
||||
start_url: '/',
|
||||
theme_color: '#0f172b',
|
||||
theme_color: '#fff',
|
||||
background_color: '#fff',
|
||||
icons: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user