Mengapa UPDATE Postgres membutuhkan waktu 39 jam?

17

Saya memiliki tabel Postgres dengan ~ 2,1 juta baris. Saya menjalankan pembaruan di bawah ini:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

Permintaan ini membutuhkan waktu 39 jam untuk berjalan. Saya menjalankan ini pada prosesor laptop 4 (fisik) core i7 Q720, banyak RAM, tidak ada yang menjalankan sebagian besar waktu. Tidak ada kendala ruang HDD. Meja baru-baru ini telah disedot, dianalisis, dan diindeks ulang.

Sepanjang waktu kueri berjalan, setidaknya setelah awal WITHselesai, penggunaan CPU biasanya rendah, dan HDD digunakan 100%. HDD digunakan sangat keras sehingga aplikasi lain berjalan jauh lebih lambat dari biasanya.

Pengaturan daya laptop berada pada kinerja tinggi (Windows 7 x64).

Inilah yang MENJELASKAN:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1hanya mengecualikan beberapa puluh ribu baris. Bahkan dengan WHEREklausa itu, saya masih beroperasi di lebih dari 2 juta baris.

Hard drive sepenuhnya dienkripsi dengan TrueCrypt 7.1a. Yang memperlambat segalanya sedikit, tetapi tidak cukup untuk menyebabkan query untuk mengambil yang berjam-jam.

Bagian ini WITHhanya membutuhkan waktu sekitar 3 menit untuk dijalankan.

The arrest_idlapangan tidak indeks untuk kunci asing. Ada 8 indeks dan 2 kunci asing di tabel ini. Semua bidang lain dalam kueri diindeks.

The arrest_idlapangan tidak memiliki kendala kecuali NOT NULL.

Tabel ini memiliki total 32 kolom.

arrest_idadalah tipe karakter yang bervariasi (20) . Saya menyadari rank()menghasilkan nilai numerik, tetapi saya harus menggunakan karakter yang bervariasi (20) karena saya memiliki baris lain di mana citing_jurisdiction<>1yang menggunakan data non-numerik untuk bidang ini.

The arrest_idbidang itu kosong untuk semua baris dengan citing_jurisdiction=1.

Ini adalah laptop pribadi kelas atas (per 1 tahun yang lalu). Saya satu-satunya pengguna. Tidak ada pertanyaan atau operasi lain yang berjalan. Mengunci sepertinya tidak mungkin.

Tidak ada pemicu di mana pun dalam tabel ini atau di mana pun dalam database.

Operasi lain pada database ini tidak pernah memakan waktu lama. Dengan pengindeksan yang tepat, SELECTpermintaan biasanya cukup cepat.

Aren Cambre
sumber
Itu Seq Scanagak menakutkan ...
rogerdpack

Jawaban:

18

Saya memiliki sesuatu yang serupa terjadi baru-baru ini dengan tabel 3,5 juta baris. Pembaruan saya tidak akan pernah selesai. Setelah banyak bereksperimen dan frustrasi, saya akhirnya menemukan pelakunya. Ternyata indeks di atas meja sedang diperbarui.

Solusinya adalah dengan menghapus semua indeks di atas meja yang diperbarui sebelum menjalankan pernyataan pembaruan. Setelah saya melakukan itu, pembaruan selesai dalam beberapa menit. Setelah pembaruan selesai, saya membuat kembali indeks dan kembali berbisnis. Ini mungkin tidak akan membantu Anda pada saat ini tetapi mungkin orang lain mencari jawaban.

Saya akan menyimpan indeks di atas meja tempat Anda menarik data. Yang itu tidak harus terus memperbarui indeks apa pun dan akan membantu menemukan data yang ingin Anda perbarui. Ini berjalan baik di laptop yang lambat.

JC Avena
sumber
3
Saya mengalihkan jawaban terbaik untuk Anda. Sejak saya memposting ini, saya telah menemukan situasi lain di mana indeks adalah masalahnya, bahkan jika kolom yang diperbarui sudah memiliki nilai dan tidak memiliki indeks (!). Tampaknya Postgres memiliki masalah dengan bagaimana mengelola indeks pada kolom lain. Tidak ada alasan bagi indeks lain ini untuk menggelembungkan waktu permintaan pembaruan ketika satu-satunya perubahan pada tabel adalah memperbarui kolom yang tidak diindeks dan Anda tidak menambah ruang yang dialokasikan untuk baris mana pun dari kolom itu.
Aren Cambre
1
Terima kasih! Semoga ini bisa membantu orang lain. Itu akan menyelamatkan saya berjam-jam sakit kepala untuk sesuatu yang tampaknya sangat sederhana.
JC Avena
5
@ArenCambre - ada alasannya: PostgreSQL menyalin seluruh baris ke lokasi yang berbeda dan menandai versi lama sebagai dihapus. Ini adalah bagaimana PostgreSQL mengimplementasikan Multi-Version Concurrency Control (MVCC).
Piotr Findeisen
Pertanyaan saya adalah ... mengapa pelakunya? Lihat juga stackoverflow.com/a/35660593/32453
rogerdpack
15

