Bagaimana cara melakukan lebih dari 50.000 penyisipan per detik dengan tokudb?

Saat ini saya sedang menguji TokuDB dan saya sangat terkesan. Saat ini penyisipan per detik telah mencapai puncaknya hingga lebih dari 50.000 per detik dengan dua pekerjaan simultan berjalan. Tingkat penyisipan rata-rata adalah antara 38.000 dan 42.000 penyisipan per detik.

Saya ingin melangkah lebih tinggi lagi, 100.000 penyisipan per detik, karena saya perlu memasukkan 1,2 miliar baris terhitung untuk saat ini dan sekitar 6 miliar baris lainnya dalam waktu dekat. Saya ingin saran tentang cara mencapai ini :-)

Pengaturan saya saat ini:

  1. Perangkat keras: VPS dengan RAM 4 GB, SSD 150 GB, 2 inti: CPU Intel Westmere E56xx/L56xx/X56xx (Nehalem-C) 2,59GHz
  2. Opsi pemasangan disk: default, noatime
  3. Sistem Operasi: CentOS 6.8 64bit
  4. Basis Data: Server Percona 5.7.14-8

Pengaturan my.cnf:

# TokuDB #
tokudb_cache_size = 2G
tokudb_commit_sync = 0
tokudb_fsync_log_period = 1000

Tata letak tabel TokuDB:

CREATE TABLE `t1` (
  `id` int(15) NOT NULL AUTO_INCREMENT,
  `m_id` int(11) NOT NULL,
  `c1` decimal(6,2) DEFAULT NULL,
  `c2` decimal(6,2) DEFAULT NULL,
  `c3` decimal(6,2) DEFAULT NULL,
  `c4` decimal(6,2) DEFAULT NULL,
  `c5` decimal(6,2) DEFAULT NULL,
  `c6` decimal(6,2) DEFAULT NULL,
  `c7` decimal(6,2) DEFAULT NULL,
  `factor` decimal(4,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1

CREATE TABLE `t2` (
  `id` int(15) NOT NULL AUTO_INCREMENT,
  `v_id` int(15) NOT NULL,
  `pid` int(11) DEFAULT NULL,
  `amount` decimal(6,2) DEFAULT NULL,
  `unit` int(1) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1

Saya menyadari fakta bahwa saya tidak menggunakan indeks apa pun selain indeks kunci utama. Hal ini disebabkan oleh dampak negatif waktu terhadap penyisipan kunci. Kunci cluster untuk setiap tabel akan dibuat di akhir pekerjaan penyisipan.

Opsi baris perintah MySQL tambahan:

SET unique_checks=OFF;

Entah bagaimana saya tidak bisa mendapatkan ini di my.cnf.. Jika seseorang tahu caranya maka ini akan sangat dihargai (saat ini Unique_checks = off akan memblokir MySQL agar tidak dimulai karena variabel yang tidak dikenal di my.cnf) .

Pernyataan SQL dikelompokkan dalam kumpulan 15.000. Skrip PHP menghasilkan pernyataan SQL dan mengirimkan kueri melalui mysqli_multiquery ke server MySQL:

<?PHP        
    foreach (generateCombinations($Arr) as $c) {

            $QueryBatch[] = "insert into t1 values (NULL"
                            . ", " . $record->id
                            . ", " . rand(1, 35)
                            . ", " . rand(1, 140)
                            . ", " . rand(1, 20)
                            . ", NULL"
                            . ", " . rand(1, 14)
                            . ", " . rand(1, 300)
                            . ", " . rand(1, 4)
                            . ", NULL );";
            $QueryBatch[] = "SET @t1id = LAST_INSERT_ID();";

            $cntBatch++;

            $pquery = array();
            foreach ( $c as $key => $pid){

                    if ( is_null($pid) )
                            continue;

                    $pquery[] = "(NULL, @t1id, " . $pid . ", " . rand(1, 800) . ", 0)";

                    $cntBatch++;
            }

            $QueryBatch[] = "insert into t2 values " . implode(',', $pquery) . ";";

            if ($cntBatch > 15000) {

                    $query = implode($QueryBatch);

                    if ( $mysqli->multi_query($query) ){
                            while ($mysqli->next_result()) {;}
                    } else {
                            printf("Errormessage: %s\n", $mysqli->error);
                            echo $query . "\n";
                    }

                    $cntBatch = 0;
                    unset($QueryBatch);
            }

    }
?>

Contoh pernyataan penyisipan SQL:

insert into t1 values (NULL, 1 , 30, 100, 15, NULL, 10, 250, 2, NULL );
SET @t1id = LAST_INSERT_ID();
insert into t2 values (NULL, @t1id, 1, 750, 0),(NULL, @t1id, 1, 600, 0),(NULL, @t1id, 1, 500, 0),(NULL, @t1id, 1, 400, 0),(NULL, @t1id, 1, 300, 0),(NULL, @t1id, 1, 200, 0),(NULL, @t1id, 1, 100, 0);
insert into t1 values (NULL, 2 , 25, 95, 14, NULL, 11, 200, 3, NULL );
SET @t1id = LAST_INSERT_ID();
insert into t2 values (NULL, @t1id, 1, 600, 0),(NULL, @t1id, 1, 100, 0),(NULL, @t1id, 1, 300, 0),(NULL, @t1id, 1, 443, 0),(NULL, @t1id, 1, 521, 0),(NULL, @t1id, 1, 213, 0),(NULL, @t1id, 1, 433, 0);
[.. At least 14982 more..]

person Robbert    schedule 07.10.2016    source sumber


Jawaban (1)


Jika itu saya, saya akan mengurangi jumlah pernyataan yang dieksekusi, dan mengurangi jumlah komitmen. Saya berasumsi AUTO_COMMIT diaktifkan, mengingat kita tidak melihat pernyataan BEGIN TRANSACTION atau COMMIT apa pun.

Itu adalah pernyataan INSERT dan SET individual yang sangat banyak. Setidaknya penyisipan ke tabel anak menggunakan penyisipan beberapa baris, bukan pernyataan penyisipan terpisah untuk setiap baris.

Jika saya ingin ini cepat, saya akan melakukannya

  1. menghasilkan nilai id untuk tabel t1, dan memasukkannya ke dalam pernyataan INSERT
  2. hapus panggilan ke LAST_INSERT_ID()
  3. gunakan penyisipan multi-baris untuk t1 (daripada pernyataan INSERT terpisah untuk setiap baris)
  4. gunakan BEGIN TRANSACTION dan COMMIT
  5. jalankan proses tunggal untuk melakukan penyisipan ke t1 (serialisasi) untuk menghindari potensi pertikaian untuk kunci

Jika untuk InnoDB, saya juga akan melakukan SET FOREIGN_KEY_CHECKS=0.

Sudah ada banyak sekali panggilan ke fungsi rand dalam kode; jadi menambah bilangan bulat id untuk t1 tidak akan mengubah arah. Saat kita mulai, kita memerlukan kueri untuk mendapatkan nilai AUTO_INCREMENT saat ini, atau mendapatkan MAX(id), mana saja...

Pada dasarnya, saya akan mengurangi jumlah pernyataan yang dieksekusi, dan menyelesaikan lebih banyak pekerjaan pada setiap pernyataan, dan lebih banyak pekerjaan sebelum setiap COMMIT.

Memasukkan sepuluh (10) t1 baris per pernyataan akan secara signifikan mengurangi jumlah pernyataan yang perlu dieksekusi.

BEGIN TRANSACTION;
-- insert ten rows into t1
INSERT INTO t1 (id,m_id,c1,c2,c3,c4,c5,c6,c7,factor) VALUES
 (444055501, 1 , 30, 100, 15, NULL, 10, 250, 2, NULL )
,(444055502, 2 , 25, 95, 14, NULL, 11, 200, 3, NULL )
, ...
,(444055510, 10 , 7, 45, 12, NULL, 10, 300, 4, NULL )
;
-- batch together the t2 rows associated with the ten t1 rows we just inserted
INSERT INTO t2 VALUES
-- 444055501  
 (NULL, 444055501, 1, 750, 0)
,(NULL, 444055501, 1, 600, 0)
,(NULL, 444055501, 1, 500, 0)
,(NULL, 444055501, 1, 400, 0)
,(NULL, 444055501, 1, 300, 0)
,(NULL, 444055501, 1, 200, 0)
,(NULL, 444055501, 1, 100, 0)
-- 444055502  
,(NULL, 444055502, 1, 600, 0)
,(NULL, 444055502, 1, 100, 0)
,(NULL, 444055502, 1, 300, 0)
,(NULL, 444055502, 1, 443, 0)
,(NULL, 444055502, 1, 521, 0)
,(NULL, 444055502, 1, 213, 0)
,(NULL, 444055502, 1, 433, 0)
-- 444055503
, ...
;

-- another ten rows into t1
INSERT INTO t1 (id,m_id,c1,c2,c3,c4,c5,c6,c7,factor) VALUES
 (444055511, 11 , 27, 94, 15, NULL, 10, 250, 11, NULL )
,(444055512, 12 , 24, 93, 14, NULL, 11, 200, 12, NULL )
, ...
,(444055520, 10 , 7, 45, 12, NULL, 10, 300, 4, NULL )
;
INSERT INTO t2 VALUES
 (NULL, 444055511, 1, 820, 0)
,(NULL, 444055511, 1, 480, 0)
, ...
;

-- repeat INSERTs into t1 and t2, and after 1000 loops
-- i.e. 10,000 t1 rows, do a commit
COMMIT;
BEGIN TRANSACTION;
INSERT INTO t1 ...

MUAT INFILE DATA

Diskusi apa pun tentang kinerja sisipan tidak akan lengkap tanpa setidaknya menyebutkan LOAD DATA INFILE.

Untuk performa terbaik, itu tidak ada duanya. Namun karena kita tidak memiliki data dalam file, dan kita tidak memiliki nilai kunci (diperlukan untuk kunci asing di t2, dan kita mendapat semua panggilan ke Rand untuk menghasilkan data, LOAD DATA INFILE sepertinya tidak agar cocok.

person spencer7593    schedule 07.10.2016
comment
Spencer7593 yang terhormat, terima kasih atas jawaban terperinci Anda! Saya mengerti apa yang Anda katakan dan setuju tetapi masalahnya adalah; Saya perlu mengetahui ID untuk t1 agar catatan terkait di t2 dapat dikaitkan. Bidang ID di t1 adalah kenaikan otomatis. Anda akan menyarankan untuk menghapus kenaikan otomatis dan membuat ID di dalam skrip? Hal ini dapat dilakukan karena pada dasarnya hanya satu pekerjaan yang akan membuat catatan. - person Robbert; 08.10.2016
comment
Nilai dapat diberikan untuk kolom AUTO_INCREMENT, tanpa menghapus atau menonaktifkan AUTO_INCREMENT. Kami mendapatkan perilaku AUTO_INCREMENT dengan memberikan nilai NULL. Saya menyarankan bahwa alih-alih memberikan nilai NULL, kita menghasilkan nilai id, dan memberikan nilai tersebut di sisipan ke t1 dan ke t2. Itulah yang akan saya lakukan untuk mempercepatnya... mengurangi jumlah pernyataan SQL dan jumlah COMMIT. - person spencer7593; 08.10.2016
comment
Dengan InnoDB, kami mengonfigurasi perilaku auto_increment untuk mendapatkan nilai auto_increment berturut-turut. Jadi penyisipan multi-baris, kita tahu baris pertama adalah LAST_INSERT_ID()+0, baris kedua yang disisipkan adalah LAST_INSERT_ID()+1, hingga LAST_INSERT_ID()+ROW_COUNT(). Jika saya harus menggunakan nilai AUTO_INCREMENT, saya akan memasukkan sisipan ke t1 (sisipan baris mutli), SELECT LAST_INSERT_ID() INTO @lid dan kemudian sisipan saya ke t2 akan menggunakan @lid+0 untuk baris yang terkait dengan baris t1 pertama yang disisipkan, @lid+1 untuk baris yang terkait dengan t1 kedua baris disisipkan. (Perhatian saya adalah saya tidak yakin perilaku AUTO_INCREMENT dengan tokudb sama dengan InnoDB.) - person spencer7593; 08.10.2016
comment
Saya telah menindaklanjuti saran Anda dan saat ini kecepatan penyisipan antara 120.000 dan 145.000 catatan per detik. Namun, karena saya baru saja mengupgrade VPS saya menjadi 4 core dan ram 8GB, saya juga meningkatkan tokudb_cache_size menjadi 5GB. Saya ingin tahu apakah kita bisa mencapai lebih tinggi lagi (200.000+/dtk) :-) - person Robbert; 08.10.2016
comment
Beberapa spesifikasi HW: Pemakaian CPU total 25%, IOPS disk 40 dan dapat ditingkatkan hingga 1000 IOPS. - person Robbert; 08.10.2016
comment
Jika tokudb memungkinkan Anda menonaktifkan pemeriksaan kunci asing (seperti yang dilakukan InnoDB), Anda mungkin dapat menjalankan dua koneksi database yang berbeda untuk melakukan penyisipan secara bersamaan... satu koneksi meledak di tabel t1, yang lainnya peledakan secara bersamaan ke meja t2. Uji dengan logging biner dinonaktifkan (sql_log_bin). Saya sangat curiga bahwa salah satu hambatan besar Anda adalah pernyataan individual, semua biaya tambahan untuk melakukan penguraian, melakukan pemeriksaan sintaksis, melakukan pemeriksaan semantik, menyiapkan rencana eksekusi, dan menjalankan rencana, mendapatkan kunci, menghasilkan rollback, menulis ke log... - person spencer7593; 08.10.2016