Як прискорити продуктивність вставки в PostgreSQL


216

Я тестую продуктивність вставки Postgres. У мене є таблиця з одним стовпцем, номер якого є її типом даних. На ньому також є індекс. Я заповнив базу даних за допомогою цього запиту:

insert into aNumber (id) values (564),(43536),(34560) ...

Я вставив 4 мільйони рядків дуже швидко 10 000 одночасно із запитом вище. Коли база даних досягла 6 мільйонів рядків, продуктивність різко знижувалася до 1 мільйона рядків кожні 15 хв. Чи є якийсь трюк для підвищення продуктивності вставки? Мені потрібні оптимальні показники вставки для цього проекту.

Використання Windows 7 Pro на машині з 5 Гб оперативної пам’яті.


5
Варто згадати і вашу Pg-версію в питаннях. У цьому випадку це не має різниці, але це робить для багатьох питань.
Крейг Рінгер

1
опустити індекси на таблицю та запустить, якщо такі є, та запустіть сценарій вставки. Після завершення об'ємного навантаження ви можете відтворити індекси.
Сандіп

Відповіді:


481

Дивіться, як заповнити базу даних у посібнику PostgreSQL, чудову статтю depesz на цю тему та це питання .

(Зверніть увагу, що ця відповідь стосується масового завантаження даних у існуючу БД або створення нового. Якщо ви зацікавлені, відновлення продуктивності БД за допомогою pg_restore або psqlвиконання pg_dumpпродукції, велика частина це не відноситься , тому що pg_dumpі pg_restoreвже роблять такі речі , як створення запускає та індексує після завершення схеми + відновлення даних) .

Потрібно зробити багато. Ідеальним рішенням було б імпортувати в UNLOGGEDтаблицю без індексів, а потім змінити її на журнал та додати індекси. На жаль, у PostgreSQL 9.4 немає підтримки для зміни таблиць із UNLOGGEDжурналу. 9.5 додає, ALTER TABLE ... SET LOGGEDщоб дозволити це робити.

Якщо ви можете взяти свою базу даних офлайн для масового імпорту, використовуйте pg_bulkload.

Інакше:

  • Вимкнути всі тригери на столі

  • Відкиньте індекси перед початком імпорту, а потім створіть їх. (Це займає багато створення індексу за один прохід менше часу, ніж на поступове додавання тих самих даних, і отриманий індекс набагато компактніший).

  • Якщо ви здійснюєте імпорт в рамках однієї транзакції, безпечно відмовитись від обмежень зовнішніх ключів, виконати імпорт та заново створити обмеження, перш ніж здійснити. Не робіть цього, якщо імпорт розділений на кілька транзакцій, оскільки ви можете ввести недійсні дані.

  • Якщо можливо, використовуйте COPYзамість INSERTs

  • Якщо ви не можете використовувати, COPYрозгляньте можливість використання багатозначних INSERTs, якщо це практично. Ви, здається, це вже робите. Не намагайтеся перераховувати занадто багато значень в одному, VALUESхоча; ці значення повинні поміститися в пам'яті кілька разів, тому зберігайте їх до декількох сотень за заявою.

  • Об’єднайте свої вставки в явні транзакції, роблячи сотні тисяч або мільйони вставок за транзакцію. Практичного обмеження AFAIK не існує, але пакетне використання дозволить вам відновитись після помилки, позначивши початок кожної партії у своїх вхідних даних. Знову, здається, ви вже робите це.

  • Використовуйте synchronous_commit=offі величезну commit_delayсуму, щоб зменшити витрати на fsync (). Це не дуже допоможе, якщо ви ввели велику роботу у великі транзакції.

  • INSERTабо COPYпаралельно з декількох з'єднань. Скільки залежить від дискової підсистеми вашого обладнання; як правило, потрібно використовувати одне підключення на фізичному жорсткому диску, якщо використовується пряме додане сховище.

  • Встановіть високе checkpoint_segmentsзначення та увімкніть log_checkpoints. Перегляньте журнали PostgreSQL і переконайтеся, що вони не скаржаться на контрольні пункти, що трапляються занадто часто.

  • Якщо і лише якщо ви не заперечуєте втратити весь кластер PostgreSQL (вашу базу даних та будь-який інший на цьому ж кластері), до катастрофічної пошкодження, якщо система під час імпорту вийде з ладу, ви можете зупинити Pg, встановити fsync=off, запустити Pg, зробити імпорт, потім (життєво) зупиніть Pg і встановіть fsync=onзнову. Див. Конфігурацію WAL . Не робіть цього, якщо у вашій базі даних PostgreSQL вже є будь-які ваші дані. Якщо ви встановите, fsync=offви також можете встановити full_page_writes=off; знову ж таки, не забудьте ввімкнути його після імпорту, щоб запобігти пошкодженню бази даних та втраті даних. Дивіться нестабільні налаштування в посібнику з Pg.