Masalah terbesar Anda adalah melakukan banyak pekerjaan tulis-berat dan berat di hard drive laptop. Itu tidak akan pernah cepat, apa pun yang Anda lakukan, terutama jika drive jenis 5400RPM yang lebih lambat dikirim dalam banyak laptop.

TrueCrypt memperlambat segalanya lebih dari "sedikit" untuk menulis. Membaca akan cukup cepat, tetapi menulis membuat RAID 5 terlihat cepat. Menjalankan DB pada volume TrueCrypt akan menjadi siksaan untuk menulis, terutama menulis acak.

Dalam hal ini, saya pikir Anda akan membuang-buang waktu untuk mencoba mengoptimalkan kueri. Anda menulis ulang sebagian besar baris, dan itu akan lambat dengan situasi penulisan Anda yang mengerikan. Apa yang saya sarankan adalah:

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

Saya menduga itu akan lebih cepat daripada hanya menjatuhkan dan menciptakan kembali kendala saja, karena UPDATE akan memiliki pola penulisan yang cukup acak yang akan membunuh penyimpanan Anda. Dua sisipan massal, satu ke dalam tabel tanpa log dan satu ke dalam tabel log-WAL tanpa kendala, mungkin akan lebih cepat.

Jika Anda memiliki cadangan yang benar-benar mutakhir dan tidak keberatan harus memulihkan database Anda dari cadangan, Anda juga dapat memulai kembali PostgreSQL dengan fsync=offparameter dan untuk full_page_writes=off sementara waktu untuk operasi massal ini. Setiap masalah tak terduga seperti kehilangan daya atau OS crash akan membuat basis data Anda tidak dapat dipulihkan fsync=off.

POSTGreSQL setara dengan "tidak ada penebangan" adalah dengan menggunakan tabel yang tidak di-log. Tabel-tabel yang tidak di-log-kan ini terpotong jika DB dimatikan dengan tidak bersih saat mereka kotor. Menggunakan tabel yang tidak terdaftar setidaknya akan mengurangi separuh beban penulisan Anda dan mengurangi jumlah pencarian, sehingga mereka bisa menjadi BANYAK lebih cepat.

Seperti di Oracle, ini bisa menjadi ide yang baik untuk menjatuhkan indeks kemudian membuatnya kembali setelah pembaruan batch besar. Perencana PostgreSQL tidak dapat menemukan bahwa pembaruan besar sedang terjadi, jeda pembaruan indeks, kemudian buat kembali indeks di akhir; bahkan jika itu bisa, akan sangat sulit baginya untuk mengetahui pada titik mana hal ini layak dilakukan, terutama di muka.

Craig Ringer
sumber
Jawaban ini tepat pada jumlah besar penulisan dan perf enkripsi yang mengerikan ditambah drive laptop lambat. Saya juga akan mencatat bahwa kehadiran 8 indeks menghasilkan banyak penulisan tambahan dan mengalahkan penerapan pembaruan baris HOT di-blok, sehingga menjatuhkan indeks dan menggunakan fillfactor yang lebih rendah di atas meja dapat mencegah satu ton migrasi baris
dbenhur
1
Panggilan bagus untuk meningkatkan peluang HOTs dengan fillfactor - meskipun dengan TrueCrypt memaksa siklus baca-tulis ulang blok dalam blok besar saya tidak yakin itu akan banyak membantu; migrasi baris bahkan mungkin lebih cepat karena menumbuhkan tabel setidaknya melakukan blok linear-ish dari penulisan.
Craig Ringer
2,5 tahun kemudian saya melakukan sesuatu yang serupa tetapi di atas meja yang lebih besar. Hanya untuk memastikan, apakah ide yang bagus untuk menghapus semua indeks, bahkan jika kolom tunggal yang saya perbarui tidak diindeks?
Aren Cambre
1
@ArenCambre Dalam hal itu ... yah, ini rumit. Jika sebagian besar pembaruan Anda memenuhi syarat untuk HOTitu maka lebih baik membiarkan indeks di tempat. Jika tidak, maka Anda mungkin ingin melepaskan dan membuat kembali. Kolom tidak diindeks, tetapi untuk dapat melakukan pembaruan HOT di sana juga perlu ada ruang kosong di halaman yang sama, jadi itu tergantung sedikit pada seberapa banyak ruang kosong yang ada di tabel. Jika kebanyakan menulis, saya akan mengatakan drop all indexes. Jika banyak diperbarui, mungkin ada lubang dan Anda mungkin OK. Alat-alat suka pageinspectdan pg_freespacemapdapat membantu menentukan ini.
Craig Ringer
Terima kasih. Dalam hal ini, itu adalah kolom boolean yang sudah memiliki entri ke setiap baris. Saya mengubah entri pada beberapa baris. Saya baru saja mengkonfirmasi: pembaruan hanya memakan waktu 2 jam setelah menjatuhkan semua indeks. Sebelumnya, saya harus menghentikan pembaruan setelah 18 jam karena itu terlalu lama. Ini terlepas dari kenyataan bahwa kolom yang sedang diperbarui pasti tidak diindeks.
Aren Cambre
2

Seseorang akan memberikan jawaban yang lebih baik untuk Postgres, tetapi di sini ada beberapa pengamatan dari perspektif Oracle yang mungkin berlaku (dan komentarnya terlalu panjang untuk kolom komentar).

Kekhawatiran pertama saya adalah mencoba memperbarui 2 juta baris dalam satu transaksi. Di Oracle, Anda akan menulis gambar sebelum setiap blok yang diperbarui sehingga sesi lain masih memiliki pembacaan yang konsisten tanpa membaca blok yang dimodifikasi dan Anda memiliki kemampuan untuk mengembalikan. Itu adalah kemunduran yang lama sedang dibangun. Anda biasanya lebih baik melakukan transaksi dalam potongan kecil. Katakan 1.000 catatan sekaligus.

Jika Anda memiliki indeks di atas meja, dan tabel tersebut akan dianggap tidak beroperasi selama pemeliharaan, Anda seringkali lebih baik menghapus indeks sebelum operasi besar dan kemudian membuatnya kembali setelahnya. Lebih murah kemudian terus berusaha mempertahankan indeks dengan setiap catatan yang diperbarui.

Oracle mengizinkan "no logging" petunjuk pada pernyataan untuk menghentikan penjurnalan. Ini mempercepat pernyataan banyak, tetapi meninggalkan db Anda dalam situasi "tidak dapat dipulihkan". Jadi, Anda ingin mencadangkan sebelumnya, dan mencadangkan lagi segera sesudahnya. Saya tidak tahu apakah Postgres memiliki opsi serupa.

Glenn
sumber
PostgreSQL tidak memiliki masalah dengan rollback yang panjang, tidak ada. ROLBACK sangat cepat di PostgreSQL, tidak peduli seberapa besar transaksi Anda. Oracle! = PostgreSQL
Frank Heikens
@FrankHeikens Terima kasih, itu menarik. Saya harus membaca tentang cara kerja jurnal di Postgres. Untuk membuat seluruh konsep transaksi bekerja, entah bagaimana dua versi data yang berbeda perlu dipertahankan selama transaksi, gambar sebelum dan gambar setelah dan itulah mekanisme yang saya maksudkan. Dengan satu atau lain cara, saya kira ada ambang di mana sumber daya untuk mempertahankan transaksi akan terlalu mahal.
Glenn
2
@Glenn postgres menyimpan versi baris di tabel itu sendiri - lihat di sini untuk penjelasan. Kompromi adalah bahwa Anda mendapatkan tuple 'mati' berkeliaran, yang dibersihkan secara serempak dengan apa yang disebut 'vakum' di postgres (Oracle tidak perlu vakum karena tidak pernah memiliki baris 'mati' di tabel itu sendiri)
Jack mengatakan coba topanswers.xyz
Terima kasih, dan agak terlambat: selamat datang di situs :-)
Jack mengatakan coba topanswers.xyz
@Glenn Dokumen kanonik untuk kontrol konkurensi versi baris PostgreSQL adalah postgresql.org/docs/current/static/mvcc-intro.html dan layak dibaca. Lihat juga wiki.postgresql.org/wiki/MVCC . Perhatikan bahwa MVCC dengan baris mati dan VACUUMhanya setengah dari jawabannya; PostgreSQL juga menggunakan apa yang disebut "tulis di muka log" (secara efektif sebuah jurnal) untuk memberikan komitmen atom dan melindungi terhadap penulisan parsial, dll. Lihat postgresql.org/docs/current/static/wal-intro.html
Craig Ringer