Bagaimana cara mempartisi tabel yang ada di postgres?

19

Saya ingin mempartisi tabel dengan 1M + baris berdasarkan rentang tanggal. Bagaimana ini biasanya dilakukan tanpa memerlukan banyak downtime atau risiko kehilangan data? Berikut adalah strategi yang saya pertimbangkan, tetapi terbuka untuk saran:

  1. Tabel yang ada adalah master dan anak-anak mewarisi darinya. Seiring waktu memindahkan data dari master ke anak, tetapi akan ada periode waktu di mana beberapa data ada di tabel master dan beberapa di anak-anak.

  2. Buat tabel master dan anak baru. Buat salinan data di tabel yang ada di tabel anak (jadi data akan berada di dua tempat). Setelah tabel anak memiliki data terbaru, ubah semua sisipan ke depan untuk menunjuk ke tabel master baru dan menghapus tabel yang ada.

Evan Appleby
sumber
1
Di sini ide saya: jika tabel memiliki kolom datetime -> buat master baru + anak baru -> masukkan data baru ke BARU + LAMA (mis: datetime = 2015-07-06 00:00:00) -> salin dari OLD ke basis BARU pada kolom waktu (di mana: datetime <2015-07-06 00:00:00) -> rename table -> ubah masukkan ke BARU lain -> buat "pemicu partisi" untuk menyisipkan / memperbarui master (masukkan / perbarui data baru - > pindah ke childs, jadi data baru akan dimasukkan ke childs) -> update master, trigger akan memindahkan data ke childs.
Luan Huynh
@Innnh, jadi Anda menyarankan opsi kedua, tetapi setelah data disalin, hapus tabel lama dan ganti nama tabel baru tersebut dengan nama yang sama dengan tabel lama. Apakah itu benar?
Evan Appleby
ganti nama tabel baru ke tabel lama, tetapi Anda harus menyimpan tabel lama hingga tabel partisi aliran baru benar-benar ok.
Luan Huynh
2
Untuk beberapa juta baris saja saya tidak berpikir partisi sebenarnya diperlukan. Menurut Anda mengapa Anda membutuhkannya? Masalah apa yang Anda sedang coba pecahkan?
a_horse_with_no_name
1
@ EvanAppleby DELETE FROM ONLY master_tableadalah solusinya.
dezso

Jawaban:

21

Karena # 1 memerlukan menyalin data dari master ke anak ketika sedang dalam lingkungan produksi aktif, saya pribadi pergi dengan # 2 (membuat master baru). Ini mencegah gangguan ke tabel asli saat sedang aktif digunakan dan jika ada masalah, saya dapat dengan mudah menghapus master baru tanpa masalah dan terus menggunakan tabel asli. Berikut langkah-langkah untuk melakukannya:

  1. Buat tabel master baru.

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. Buat anak-anak yang mewarisi dari tuan.

    CREATE TABLE child_2014 (
        CONSTRAINT pk_2014 PRIMARY KEY (id),
        CONSTRAINT ck_2014 CHECK ( dt_created < DATE '2015-01-01' )
    ) INHERITS (new_master);
    CREATE INDEX idx_2014 ON child_2014 (dt_created);
    
    CREATE TABLE child_2015 (
        CONSTRAINT pk_2015 PRIMARY KEY (id),
        CONSTRAINT ck_2015 CHECK ( dt_created >= DATE '2015-01-01' AND dt_created < DATE '2016-01-01' )
    ) INHERITS (new_master);
    CREATE INDEX idx_2015 ON child_2015 (dt_created);
    
    ...
  3. Salin semua data historis ke tabel master baru

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
  4. Jeda untuk sementara waktu sisipan / pembaruan baru ke basis data produksi

  5. Salin data terbaru ke tabel master baru

    INSERT INTO child_2015 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created >= '01/01/2015'::date AND dt_created < '01/01/2016'::date;
  6. Ganti nama tabel sehingga new_master menjadi basis data produksi.

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  7. Tambahkan fungsi untuk pernyataan INSERT ke old_master sehingga data diteruskan ke partisi yang benar.

    CREATE OR REPLACE FUNCTION fn_insert() RETURNS TRIGGER AS $$
    BEGIN
        IF ( NEW.dt_created >= DATE '2015-01-01' AND
             NEW.dt_created < DATE '2016-01-01' ) THEN
            INSERT INTO child_2015 VALUES (NEW.*);
        ELSIF ( NEW.dt_created < DATE '2015-01-01' ) THEN
            INSERT INTO child_2014 VALUES (NEW.*);
        ELSE
            RAISE EXCEPTION 'Date out of range';
        END IF;
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
  8. Tambah pemicu sehingga fungsinya dipanggil pada INSERTS

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Tetapkan pengecualian kendala ke ON

    SET constraint_exclusion = on;
  10. Aktifkan kembali UPDAT dan INSER pada basis data produksi

  11. Atur pemicu atau cron agar partisi baru dibuat dan fungsinya diperbarui untuk menetapkan data baru ke partisi yang benar. Referensi artikel ini untuk contoh kode

  12. Hapus old_master_backup

Evan Appleby
sumber
1
Langgan yang bagus. Akan menarik jika itu benar-benar membuat pertanyaan Anda lebih cepat. 10 juta masih belum banyak baris yang akan saya pikirkan tentang partisi. Saya ingin tahu apakah kinerja Anda yang merendahkan itu mungkin disebabkan karena vacuumtidak mengejar atau dicegah karena sesi "menganggur dalam transaksi".
a_horse_with_no_name
@a_horse_with_no_name, sejauh ini belum membuat kueri secara signifikan lebih baik :( Saya menggunakan Heroku yang memiliki pengaturan vakum otomatis dan tampaknya terjadi setiap hari untuk meja besar ini. Akan melihat lebih dalam ke sana.
Evan Appleby
Bukankah seharusnya sisipan pada langkah 3 dan 5 untuk tabel new_master dan biarkan postgresql memilih tabel / partisi anak yang tepat?
pakman
@pakman fungsi untuk menetapkan anak yang tepat tidak ditambahkan sampai langkah 7
Evan Appleby
4

Ada alat baru bernama pg_pathman ( https://github.com/postgrespro/pg_pathman ) yang akan melakukan ini untuk Anda secara otomatis.

Jadi sesuatu seperti yang berikut ini akan melakukannya.

SELECT create_range_partitions('master', 'dt_created', 
   '2015-01-01'::date, '1 day'::interval);
kakoni
sumber