Слід також переглянути налаштування системи:

  • Використовуйте якісні SSD для зберігання, наскільки це можливо. Хороші SSD-диски з надійними, захищеними від потужності кешами зворотного запису роблять швидкість фіксації неймовірно швидшою. Вони менш вигідні, коли ви дотримуєтесь рекомендацій, наведених вище - що зменшує вимивання диска / кількість fsync()с -, але все ж може стати великою допомогою. Не використовуйте дешеві жорсткі диски без належного захисту від відключення електроенергії, якщо ви не дбаєте про збереження своїх даних.

  • Якщо ви використовуєте RAID 5 або RAID 6 для прямого вкладеного сховища, зупиніть зараз. Створіть резервні копії даних, реструктуруйте RAID-масив до RAID 10 та повторіть спробу. RAID 5/6 безнадійні для масової продуктивності запису - хоча хороший RAID-контролер з великим кешем може допомогти.

  • Якщо у вас є можливість використовувати апаратний RAID-контролер з великим кешованим резервним запасом, це може справді покращити продуктивність запису для робочих навантажень з великою кількістю комісій. Це не так сильно допомагає, якщо ви використовуєте команду async з командою commit_delay або якщо ви робите менше великих транзакцій під час масового завантаження.

  • Якщо можливо, збережіть WAL ( pg_xlog) на окремому масиві диска / диска. Мало сенсу використовувати окрему файлову систему на тому ж диску. Люди часто вибирають використовувати пару RAID1 для WAL. Знову ж таки, це має більший вплив на системи з високими коефіцієнтами фіксації, і це мало ефекту, якщо ви використовуєте незакриту таблицю в якості цілі завантаження даних.

Вас також може зацікавити Оптимізація PostgreSQL для швидкого тестування .


1
Чи погоджуєтесь ви, що штраф за запис від RAID 5/6 дещо пом'якшується, якщо використовуються якісні SSD? Очевидно, що все ще існує штраф, але я думаю, що різниця набагато менш болісна, ніж це з жорсткими дисками.

1
Я цього не перевіряв. Я б сказав, що це, мабуть, менш погано - негативні ефекти посилення запису та (для невеликих записів) необхідність циклу читання-модифікації запису все ще існують, але суворе покарання за надмірні пошуки має бути проблемою.
Крейг Рінгер

Чи можемо ми просто відключити індекси замість того, щоб скидати їх, наприклад, встановивши indisvalid( postgresql.org/docs/8.3/static/catalog-pg-index.html ) на помилкові, потім завантаживши дані, а потім занести індекси в Інтернеті REINDEX?
Владислав Раструсний

1
@CraigRinger Я протестував RAID-5 проти RAID-10 із SSD на Perc H730. RAID-5 насправді швидше. Крім того, варто відзначити, що вставка / транзакції в поєднанні з великими байтами здаються швидшими, ніж копіювання. Хоча в цілому хороша порада.
атлас

2
Хтось бачить якісь значні покращення швидкості UNLOGGED? Швидкий тест показує щось на зразок поліпшення на 10-20%.
серг

