Расчет и экономия места в PostgreSQL

У меня есть таблица в pg так:

CREATE TABLE t (
    a BIGSERIAL NOT NULL,               -- 8 b
    b SMALLINT,                         -- 2 b
    c SMALLINT,                         -- 2 b
    d REAL,                             -- 4 b
    e REAL,                             -- 4 b
    f REAL,                             -- 4 b
    g INTEGER,                          -- 4 b
    h REAL,                             -- 4 b
    i REAL,                             -- 4 b
    j SMALLINT,                         -- 2 b
    k INTEGER,                          -- 4 b
    l INTEGER,                          -- 4 b
    m REAL,                             -- 4 b
    CONSTRAINT a_pkey PRIMARY KEY (a)
);

Вышеприведенное добавляет до 50 байтов на строку. Мой опыт показывает, что мне нужно еще от 40% до 50% для системных накладных расходов, даже без каких-либо пользовательских индексов к вышеперечисленному. Итак, около 75 байт на строку. У меня будет много-много строк в таблице, потенциально более 145 миллиардов строк, поэтому таблица будет занимать 13-14 терабайт. Какие приемы, если таковые имеются, можно использовать для сжатия этой таблицы? Мои возможные идеи ниже...

Преобразуйте значения real в integer. Если они могут храниться как smallint, это экономия 2 байта на поле.

Преобразуйте столбцы b .. m в массив. Мне не нужно искать в этих столбцах, но мне нужно иметь возможность возвращать значение одного столбца за раз. Итак, если мне нужен столбец g, я мог бы сделать что-то вроде

SELECT a, arr[5] FROM t;

Сэкономил бы я место с опцией массива? Будет ли штраф за скорость?

Любые другие идеи?


person punkish    schedule 03.06.2010    source источник


Ответы (4)


Я не вижу ничего полезного (и что-то терять) в хранении нескольких числовых полей в массиве.

размер каждого числового типа четко задокументирован, вы должны просто используйте шрифт наименьшего размера, совместимый с желаемым диапазоном разрешения; и это все, что вы можете сделать.

Я не думаю (но я не уверен), если есть какое-то требование выравнивания байтов для столбцов вдоль строки, в этом случае изменение порядка столбцов может изменить используемое пространство, но я так не думаю.

Кстати, на каждую строку приходится исправление, около 23 байта.

person leonbloy    schedule 03.06.2010
comment
Начиная с 9.2, это 24 байта на строку для заголовка строки и 4 байта для смещения страницы (хранится в заголовке страницы), или 28 байтов на строку. Есть и другие элементы, которые могут быть задействованы, например, 1 байт на 8 столбцов, которые поддерживают значения NULL (значения NULL хранятся в виде битовой маски). - person Sean; 15.10.2012
comment
@Sean: Это не совсем правильно. Заголовок строки (HeapTupleHeader) имеет 23 байта, а не 24, согласно руководству здесь: There is a fixed-size header (occupying 23 bytes on most machines), followed by an optional null bitmap, an optional object ID field. Разница существенна: битовая маска NULL для таблиц с количеством столбцов до 8 помещается в этот один свободный байт, что делает хранилище NULL фактически бесплатным для этих таблиц. - person Erwin Brandstetter; 15.10.2012
comment
Правильно, однако из-за выравнивания типов данных между байтами 23 и 24 почти наверняка есть дыра, а начиная с байта 25 начинается INT. Таким образом, заголовок составляет всего 23 байта, но потребляемое пространство составляет 24 байта. - person Sean; 15.10.2012

Колонка тетрис

На самом деле вы можете кое-что сделать, но это требует более глубокого понимания. Ключевое слово: заполнение выравнивания. Каждый тип данных имеет определенные требования к выравниванию.

Вы можете свести к минимуму потерю пространства из-за отступов между столбцами, расположив их в нужном порядке. В следующем (крайнем) примере будет потрачено много места на физическом диске:

CREATE TABLE t (
    e int2    -- 6 bytes of padding after int2
  , a int8
  , f int2    -- 6 bytes of padding after int2
  , b int8
  , g int2    -- 6 bytes of padding after int2
  , c int8
  , h int2    -- 6 bytes of padding after int2
  , d int8)

Чтобы сэкономить 24 байта на строку, используйте вместо этого:

CREATE TABLE t (
    a int8
  , b int8
  , c int8
  , d int8
  , e int2
  , f int2
  , g int2
  , h int2)   -- 4 int2 occupy 8 byte (MAXALIGN), no padding at the end

db‹›fiddle здесь
Старый sqlfiddle

Как показывает опыт, если вы поместите сначала 8-байтовые столбцы, а затем 4-байтовые, 2-байтовые и 1-байтовые столбцы последними, вы не ошибетесь.

boolean, uuid (!) и некоторые другие типы не требуют заполнения выравнивания. text, varchar и другие типы varlena (переменной длины) номинально требуют выравнивания int (4 байта на большинстве машин). Но я не заметил заполнения выравнивания в формате диска (в отличие от ОЗУ). В конце концов я нашел объяснение в примечании в исходном коде:

Обратите также внимание, что мы допускаем нарушение номинального выравнивания при хранении упакованных варлен; механизм TOAST заботится о том, чтобы скрыть это от большей части кода.

Таким образом, выравнивание int применяется только тогда, когда данные (возможно, сжатые), включая один начальный байт длины, превышают 127 байтов. Тогда хранилище varlena переключается на четыре начальных байта и требует выравнивания int.

Обычно вы можете сэкономить пару байтов на строку в лучшем случае, играя в тетрис с столбцами. Ничего из этого не требуется в большинстве случаев. Но с миллиардами строк это может легко означать пару гигабайт.

Вы можете проверить фактический размер столбца/строки с помощью функции < strong>pg_column_size().
Некоторые типы занимают больше места в оперативной памяти, чем на диске (сжатый или упакованный формат). Вы можете получить большие результаты для констант (формат ОЗУ), чем для столбцов таблицы, при тестировании одного и того же значения (или строки значений по сравнению со строкой таблицы) с помощью pg_column_size().

Наконец, некоторые типы могут быть сжатыми или всплывающими (хранятся вне строки). или оба.

Накладные расходы на кортеж (строку)

4 байта на строку для идентификатора элемента — вышеперечисленные соображения не распространяются.
И не менее 24 байтов (23 + заполнение) для заголовка кортежа. Руководство по макету страницы базы данных:

Существует заголовок фиксированного размера (занимающий 23 байта на большинстве машин), за которым следует необязательный нулевой битовый массив, необязательное поле идентификатора объекта и пользовательские данные.

Для заполнения между заголовком и пользовательскими данными вам необходимо знать MAXALIGN на вашем сервере — обычно 8 байтов в 64-разрядной ОС (или 4 байта в 32-разрядной ОС). Если вы не уверены, проверьте pg_controldata.

Запустите следующее в своем каталоге двоичных файлов Postgres, чтобы получить окончательный ответ:

./pg_controldata /path/to/my/dbcluster

Руководство:

Фактические пользовательские данные (столбцы строки) начинаются со смещения, указанного t_hoff, которое всегда должно быть кратно MAXALIGN расстоянию для платформы.

Таким образом, вы обычно получаете оптимальное хранилище, упаковывая данные кратно 8 байтам.

В приведенном вами примере нет ничего полезного. Он уже плотно упакован. 2 байта заполнения после последнего int2, 4 байта в конце. Вы можете объединить заполнение до 6 байтов в конце, что ничего не изменит.

Накладные расходы на страницу данных

Размер страницы данных обычно составляет 8 КБ. Некоторые накладные расходы/раздувание также на этом уровне: остатки недостаточно велики, чтобы поместиться в другой кортеж, и, что более важно, мертвые строки или процент, зарезервированный с помощью FILLFACTOR параметр.

Есть несколько других факторов, которые следует учитывать при выборе размера на диске:

Типы массивов?

С типом массива, который вы оценивали, вы должны добавить 24 байта служебных данных для типа. Кроме того, элементы массива, как обычно, занимают место. Нечего там выигрывать.

