Зачем вообще нужен tablespace в PostgreSQL
Tablespace в PostgreSQL — это логическое имя, привязанное к каталогу в файловой системе. В этом каталоге будут храниться файлы данных отдельных объектов: таблиц, индексов, materialized view и т. п. Практический смысл простой: вы управляете размещением «тяжёлых» объектов по разным дискам/томам, не поднимая отдельные кластеры.
Типовые сценарии, где tablespace реально помогает:
- Разнести I/O: индексы на быстрый NVMe, таблицы на более ёмкий диск.
- Упростить расширение хранилища: когда один раздел заканчивается, переносите крупные отношения в новый tablespace.
- Контроль стоимости: горячие данные на SSD, архив на HDD.
- Изоляция рисков: временные/ETL-объекты отдельно, чтобы они не «съели» место у продовых таблиц.
Tablespace не ускоряет PostgreSQL «магически». Он даёт контроль над размещением на уровне файловой системы. Прирост производительности появляется только если вы осознанно выбираете устройства хранения и нагрузка действительно упирается в I/O.
Какие tablespace есть по умолчанию
В каждом кластере PostgreSQL есть минимум два системных пространства:
pg_default— дефолтное место для большинства объектов.pg_global— служебные глобальные объекты кластера (обычно его не трогают).
Пользовательские tablespace вы создаёте сами и выбираете, какие объекты туда отправлять: сразу при создании или позже — переносом.