15

Використання COPY table TO ... WITH BINARYвідповідно до документації " дещо швидше, ніж текстові та CSV формати ". Робіть це лише в тому випадку, якщо у вас є мільйони рядків для вставки та якщо вам зручно використовувати бінарні дані.

Ось приклад рецепту в Python, використовуючи psycopg2 з двійковим входом .


1
Бінарний режим може значно заощадити час на деяких входах, таких як часові позначки, де їх аналіз нетривіальний. Для багатьох типів даних це не дає великої користі або навіть може бути трохи повільніше через збільшення пропускної здатності (наприклад, малі цілі числа). Гарний момент підняття його.
Крейг Рінгер

11

Окрім чудового повідомлення Крейга Рінгера та публікації в блозі Depesz, якщо ви хочете пришвидшити свої вставки через інтерфейс ODBC ( psqlodbc ), використовуючи вкладені готові оператори всередині транзакції, потрібно зробити кілька додаткових речей, щоб зробити це працювати швидко:

  1. Встановіть рівень помилок відхилення за рівнем "Transaction", вказавши Protocol=-1в рядку з'єднання. За замовчуванням psqlodbc використовує рівень "Statement", який створює SAVEPOINT для кожного оператора, а не всієї транзакції, роблячи вставки повільнішими.
  2. Використовуйте підготовлені на сервері оператори, вказавши UseServerSidePrepare=1в рядку з'єднання. Без цієї опції клієнт надсилає всю заяву вставки разом з кожним вставленим рядком.
  3. Вимкнення автоматичного фіксації кожного оператора за допомогою SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. Після того, як всі рядки будуть вставлені, виконайте транзакцію, використовуючи SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. Немає необхідності явно відкривати транзакцію.

На жаль, psqlodbc "реалізує" SQLBulkOperations, видаючи серію непідготовлених операторів вставки, так що для досягнення найшвидшого вставки потрібно кодувати вищезазначені кроки вручну.


Великий розмір буфера розетки, A8=30000000в рядку підключення також слід використовувати для прискорення вставок.
Андрус

10

Я сьогодні витратив близько 6 годин на те саме питання. Вставки йдуть з "регулярною" швидкістю (менше 3 сек на 100 К) аж до 5 ММ ​​(із загальних 30 ММ) рядків, а потім продуктивність різко знижується (аж до 1 хвилини на 100 К).

Я не буду перераховувати всі речі, які не працювали, і нарізати прямо на м’ясо.

Я впустив первинний ключ на цільову таблицю (це був GUID), і мої 30 МІ чи рядків радісно перепливали до місця призначення зі постійною швидкістю менше 3сек за 100 К.


7

Якщо ви хочете вставити стовпці з UUID (це не зовсім ваш випадок) і додати до @Dennis відповідь (я поки не можу коментувати), порадьте, ніж використовувати gen_random_uuid () (вимагає PG 9.4 та pgcrypto модуля) є ( багато) швидше, ніж uuid_generate_v4 ()

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)

проти


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)

Крім того, це запропонований офіційний спосіб зробити це

Примітка

Якщо вам потрібні лише випадково генеровані UUID (версія 4) UUID, розгляньте замість цього функцію gen_random_uuid () з модуля pgcrypto.

Час вставки, що випав, від ~ 2 години до ~ 10 хвилин протягом 3,7 М рядків.


1

Для оптимальної продуктивності вставки відключіть індекс, якщо це варіант для вас. Крім цього, також корисне обладнання (диск, пам'ять)


-1

Я також зіткнувся з цією проблемою продуктивності вставки. Моє рішення породжує кілька процедур, щоб закінчити роботу над введенням. Тим часом, SetMaxOpenConnsслід вказати належну кількість, інакше надмірна помилка відкритого з'єднання буде попереджена

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

Швидкість завантаження для мого проекту набагато швидша. Цей фрагмент коду просто дав уявлення про те, як він працює. Читачі повинні мати можливість легко змінювати його.


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