Налаштування PostgreSQL для продуктивності читання


39

Наша система записує безліч даних (різновид системи Big Data). Виконання записів досить добре для наших потреб, але виконання читання дійсно занадто повільне.

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

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

Таблиця може містити мільйони рядків, навіть мільярди рядків, а запит на читання, як правило, на певний період (часова мітка / індекс) та тег. Звичайний запит, який повертає близько 200 тис. Рядків. Наразі ми можемо читати близько 15 к рядків за секунду, але нам потрібно бути в 10 разів швидшими. Це можливо і якщо так, то як?

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

Це ВМ, що використовується для тестування. Хост VM - це 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
);

Запит

На виконання запиту потрібно тривати приблизно 30 секунд у pgAdmin3, але ми хотіли б мати той самий результат за 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"

Відповіді:


52

Вирівнювання даних та розмір пам’яті

Насправді накладні витрати на один кортеж становлять 24 байти для заголовка кортежу плюс 4 байти для вказівника елемента.
Більш детально в розрахунку в цій відповіді:

Основи вирівнювання даних та прокладки у цій відповіді на відповідь:

У нас є три стовпчики для основного ключа:

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

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

Призводить до:

 4 байти вказівника елемента в заголовку сторінки (не рахуючи кратного 8 байт)
---
23 байти для заголовка кортежу
 1 байт-прокладка для вирівнювання даних (або растрова карта NULL)
 8 байт "Timestamp"
 2 байти "TimestampIndex"
 2 байти прокладки для вирівнювання даних
 4 байти "KeyTag" 
 0 прокладки до найближчого кратного 8 байт
-----
44 байти на кортеж

Детальніше про вимірювання розміру об'єкта в цій відповіді:

Порядок стовпців у багатоколоновому покажчику

Прочитайте ці два питання та відповіді, щоб зрозуміти:

Те, як у вас є ваш індекс (первинний ключ), ви можете отримувати рядки без кроку сортування, що привабливо, особливо для 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- це більш досконала альтернатива, яка може зробити те ж саме, не маючи ексклюзивного замка на столі.
Це може суттєво допомогти з величезними таблицями, оскільки потрібно читати набагато менше блоків таблиці.

ОЗП

Як правило, 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вручну?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.