Подготовка: что проверить до создания tablespace
Файловая система, путь и права
Каталог tablespace должен существовать на сервере и быть доступен пользователю ОС, под которым работает PostgreSQL (чаще всего postgres). На практике лучше сразу воспринимать tablespace как «контракт» между БД и файловой системой: путь должен быть стабильным, а диск — предсказуемым по поведению.
- Предпочтительно держать tablespace на отдельном смонтированном томе (LVM/RAID/отдельный диск), а не просто в подпапке того же раздела.
- Убедитесь, что выбранная ФС и mount-опции подходят под БД (корректные барьеры записи, без экспериментальных опций).
- Сразу продумайте резервное копирование и восстановление: tablespace физически лежит вне каталога данных, но логически является частью кластера.
Кто может создавать tablespace
Операции с tablespace обычно требуют прав суперпользователя PostgreSQL или роли, которой разрешено управление tablespace. В некоторых управляемых средах это ограничено политиками безопасности — лучше уточнить до начала работ, чтобы не упереться в ограничения во время окна обслуживания.
Создание tablespace: безопасный минимальный рецепт
Пример: хотим создать tablespace для индексов на отдельном диске.
sudo -u postgres mkdir -p /pgdata_ts/indexes
sudo -u postgres chmod 700 /pgdata_ts/indexes
Создаём tablespace в SQL:
CREATE TABLESPACE ts_indexes LOCATION '/pgdata_ts/indexes';
Проверяем список tablespace в psql:
\db
Если нужно ограничить доступ, назначьте владельца и дальше выдавайте права точечно:
ALTER TABLESPACE ts_indexes OWNER TO app_owner;
Полезная привычка: фиксируйте в документации, что хранится в tablespace и какой диск за ним стоит. Это упрощает инциденты «место заканчивается» и планирование расширения.
Перенос таблицы: ALTER TABLE SET TABLESPACE
Базовая команда переноса таблицы:
ALTER TABLE public.orders SET TABLESPACE ts_data;
Важные эксплуатационные моменты:
- Это физическое перемещение (переписывание файлов). Нужны I/O и время.
- Будут блокировки уровня таблицы. На активных системах перенос почти всегда планируют в maintenance window.
- Индексы не обязаны «переехать» вместе с таблицей. Их tablespace задаётся отдельно, и часто таблица остаётся в одном месте, а индексы — в другом.
Как задать tablespace по умолчанию для новых объектов
Для базы данных можно задать tablespace по умолчанию (это влияет на будущие объекты, но не перемещает существующие):
ALTER DATABASE appdb SET TABLESPACE ts_data;
Если нужна именно «миграция всего», обычно делают план: сначала перенос самых больших индексов, затем таблиц волнами, затем выравнивание политики по схемам и ролям.
Перенос индекса: ALTER INDEX SET TABLESPACE
Индекс переносится отдельной командой:
ALTER INDEX public.orders_created_at_idx SET TABLESPACE ts_indexes;
На практике перенос индекса часто проще (и «дешевле» по времени), чем перенос большой таблицы, но блокировки и конкуренция за I/O всё равно будут. В высоконагруженных системах обычно переносят индексы пачками, с контролем длительности и откатом по необходимости.
Сгенерировать команды переноса для всех индексов схемы
Удобный приём — сгенерировать SQL из каталога и прогонять его по списку, логируя результаты:
SELECT format('ALTER INDEX %I.%I SET TABLESPACE ts_indexes;', n.nspname, c.relname)
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i'
AND n.nspname = 'public';
Если вы делаете это в проде, прогоняйте команды по одной, отслеживайте блокировки и время выполнения. Для отдельной темы про борьбу с раздуванием (когда перенос совмещают с переупаковкой) см. материал про уменьшение bloat и обслуживание pg_repack.
Как оценить размеры: pg_relation_size и pg_total_relation_size
Перед переносом полезно понять, кто именно «ест диск». Базовая функция pg_relation_size показывает размер самой relation (например, таблицы без индексов):
SELECT pg_size_pretty(pg_relation_size('public.orders'));
Чаще нужен полный размер таблицы вместе с индексами и TOAST — для этого используйте pg_total_relation_size:
SELECT pg_size_pretty(pg_total_relation_size('public.orders'));
Найти топ самых «тяжёлых» таблиц по полной занимаемой площади:
SELECT
n.nspname AS schema,
c.relname AS table,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
ORDER BY pg_total_relation_size(c.oid) DESC
LIMIT 20;
Эти запросы удобно держать как «оперативный набор» на случай инцидентов, когда нужно быстро принять решение: переносить, чистить, расширять диск или ограничивать рост.
Мониторинг диска: что смотреть кроме df
df показывает занятость разделов, но для PostgreSQL важна связка «уровень ОС» и «уровень БД». Практично мыслить тремя слоями:
- ОС: свободное место, inode, I/O wait, задержки диска.
- Кластер PostgreSQL: рост каталогов, WAL, временных файлов, частота и длительность чекпоинтов.
- Объекты: какие таблицы/индексы растут, где bloat, какие операции его провоцируют.
Быстрые команды на стороне ОС (частый старт при инциденте «заканчивается место»):
df -h
df -i
du -sh /var/lib/postgresql
du -sh /pgdata_ts
Дальше сопоставляйте это с топом по pg_total_relation_size и принимайте решение: что переносить, что обслуживать (REINDEX/VACUUM FULL/pg_repack), а где проблема вообще не в таблицах (например, в WAL или временных файлах).
Если вы храните бэкапы на отдельном хранилище, пригодится понятный, воспроизводимый процесс. В тему может быть полезна статья про бэкапы на объектное хранилище с restic/borg (подходы применимы и к PostgreSQL-дампам/бэкапам файлового уровня).