person Erwin Brandstetter    schedule 15.09.2011
comment
Как показывает опыт, если сначала поставить 8-байтовые столбцы, а затем 4-байтовые, 2-байтовые и 1-байтовые столбцы в последнюю очередь, вы не ошибетесь. несколько важных советов. - person Evan Carroll; 02.01.2017
comment
Это отличный/интересный ответ, но я не понимаю, почему порядок имен столбцов в CREATE TABLE вообще важен. Я не думал, что это имеет значение. Почему postgres не может / не может сделать эту оптимизацию тетриса для вас? Почему порядок столбцов считается достаточно важным, чтобы оставаться таким, как он определен? - person Russ; 09.05.2017
comment
@Russ: Потому что никто не реализовал разделение между логическим и физическим порядком столбцов. Это открытый элемент TODO, но не тривиальный, так как он возится с системой каталоги повсюду. Все еще верно почти 6 лет спустя. Начиная с Postgres 9.3, VIEW может служить для представления другого порядка столбцов для простых случаев (записи также распространяются автоматически). - person Erwin Brandstetter; 09.05.2017
comment
Спасибо, @Эрвин. Тем не менее, все еще кажется странным оставлять это пользователю. Я понимаю, что возиться с системными каталогами после создания таблицы было бы сложно, но не похоже, что это было бы проблемой, если бы изменение порядка было сделано заранее. В чем может быть проблема с чем-то вроде параметра хранения для достижения этого? Что-то вроде CREATE TABLE WITH column_reorder_ok, которое подразумевает, что меня не волнует логический порядок столбцов, так что возитесь с ним, как хотите, чтобы оптимизировать таблицу. Или, с вашей терминологией, WITH column_tetris_ok. :) - person Russ; 16.05.2017
comment
@Russ: Звучит как полезная функция. Инструмент для изменения порядка столбцов до создания таблицы, что позволяет избежать сложностей, связанных с работой с системными каталогами. Также может быть реализован в любом клиентском программном обеспечении, инструмент для оптимизации оператора CREATE TABLE для минимального хранения ... - person Erwin Brandstetter; 16.05.2017
comment
К вашему сведению: ваш ответ попал в топ-15 на сайте news.ycombinator.com (26 февраля 2018 г.) . поздравляю! комментарии здесь: news.ycombinator.com/item?id=16471242 - person hiro protagonist; 27.02.2018
comment
@hiroprotagonist: Спасибо, это объясняет всю эту внезапную любовь. - person Erwin Brandstetter; 27.02.2018
comment
@Russ, я согласен, что-то можно сделать, но это сложно. При добавлении и удалении столбцов существующие строки остаются совместимыми, поскольку их порядок не меняется. Новые добавленные столбцы помещаются в конец строки, в то время как удаленные столбцы никогда не исчезают, а просто отключаются и фактически обнуляются. (И, в качестве преимущества, открытые транзакции все еще могут видеть этот столбец.) Конечно, некоторую оптимизацию можно выполнить с помощью логико-физической карты в таблице или даже в дисковом сегменте (или как бы он ни назывался). - person Chris Cogdon; 27.02.2018
comment
@ErwinBrandstetter Будет ли иметь значение порядок столбцов, если я знаю, что некоторые из столбцов, вероятно, будут иметь нулевые значения в большинстве строк или кто-то все еще использует простое эмпирическое правило, как было предложено? - person Debasish Mitra; 21.04.2020
comment
@DebasishMitra: Столбцы, которые (почти) всегда имеют значение NULL, не мешают выравниванию, поскольку NULL представлен только одним битом в растровом изображении NULL. Их расположение не имеет значения. Может пойти в конец таблицы для гомеопатических выгод. - person Erwin Brandstetter; 29.12.2020

Из этой отличной документации: https://www.2ndquadrant.com/en/blog/on-rocks-and-sand/

Для таблицы с именем my_table, которая у вас уже есть или которую вы создаете в процессе разработки, этот запрос даст оптимальный порядок слева направо.

SELECT a.attname, t.typname, t.typalign, t.typlen
FROM pg_class c
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
WHERE c.relname = 'my_table'
 AND a.attnum >= 0
ORDER BY t.typlen DESC
person jboxxx    schedule 01.01.2020
comment
Этот запрос действительно универсален. Кто-нибудь знает об инструменте, который автоматизирует перевыравнивание столбцов в соответствии с этим запросом? wiki.postgresql.org/wiki/Alter_column_position - person olidem; 17.09.2020

Используйте базы данных No-SQL для такого рабочего процесса, потому что они похожи на json и нет ничего похожего на строки, вы бы лучше хранили данные для каждого ключа там, и структура каждого is независима ни от кого другого.

НЕКОТОРЫЕ Примеры: MongoDB, Cassandra.

person Dreaupiter    schedule 15.07.2021
comment
Этот ответ не касается вопроса ОП о производительности на конкретной платформе. Предложение использовать другую платформу не отвечает на вопрос. - person dmedine; 16.07.2021