max_index_keys считает вещи, которые не являются ключами, начиная с PostgreSQL 11. То, что он сообщает — 32, — это максимальное число столбцов в индексе, а с появлением INCLUDE столбец в индексе не обязательно является ключом: индекс с 31 ключом, несущий один INCLUDE-столбец, находится на пределе, как и индекс с одним ключом, несущий 31 такой столбец. Сообщение об ошибке говорит «столбцов» (cannot use more than 32 columns in an index), pg_controldata говорит Maximum columns in an index:, и только имя параметра всё ещё говорит о ключах.
Вопрос обычно приходит как вопрос безопасности: «Стоит ли использовать параметры привязки, или можно собрать строку SQL самому?» Ответ на этот вопрос таков: используйте параметры привязки, сборка строки — это путь к SQL-инъекции, и все мы можем спокойно жить дальше.
Вопрос производительности, скрывающийся под ним, куда интереснее, и народная мудрость о нём в основном неверна. «Подготовленные операторы пропускают планирование» — обычная версия. Это верно лишь в узком наборе обстоятельств, и когда это верно, это не всегда хорошая новость. Эта статья о том, что PostgreSQL делает с запросом, значения которого впечатаны в текст, что он делает с запросом, значения которого приходят отдельно, и как кэш планов, стоящий между этими двумя путями, принимает свои решения. Есть конкретный момент — шестое выполнение подготовленного оператора, — где два пути расходятся, и удивительно много случаев «мой запрос стал медленным, а я ничего не менял» восходит именно к нему.
max_identifier_length сообщает число 63, и это число — самое неинтересное в нём. Каждая реляционная база данных ограничивает длину имени. Необычно в PostgreSQL то, что происходит, когда вы его превышаете: MySQL, SQL Server и Oracle отвергают оператор, а PostgreSQL обрезает имя до 63 байт, упоминает об этом в NOTICE и продолжает так, будто это то, что вы имели в виду. Почти каждая реальная проблема, связанная с этим параметром, восходит к этому одному решению.
max_files_per_process — это не лимит в том смысле, в каком его понимает большинство людей, когда превышение даёт ошибку. Это размер кэша. Каждый серверный процесс держит пул открытых файловых дескрипторов ядра, и когда пул полон, а серверному процессу нужен ещё один, он закрывает наименее недавно использованный и продолжает. Ничего не ломается. Запрос просто тратит пару дополнительных системных вызовов на файл, который был открыт тридцать секунд назад.
Никто не смотрит max_function_args намеренно. Вы встречаете его, когда PostgreSQL прерывает что-то несвязанное сообщением cannot pass more than 100 arguments to a function, и вы идёте искать ручку. Её нет.
max_connections — это бюджет памяти и автоматический выключатель, носящий костюм ёмкости. Он не решает, сколько запросов ваш сервер может выполнять одновременно; это решают количество ядер и хранилище, и они были решены, когда вы покупали оборудование. max_connections решает, сколько клиентских серверных процессов PostgreSQL создаст, прежде чем начнёт говорить «нет», и, что менее очевидно, сколько разделяемой памяти он откладывает в предположении, что каждый из них может появиться.
max_active_replication_origins — это десятилетний комментарий XXX, который наконец был оплачен. Он появился в PostgreSQL 18, и он существует потому, что то, чем он управляет, с 9.5 занимало число у другого параметра. На 14–17 число источников репликации, которые может отслеживать подписчик, задаётся max_replication_slots, параметром, чья настоящая работа — на издателе; с 16 документация всё равно перечисляет его в разделе для подписчика с примечанием, что он «также применяется на отправляющем сервере, но с другим значением». Отдельный параметр был впервые предложен в 2021 году и никуда не пошёл. Он ждал 18.
Небольшая настройка, которую большинство команд либо игнорируют, либо угадывают. Вот быстрая версия, а затем полный разбор.
Ключевые моменты
Fillfactor задаёт, насколько полно Postgres упаковывает каждую страницу таблицы при записи, оставляя остальное как свободное пространство для будущих обновлений, которые попадут на место (HOT-обновления), что сокращает WAL и ввод-вывод.
Начальная формула: 100 − (средний размер строки ÷ 8 192 × 100). Получите средний размер строки из pg_relation_size() и n_live_tup, запускайте после недавнего ANALYZE.
Для таблиц с интенсивными обновлениями берите значение ниже начального числа, чтобы оставить больше места для повторных перезаписей на месте.
Не устанавливайте по умолчанию для каждой таблицы 70 или 80. Рассчитывайте из реального размера строки таблицы и характера обновлений, затем корректируйте на основе наблюдаемой доли HOT и раздувания.
maintenance_work_mem — это параметр памяти, который ведёт себя так, как люди ошибочно предполагают, что ведёт себя work_mem: одно выделение, для одной операции, в одном сеансе, и число, которое вы задаёте, — это число, которое используется. work_mem — это допуск на каждую сортировку, каждый хеш, каждого исполнителя, который один запрос может затребовать дюжину раз. maintenance_work_mem — это потолок. Документация говорит, что «безопасно устанавливать это значение значительно больше work_mem», и это одно из немногих мест, где вам дают разрешение быть щедрым, и в основном они правы. Эта статья о «в основном».
Ваша база данных работает как часы — пока простой DELETE не поставит её на колени. Что пошло не так? Хотя мы склонны сосредотачиваться на оптимизации операций SELECT и INSERT, мы часто упускаем из виду скрытую сложность DELETE. А ведь удаление ненужных данных столь же важно. Устаревшие или нерелевантные данные могут раздувать вашу базу данных, снижать производительность и делать обслуживание кошмаром. Хуже того, хранение некоторых типов данных без действительного обоснования может даже привести к проблемам соответствия.
maintenance_io_concurrency — это effective_io_concurrency для работы, которую документация описывает как выполняемую «от имени множества клиентских сеансов». Эта фраза — весь смысл дизайна: VACUUM выплачивает долг по вводу-выводу, который накопил каждый сеанс, поэтому он может разумно быть более агрессивным в упреждающем чтении, чем любой из них. Механически эти два параметра — одна и та же ручка, включая трёхэтапную историю того, что означает число, и я не буду повторять это здесь. Эта статья о том, какая работа управляется им, потому что список не таков, как подсказывает название.
logical_decoding_work_mem — это то, сколько чужой незафиксированной транзакции walsender будет держать в памяти, прежде чем начнёт записывать эту транзакцию на диск во второй раз.
"SQL Server медленный". Как часто вы слышали это? Наиболее часто недовольство возникает при проблемах с производительностью, но во многих случаях это утверждение не выдерживает анализа. Чаще всего SQL Server просто реагирует на возлагаемую на него рабочую нагрузку, и сама рабочая нагрузка является проблемой.
SQL Server не гадает. Он точно показывает, где он потратил время и почему. Если проблема в движке, данные это покажут. Если нет, он покажет это так же ясно. Цель состоит в отделении догадок от фактов и проверки, действительно ли узкое место находится в SQL Server, а не в слое приложения.
Начнем со статистики ожиданий
Первый шагом является проверка статистики ожидания, которая дает непосредственный взгляд на то, где SQL Server проводит время в ожидании. Если имеется узкое место во вводе-выводе, блокировке или записи в журнал транзакций, это будет отражено в ожиданиях.
Продолжить чтение "Когда SQL Server не является узким местом"
Это самые старые параметры журналирования, которые PostgreSQL всё ещё поставляет, и вывод выглядит соответственно. Каждый из них — булев параметр, по умолчанию выключен, контекст superuser, и каждый заставляет серверный процесс вызывать getrusage() до и после одной стадии выполнения оператора и записывать разницу в журнал на уровне LOG. log_parser_stats покрывает парсер, анализ разбора и переписчик (три блока на оператор, по одному на стадию); log_planner_stats покрывает планировщик; log_executor_stats покрывает исполнитель; а log_statement_stats покрывает весь оператор, поэтому он отказывается включаться вместе с любым из других трёх, и они отказываются включаться вместе с ним. Вывод для одного SELECT count(*) при включённом log_statement_stats: