Настройка PostgreSQL для производительности чтения

39

Наша система записывает много данных (вид системы Big Data). Производительность записи достаточно для наших нужд, но производительность чтения слишком низкая.

Структура первичного ключа (ограничения) одинакова для всех наших таблиц:

timestamp(Timestamp) ; index(smallint) ; key(integer).

Таблица может содержать миллионы строк, даже миллиарды строк, и запрос на чтение обычно относится к определенному периоду (отметка времени / индекс) и тегу. Распространено иметь запрос, который возвращает около 200 тыс. Строк. В настоящее время мы можем читать около 15 тыс. Строк в секунду, но нам нужно быть в 10 раз быстрее. Возможно ли это, и если да, то как?

Примечание: PostgreSQL поставляется с нашим программным обеспечением, поэтому аппаратное обеспечение отличается от одного клиента к другому.

Это виртуальная машина, используемая для тестирования. Хост виртуальной машины - Windows Server 2008 R2 x64 с 24,0 ГБ ОЗУ.

Спецификация сервера (виртуальная машина VMWare)

Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)

postgresql.conf Оптимизации

shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)

Определение таблицы

CREATE TABLE "AnalogTransition"
(
  "KeyTag" integer NOT NULL,
  "Timestamp" timestamp with time zone NOT NULL,
  "TimestampQuality" smallint,
  "TimestampIndex" smallint NOT NULL,
  "Value" numeric,
  "Quality" boolean,
  "QualityFlags" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
  CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
      REFERENCES "Tag" ("Key") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE,
  autovacuum_enabled=true
);

запрос

Выполнение запроса в pgAdmin3 занимает около 30 секунд, но мы бы хотели получить тот же результат в течение 5 секунд, если это возможно.

SELECT 
    "AnalogTransition"."KeyTag", 
    "AnalogTransition"."Timestamp" AT TIME ZONE 'UTC', 
    "AnalogTransition"."TimestampQuality", 
    "AnalogTransition"."TimestampIndex", 
    "AnalogTransition"."Value", 
    "AnalogTransition"."Quality", 
    "AnalogTransition"."QualityFlags", 
    "AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;

Объясните 1

"Limit  (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
"  Buffers: shared hit=190147"
"  ->  Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
"        Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
"        Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"

Объяснить 2

В моем последнем тесте мои данные заняли 7 минут! Увидеть ниже:

"Limit  (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
"  ->  Index Scan using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"
JPelletier
источник

Ответы:

52

Выравнивание данных и размер хранилища

На самом деле, издержки на индексный кортеж составляют 8 байт для заголовка кортежа плюс 4 байта для указателя элемента.

Связанный:

У нас есть три столбца для первичного ключа:

PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag")

"Timestamp"      timestamp (8 bytes)
"TimestampIndex" smallint  (2 bytes)
"KeyTag"         integer   (4 bytes)

Результаты в:

 4-байтовый указатель элемента в заголовке страницы (не считая кратного 8 байтам)

 8 байтов для заголовка кортежа индекса
 8 байт "Метка времени"
 2 байта "TimestampIndex"
 2 байта для выравнивания данных
 4 байта "KeyTag" 
 0 дополняет до ближайшего кратного 8 байтов
-----
28 байт на индексный кортеж; плюс несколько байтов служебных данных.

Больше об измерении размера объекта в этом связанном ответе:

Порядок столбцов в многоколоночном индексе

Прочитайте эти два вопроса и ответы, чтобы понять:

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

Как правило, в многостолбцовом индексе столбцы «равенства» должны идти первыми, а столбцы «диапазона» - последними:

Поэтому попробуйте дополнительный индекс с обратным порядком столбцов :

CREATE INDEX analogransition_mult_idx1
   ON "AnalogTransition" ("KeyTag", "TimestampIndex", "Timestamp");

Это зависит от распределения данных. Но с millions of row, even billion of rowsэтим может быть существенно быстрее.

Размер кортежа на 8 байтов больше благодаря выравниванию и заполнению данных. Если вы используете это как обычный индекс, вы можете попытаться удалить третий столбец "Timestamp". Может быть немного быстрее или нет (так как это может помочь с сортировкой).

Возможно, вы захотите сохранить оба индекса. В зависимости от ряда факторов, ваш первоначальный индекс может быть предпочтительным - в частности, с небольшим LIMIT.

автовакуум и настольная статистика

Статистика вашей таблицы должна быть актуальной. Я уверен, что у вас работает автовакуум .

Поскольку ваша таблица кажется огромной и статистика важна для правильного плана запросов, я бы существенно увеличил целевой показатель статистики для соответствующих столбцов:

ALTER TABLE "AnalogTransition" ALTER "Timestamp" SET STATISTICS 1000;

... или даже выше с миллиардами строк. Максимум 10000, по умолчанию 100.

Сделайте это для всех столбцов WHEREили ORDER BYпредложений. Тогда беги ANALYZE.

Макет таблицы

При этом, если вы примените то, что узнали о выравнивании и заполнении данных, эта оптимизированная компоновка таблицы должна сэкономить некоторое дисковое пространство и немного повысить производительность (игнорируя pk & fk):

CREATE TABLE "AnalogTransition"(
  "Timestamp" timestamp with time zone NOT NULL,
  "KeyTag" integer NOT NULL,
  "TimestampIndex" smallint NOT NULL,
  "TimestampQuality" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  "QualityFlags" smallint,
  "Quality" boolean,
  "Value" numeric
);

CLUSTER / pg_repack

Чтобы оптимизировать производительность чтения для запросов, использующих определенный индекс (будь то исходный или предложенный мной вариант), вы можете переписать таблицу в физическом порядке индекса. CLUSTERделает это, но это довольно агрессивно и требует эксклюзивной блокировки на время операции.
pg_repackэто более сложная альтернатива, которая может сделать то же самое без эксклюзивной блокировки на столе.
pg_squeezeболее поздний аналогичный инструмент (еще не использовал его).

Это может существенно помочь с огромными таблицами, так как нужно читать намного меньше блоков таблицы.

баран

Как правило, 2 ГБ физической ОЗУ просто недостаточно для быстрой обработки миллиардов строк. Большее количество ОЗУ может иметь большое значение, сопровождаемое адаптированными настройками: очевидно, больше effective_cache_sizeс самого начала.

Эрвин Брандштеттер
источник
2
Я добавил простой индекс только для KeyTag, и теперь он выглядит довольно быстро. Я также буду применять ваши рекомендации по выравниванию данных. Большое спасибо!
JPelletier
9

Итак, из планов я вижу одну вещь: ваш индекс либо раздут (затем вместе с базовой таблицей), либо просто не очень хорош для такого рода запросов (я попытался рассмотреть это в своем последнем комментарии выше).

Одна строка индекса содержит 14 байтов данных (а некоторые для заголовка). Теперь, исходя из чисел, указанных в плане: вы получили 500 000 строк на 190147 страницах - это означает, в среднем, менее 3 полезных строк на странице, то есть около 37 байтов на страницу 8 КБ. Это очень плохое соотношение, не так ли? Поскольку первый столбец индекса является Timestampполем и используется в запросе в качестве диапазона, планировщик может - и делает - выбрать индекс, чтобы найти совпадающие строки. Но TimestampIndexв WHEREусловиях ничего не упоминается , поэтому фильтрация KeyTagне очень эффективна, так как эти значения предположительно появляются случайным образом на страницах индекса.

Итак, одна возможность - это изменение определения индекса на

CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp", "KeyTag", "TimestampIndex")

(или, учитывая загрузку вашей системы, создайте этот индекс как новый:

CREATE INDEX CONCURRENTLY "idx_AnalogTransition" 
    ON "AnalogTransition" ("Timestamp", "KeyTag", "TimestampIndex");
  • это наверняка займет некоторое время, но вы все еще можете работать в то же время.)

Другая возможность, что большая часть страниц индекса занята мертвыми строками, которые можно удалить путем очистки. Вы создали таблицу с настройкой autovacuum_enabled=true- но вы когда-нибудь начинали автоочистку? Или запустить VACUUMвручную?

Dezso
источник