Признаки bloat: когда tablespace помогает, а когда нет
Bloat — раздувание таблиц и индексов из-за MVCC, частых обновлений/удалений, неидеального autovacuum и т. п. Внешне это часто выглядит так: «данных не прибавилось, а диск улетает».
Типичные признаки в проде:
- Размер таблицы/индекса растёт заметно быстрее, чем бизнес-данные.
- Запросы замедляются, особенно по индексам (индекс больше, чтений больше).
- VACUUM проходит, но «место не возвращается» на уровне файловой системы (оно остаётся внутри файла и будет переиспользовано позже).
- Растёт I/O и время чекпоинтов на фоне той же нагрузки.
Перенос в tablespace не лечит bloat. Он может временно разгрузить переполненный том, но причина раздувания останется. Чтобы реально уменьшить размер на диске, нужны операции обслуживания: REINDEX, VACUUM FULL или переупаковка (например, через pg_repack) — в зависимости от допустимых блокировок и требований к доступности.
Планирование maintenance window: как переносить без сюрпризов
Почти любой серьёзный перенос (особенно таблиц) стоит планировать как работу, влияющую на доступность. С точки зрения эксплуатации критичны четыре вещи: блокировки, скорость копирования, запас места и понятный откат.
Чек-лист перед переносом
- Оцените размеры через
pg_total_relation_sizeи прикиньте длительность по реальной скорости диска. - Проверьте свободное место на целевом томе с запасом; учитывайте рост WAL во время интенсивной записи.
- Согласуйте окно и предупредите пользователей: возможны задержки и блокировки.
- Проверьте бэкап и процедуру восстановления до начала работ.
Стратегия «малых шагов»
- Начните с самых больших индексов: обычно это быстро даёт выигрыш по месту и I/O.
- Затем переносите крупные таблицы, но только после пробного переноса 1–2 объектов, чтобы понять реальную длительность и влияние.
- После каждого этапа фиксируйте результат: размеры, latency, время чекпоинтов, жалобы приложений.
Проверка результата: где реально лежит объект
После операций убедитесь, что таблица/индекс действительно в нужном tablespace. Быстрая проверка по системным каталогам:
SELECT
n.nspname AS schema,
c.relname AS name,
c.relkind,
t.spcname AS tablespace
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
WHERE n.nspname = 'public'
AND c.relname IN ('orders', 'orders_created_at_idx');
Если поле tablespace вернулось NULL, это означает, что объект находится в pg_default (то есть использует значение по умолчанию).
Типовые ошибки и подводные камни
«Я перенёс таблицу, но диск почти не освободился»
Частая причина: основной объём занимали индексы, TOAST или другой крупный объект. Смотрите pg_total_relation_size и отдельно размеры индексов; переносите то, что реально большое.
«Операция идёт слишком долго»
Перенос упирается в I/O. На перегруженном диске или при конкурирующей нагрузке он может идти часами. На будущее: планируйте окно, снижайте параллельную активность, переносите в «тихие» часы.
«Сломались бэкапы или восстановление»
При файловых бэкапах и снимках томов tablespace на отдельном диске становится отдельным объектом, который тоже должен попадать в бэкап. Проверьте, что процесс резервного копирования охватывает все пути tablespace.
«Хотел вынести временные объекты»
Под временные данные иногда заводят отдельные tablespace, но часто эффективнее контролировать запросы, которые генерируют огромные сортировки/хэши, и мониторить временные файлы через метрики и настройки.
Мини-план внедрения tablespace в проде
- Соберите топ объектов по
pg_total_relation_sizeи поймите, что именно давит на диск. - Зафиксируйте политику размещения: например, «таблицы в ts_data, индексы в ts_indexes».
- Создайте tablespace на отдельном томе, проверьте права, бэкап и восстановление.
- Сделайте тестовый перенос 1–2 индексов, замерьте время и влияние.
- Запланируйте maintenance window и переносите основной объём волнами.
- Настройте регулярный мониторинг роста томов и топа размеров в БД.
Итоги
Tablespace в PostgreSQL — это про управляемость: куда растут таблицы и индексы, как быстро расширять хранилище и насколько предсказуемо переживать ситуации «диск заканчивается». Для большинства задач достаточно уверенно владеть ALTER TABLE ... SET TABLESPACE, ALTER INDEX ... SET TABLESPACE и регулярно смотреть размеры через pg_relation_size и pg_total_relation_size. А дальше всё решает дисциплина: мониторинг, работа с bloat и аккуратное планирование окна обслуживания.
Если вы размещаете PostgreSQL на выделенных ресурсах, проще контролировать диски и I/O на уровне виртуальной машины. Для таких задач обычно выбирают VDS, а tablespace используют как инструмент внутри БД для грамотного раскладывания «тяжёлых» объектов.


