PostgreSQL'de ekleme performansı nasıl hızlandırılır


216

Postgres ekleme performansını test ediyorum. Veri türü olarak bir sütun içeren bir tablo var. Üzerinde bir de indeks var. Ben bu sorguyu kullanarak veritabanını doldurdu:

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

Yukarıdaki sorgu ile bir kerede 10.000 satırları çok hızlı bir şekilde 10.000 satır ekledim. Veritabanı 6 milyon satıra ulaştıktan sonra, performans her 15 dakikada bir büyük ölçüde 1 Milyon satıra düştü. Yerleştirme performansını artırmak için herhangi bir hile var mı? Bu projede en uygun yerleştirme performansına ihtiyacım var.

5 GB RAM bulunan bir makinede Windows 7 Pro kullanma.


5
Sorularda da Pg sürümünüzden bahsetmeye değer. Bu durumda tonlarca fark yaratmaz, ancak birçok soru için yapar.
Craig Ringer

1
dizinleri tabloya bırakın ve varsa tetikler ve ekleme komut dosyasını çalıştırın. Toplu yükü tamamladıktan sonra dizinleri yeniden oluşturabilirsiniz.
Sandeep

Yanıtlar:


481

Bkz Bir veritabanını doldurmak PostgreSQL kılavuzda, depesz mükemmel kadar alışılmış makale konuyla ilgili ve bu SO soru .

(Bu yanıtın mevcut bir DB'ye toplu olarak yükleme veya yeni bir tane oluşturmakla ilgili olduğunu unutmayın. DB geri yükleme performansı pg_restoreveya çıktı psqlyürütme ile ilgileniyorsanız pg_dump, bunun çoğu geçerli değildir pg_dumpve pg_restorezaten oluşturma gibi şeyler yapıyor bir şema + veri geri yüklemesini bitirdikten sonra tetikler ve dizinler) .

Yapılacak çok şey var. İdeal çözüm, UNLOGGEDdizinleri olmayan bir tabloya içe aktarmak , ardından günlüğe değiştirmek ve dizinleri eklemek olacaktır. Maalesef PostgreSQL 9.4'te tabloları UNLOGGEDoturumdan oturum açmak için destek yoktur . 9.5 ALTER TABLE ... SET LOGGEDbunu yapmanıza izin vermek için ekler .

Toplu içe aktarma için veritabanınızı çevrimdışı duruma getirebiliyorsanız, kullanın pg_bulkload.

Aksi takdirde:

  • Tablodaki tetikleyicileri devre dışı bırak

  • İçe aktarma işlemine başlamadan önce dizinleri bırakın, daha sonra bunları yeniden oluşturun. ( Bir geçişte bir dizin oluşturmak, aynı verileri aşamalı olarak eklemekten çok daha az zaman alır ve elde edilen dizin çok daha küçüktür).

  • İçe aktarma işlemini tek bir işlemde yapıyorsanız, taahhütte bulunmadan önce yabancı anahtar kısıtlamalarını bırakmak, içe aktarma işlemini yapmak ve kısıtlamaları yeniden oluşturmak güvenlidir. Geçersiz veriler ekleyebileceğiniz için içe aktarma birden çok işleme bölünmüşse bunu yapmayın.

  • Mümkünse s COPYyerine kullanınINSERT

  • COPYKullanamıyorsanız INSERT, pratikse çok değerli s kullanmayı düşünün . Bunu zaten yapıyor gibisin. Yine de tek bir yerde çok fazla değer listelemeye çalışmayın VALUES; bu değerlerin belleğe birkaç kez uyması gerekir, bu yüzden ifade başına birkaç yüz değerinde tutun.

  • Ekleme işlemlerinizi yüz binlerce veya milyonlarca ekleme yaparak açık işlemlere toplu olarak ekleyin. Pratik bir sınır AFAIK yoktur, ancak toplu iş, giriş verilerinizdeki her toplu işin başlangıcını işaretleyerek bir hatadan kurtulmanızı sağlar. Yine, bunu zaten yapıyor gibi görünüyorsunuz.

  • Fsync () maliyetlerini azaltmak için kullanın synchronous_commit=offve çok büyük commit_delay. Yine de, çalışmanızı büyük işlemlere kattıysanız çok yardımcı olmaz.

  • INSERTveya COPYbirkaç bağlantıdan paralel olarak. Kaç tanesi donanımınızın disk alt sistemine bağlıdır; genel kural olarak, doğrudan bağlı depolama alanı kullanılıyorsa, fiziksel sabit sürücü başına bir bağlantı istiyorsunuz.

  • Yüksek bir checkpoint_segmentsdeğer belirleyin ve etkinleştirin log_checkpoints. PostgreSQL günlüklerine bakın ve çok sık meydana gelen kontrol noktalarından şikayet etmediğinden emin olun.

  • Yalnızca ve ancak sistem içe aktarma sırasında çökerse, PostgreSQL kümenizin tamamını (veritabanınız ve aynı kümedeki diğerlerini) yıkıcı bozulmaya uğratırsanız, Pg'yi durdurabilir, ayarlayabilir fsync=off, Pg'yi başlatabilir, içe aktarma, sonra (hayati olarak) Pg'yi durdurun ve fsync=ontekrar ayarlayın . Bkz. WAL yapılandırması . PostgreSQL kurulumunuzdaki herhangi bir veritabanında önem verdiğiniz veriler varsa bunu yapmayın. Ayarladıysanız, fsync=offşunları da ayarlayabilirsiniz full_page_writes=off; yine, veritabanı bozulmasını ve veri kaybını önlemek için içe aktarma işleminden sonra tekrar açmayı unutmayın. Pg kılavuzundaki dayanıklı olmayan ayarlara bakın .

Ayrıca sisteminizi ayarlamaya da bakmalısınız:

  • Depolama için mümkün olduğunca kaliteli SSD'ler kullanın . Güvenilir, güç korumalı geri yazma önbelleklerine sahip iyi SSD'ler, taahhüt oranlarını inanılmaz derecede daha hızlı hale getirir. Yukarıdaki tavsiyelere uyduğunuzda daha az faydalıdırlar - bu da disk temizleme / fsync()s sayısını azaltır - ancak yine de büyük bir yardımcı olabilir. Verilerinizi tutmayı önemsemediğiniz sürece uygun elektrik kesintisi koruması olmadan ucuz SSD'leri kullanmayın.

  • Doğrudan bağlı depolama için RAID 5 veya RAID 6 kullanıyorsanız, şimdi durun. Verilerinizi yedekleyin, RAID dizinizi RAID 10 olarak yeniden yapılandırın ve tekrar deneyin. RAID 5/6, toplu yazma performansı için umutsuzdur - ancak büyük bir önbelleğe sahip iyi bir RAID denetleyicisi yardımcı olabilir.

  • Büyük bir pil destekli geri yazma önbelleğine sahip bir donanım RAID denetleyicisi kullanma seçeneğiniz varsa, bu çok fazla iş yükü olan iş yükleri için yazma performansını gerçekten artırabilir. Bir commit_delay ile async taahhüdü kullanıyorsanız veya toplu yükleme sırasında daha az büyük işlem yapıyorsanız bu pek yardımcı olmaz.

  • Mümkünse, WAL ( pg_xlog) öğesini ayrı bir disk / disk dizisinde saklayın . Aynı diskte ayrı bir dosya sistemi kullanmanın pek bir anlamı yoktur. İnsanlar genellikle WAL için bir RAID1 çifti kullanmayı tercih ederler. Yine, bunun yüksek taahhüt oranları olan sistemler üzerinde daha fazla etkisi vardır ve veri yükleme hedefi olarak engellenmemiş bir tablo kullanıyorsanız çok az etkisi vardır.

Ayrıca ilginizi çekebilir Hızlı test için PostgreSQL'i optimize edin .


1
İyi kalitede SSD'ler kullanılırsa RAID 5/6'dan yazma cezasının biraz azaltıldığını kabul eder misiniz? Açıkçası hala bir ceza var, ama fark HDD'lerden çok daha az acı verici.

1
Bunu test etmedim. Muhtemelen daha az kötü olduğunu söyleyebilirim - kötü yazma amplifikasyon etkileri ve (küçük yazma işlemleri için) bir okuma-değiştirme-yazma döngüsü ihtiyacı hala var, ancak aşırı arama için ciddi ceza bir sorun olmamalı.
Craig Ringer

Dizinleri bırakmak yerine devre dışı bırakabilir miyiz, örneğin indisvalid( postgresql.org/docs/8.3/static/catalog-pg-index.html ) öğesini false olarak ayarlayıp verileri yükleyip ardından dizinleri çevrimiçi duruma getirerek REINDEX?
Vladislav Rastrusny

1
@CraigRinger RAID-5 ve RAID-10'u SSD'lerle Perc H730'da test ettim. RAID-5 aslında daha hızlı. Ayrıca, ek / işlemlerin büyük bytea'larla birlikte kopyadan daha hızlı göründüğünü belirtmek gerekir. Genel olarak iyi bir tavsiye olsa.
atlaste

2
Herkes herhangi bir büyük hız gelişmeler görüyor UNLOGGED? Hızlı bir test% 10-20 iyileşme gösterir.
serg

15

Kullanım COPY table TO ... WITH BINARYbelgelerine göre olan "dır hızlı metin ve CSV biçimlerinden daha biraz ." Bunu yalnızca eklemek için milyonlarca satırınız varsa ve ikili verilerden memnunsanız yapın.

İşte ikili girişli psycopg2 kullanan Python'da bir örnek tarif .


1
İkili mod, zaman damgaları gibi bazı girdilerde ayrıştırmanın önemsiz olduğu yerlerde büyük bir zaman tasarrufu sağlayabilir. Birçok veri türü için çok fazla fayda sağlamaz veya artan bant genişliği (örneğin küçük tamsayılar) nedeniyle biraz daha yavaş olabilir. Yükseltmek için iyi bir nokta.
Craig Ringer

11

Mükemmel Craig Ringer'ın gönderisi ve depesz'in blog gönderisine ek olarak, bir işlemin içinde hazırlanmış deyim eklerini kullanarak eklerinizi ODBC ( psqlodbc ) arayüzü üzerinden hızlandırmak isterseniz, bunu yapmak için yapmanız gereken birkaç ekstra şey vardır. Hızlı çalış:

  1. Protocol=-1Bağlantı dizesinde belirterek , hataların geri alınma düzeyini "İşlem" olarak ayarlayın . Varsayılan olarak psqlodbc, tüm işlem yerine her ifade için bir SAVEPOINT oluşturan ve eklemeleri yavaşlatan "Statement" seviyesini kullanır.
  2. UseServerSidePrepare=1Bağlantı dizesinde belirterek sunucu tarafında hazırlanan ifadeleri kullanın . Bu seçenek olmadan istemci, eklenen her satırla birlikte tüm insert deyimini gönderir.
  3. Her ifadede otomatik taahhüdü devre dışı bırak SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. Tüm satırlar eklendikten sonra işlemi kullanarak gerçekleştirin SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. Açıkça bir işlem açmaya gerek yoktur.

Ne yazık ki, psqlodbc SQLBulkOperationsbir dizi hazırlıksız insert deyimi vererek "uygular" , böylece en hızlı insert elde etmek için yukarıdaki adımları manuel olarak kodlamak gerekir.


A8=30000000Bağlantı soketindeki büyük soket arabellek boyutu da ekleri hızlandırmak için kullanılmalıdır.
Andrus

10

Bugün aynı konuda yaklaşık 6 saat geçirdim. Uçlar 5MI (toplam 30MI dışında) sıraya kadar 'normal' bir hızda (100K başına 3 saniyeden az) ve daha sonra performans büyük ölçüde batırılır (100K başına 1 dakikaya kadar).

İşe yaramayan ve doğrudan ete kesilmeyen şeyleri listelemeyeceğim.

Ben bir birincil anahtar düştü mutlulukla 100K başına 3sec daha az sabit bir hızda hedeflerine aktı (bir GUID oldu) hedef masaya ve benim 30 mi veya satırları.


7

UUID'ler ( tam olarak sizin durumunuz değil ) ile sütunlar eklemek ve @Dennis yanıtına (henüz yorum yapamıyorum) eklemek istiyorsanız , gen_random_uuid () (PG 9.4 ve pgcrypto modülünü gerektirir) kullanmaktan ( lot) uuid_generate_v4 () 'den daha hızlı

=# 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)

vs


=# 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)

Ayrıca, bunu yapmanın önerilen resmi yolu

Not

Yalnızca rastgele oluşturulmuş (sürüm 4) UUID'lere ihtiyacınız varsa, bunun yerine pgcrypto modülünden gen_random_uuid () işlevini kullanmayı düşünün.

Bu takma süresi 3.7M sıralar için ~ 2 saatten ~ 10 dakikaya düştü.


1

En uygun Ekleme performansı için bu sizin için bir seçenekse dizini devre dışı bırakın. Bunun dışında daha iyi donanım (disk, bellek) de yardımcı olur


-1

Bu ekleme performansı sorunuyla da karşılaştım. Benim çözüm ekleme işini bitirmek için bazı gitmek rutinleri spawn olduğunu. Bu arada, SetMaxOpenConnsuygun bir sayı verilmelidir, aksi takdirde çok fazla açık bağlantı hatası uyarılır.

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()

Yükleme hızı projem için çok daha hızlı. Bu kod pasajı nasıl çalıştığı hakkında bir fikir verdi. Okuyucular kolayca değiştirebilmelidir.


Bunu söyleyebilirsin. Ama benim durumumda milyonlarca satır için çalışma süresini birkaç saatten birkaç dakikaya düşürüyor. :)
Patrick
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.