จะทำแทรกมากกว่า 50,000 ครั้งต่อวินาทีด้วย tokudb ได้อย่างไร

ขณะนี้ฉันกำลังทดสอบ TokuDB และรู้สึกประทับใจมาก ในขณะนี้ เม็ดมีดต่อวินาทีพุ่งสูงสุดที่เพียง 50,000 ต่อวินาที โดยทำงานสองงานพร้อมกัน อัตราการแทรกเฉลี่ยอยู่ระหว่าง 38,000 ถึง 42,000 เม็ดต่อวินาที

ฉันต้องการที่จะเพิ่มการแทรก 100,000 ต่อวินาทีให้สูงขึ้น เนื่องจากฉันจะต้องแทรกแถวที่คำนวณแล้ว 1.2 พันล้านแถวในตอนนี้และอีกประมาณ 6 พันล้านแถวในอนาคตอันใกล้นี้ ฉันต้องการคำแนะนำเกี่ยวกับวิธีการบรรลุเป้าหมายนี้ :-)

การตั้งค่าปัจจุบันของฉัน:

  1. ฮาร์ดแวร์: VPS พร้อม RAM 4GB, 150GB SSD, 2 คอร์: Intel Westmere E56xx/L56xx/X56xx (Nehalem-C) CPU 2.59GHz
  2. ตัวเลือกการเมานต์ดิสก์: ค่าเริ่มต้น, noatime
  3. ระบบปฏิบัติการ: CentOS 6.8 64bit
  4. ฐานข้อมูล: เซิร์ฟเวอร์ Percona 5.7.14-8

การตั้งค่า My.cnf:

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

เค้าโครงตาราง 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

ฉันตระหนักถึงความจริงที่ว่าฉันไม่ได้ใช้ดัชนีใด ๆ นอกเหนือจากดัชนีคีย์หลัก นี่เป็นเพราะผลกระทบด้านลบที่คีย์จะมีต่อการใส่ คีย์คลัสเตอร์สำหรับแต่ละตารางจะถูกสร้างขึ้นเมื่อสิ้นสุดงานแทรก

ตัวเลือกบรรทัดคำสั่ง MySQL เพิ่มเติม:

SET unique_checks=OFF;

อย่างไรก็ตาม ฉันไม่สามารถรับสิ่งนี้ได้ใน my.cnf.. หากมีใครรู้ว่าทำอย่างไร สิ่งนี้จะได้รับการชื่นชมอย่างมาก (ในปัจจุบัน Unique_checks = off จะบล็อก MySQL ไม่ให้เริ่มทำงานเนื่องจากตัวแปรที่ไม่รู้จักใน my.cnf) .

คำสั่ง SQL ถูกจัดกลุ่มเป็นกลุ่มละ 15.000 สคริปต์ PHP สร้างคำสั่ง SQL และส่งแบบสอบถามผ่าน mysqli_multiquery ไปยังเซิร์ฟเวอร์ 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);
            }

    }
?>

ตัวอย่างของคำสั่งแทรก 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 แหล่งที่มา


คำตอบ (1)


ถ้าเป็นฉัน ฉันจะลดจำนวนคำสั่งที่ถูกดำเนินการ และลดจำนวนการคอมมิตลง ฉันสมมติว่า AUTO_COMMIT เปิดใช้งานอยู่ เนื่องจากเราไม่เห็นคำสั่ง BEGIN TRANSACTION หรือ COMMIT ใด ๆ

นั่นเป็นปริมาณที่เพิ่มขึ้นทั้งหมดของคำสั่ง INSERT และ SET แต่ละรายการ อย่างน้อยส่วนแทรกในตารางลูกก็ใช้การแทรกหลายแถว ไม่ใช่คำสั่งการแทรกแยกกันสำหรับแต่ละแถว

ถ้าฉันต้องการให้มันรวดเร็วฉันก็จะทำ

  1. สร้างค่า id สำหรับตาราง t1 และรวมค่าเหล่านั้นไว้ในคำสั่ง INSERT
  2. ยกเลิกการโทรไปที่ LAST_INSERT_ID()
  3. ใช้การแทรกหลายแถวสำหรับ t1 (แทนที่จะแยกคำสั่ง INSERT สำหรับแต่ละแถว)
  4. ใช้ BEGIN TRANSACTION และ COMMIT
  5. รันกระบวนการ single เพื่อแทรกลงใน t1 (ทำให้เป็นอนุกรม) เพื่อหลีกเลี่ยงการโต้แย้งที่อาจเกิดขึ้นสำหรับการล็อค

ถ้าเป็น InnoDB ฉันก็จะทำ SET FOREIGN_KEY_CHECKS=0 ด้วย

มีการเรียกใช้ฟังก์ชัน rand ในโค้ดเป็นจำนวนมากอยู่แล้ว ดังนั้นการเพิ่มจำนวนเต็ม id สำหรับ t1 จะไม่ทำให้เข็มขยับ เมื่อเราเริ่มต้น เราจำเป็นต้องมีแบบสอบถามเพื่อรับค่า AUTO_INCREMENT ปัจจุบัน หรือรับ MAX(id) แล้วแต่จำนวนใด...

โดยพื้นฐานแล้ว ฉันจะลดจำนวนคำสั่งที่กำลังดำเนินการ และทำงานให้เสร็จมากขึ้นในแต่ละคำสั่ง และทำงานให้น้อยลงก่อนแต่ละ COMMIT

การแทรกสิบ (10) t1 แถวต่อคำสั่งจะอย่างมากลดจำนวนคำสั่งที่ต้องดำเนินการ

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 ...

โหลดข้อมูลเข้าแฟ้ม

การอภิปรายเกี่ยวกับประสิทธิภาพของส่วนแทรกจะไม่สมบูรณ์หากไม่มีการกล่าวถึง LOAD DATA INFILE เป็นอย่างน้อย

เพื่อประสิทธิภาพที่ดีที่สุดที่ไม่สามารถเอาชนะได้ แต่เนื่องจากเราไม่มีข้อมูลในไฟล์ และเราไม่มีค่าคีย์ (จำเป็นสำหรับคีย์ต่างประเทศใน t2 และเราได้รับสายทั้งหมดให้ rand เพื่อสร้างข้อมูล LOAD DATA INFILE ดูเหมือนจะไม่มี เพื่อให้มีความพอดี

person spencer7593    schedule 07.10.2016
comment
เรียน Spencer7593 ขอบคุณสำหรับคำตอบโดยละเอียด! ฉันเข้าใจสิ่งที่คุณพูดและเห็นด้วย แต่ประเด็นคือ; ฉันจำเป็นต้องทราบ ID สำหรับ t1 สำหรับบันทึกที่เกี่ยวข้องใน t2 ที่เกี่ยวข้อง ฟิลด์ ID ใน t1 เป็นการเพิ่มขึ้นอัตโนมัติ คุณอยากจะแนะนำให้ลบการเพิ่มอัตโนมัติและสร้าง ID ภายในสคริปต์หรือไม่ ซึ่งสามารถทำได้โดยพื้นฐานแล้วมีเพียงงานเดียวเท่านั้นที่จะสร้างเรกคอร์ด - person Robbert; 08.10.2016
comment
สามารถระบุค่าสำหรับคอลัมน์ AUTO_INCREMENT โดยไม่ต้องลบหรือปิดใช้งาน AUTO_INCREMENT เราได้รับพฤติกรรม AUTO_INCREMENT โดยระบุค่า NULL ฉันแนะนำว่าแทนที่จะระบุค่า NULL เราสร้างค่า id และระบุค่านั้นทั้งในส่วนแทรกให้กับทั้ง t1 และ t2 นั่นคือสิ่งที่ฉันจะทําเพื่อเพิ่มความเร็ว... ลดจำนวนคำสั่ง SQL และจำนวน COMMIT - person spencer7593; 08.10.2016
comment
ด้วย InnoDB เรากำหนดค่าพฤติกรรมการเพิ่มขึ้นอัตโนมัติเพื่อรับค่าการเพิ่มขึ้น ต่อเนื่องกัน ดังนั้นการแทรกหลายแถว เรารู้ว่าแถวแรกคือ LAST_INSERT_ID()+0 แถวที่สองที่แทรกคือ LAST_INSERT_ID()+1 จนถึง LAST_INSERT_ID()+ROW_COUNT() หากฉันต้องใช้ค่า AUTO_INCREMENT ฉันจะแบทช์ส่วนแทรกเป็น t1 (การแทรกหลายแถว), SELECT LAST_INSERT_ID() INTO @lid จากนั้นส่วนแทรกของฉันไปที่ t2 จะใช้ @lid+0 สำหรับแถวที่เกี่ยวข้องกับ t1 แถวแรกที่แทรก, @lid+1 สำหรับแถวที่เกี่ยวข้องกับ t1 ที่สอง แทรกแถวแล้ว (ข้อควรระวังของฉันคือฉันไม่แน่ใจว่าพฤติกรรม AUTO_INCREMENT กับ tokudb นั้นเหมือนกับ InnoDB) - person spencer7593; 08.10.2016
comment
ฉันได้ปฏิบัติตามคำแนะนำของคุณแล้ว และขณะนี้ความเร็วในการแทรกอยู่ระหว่าง 120,000 ถึง 145,000 บันทึกต่อวินาที อย่างไรก็ตาม เนื่องจากฉันเพิ่งอัปเกรด VPS เป็น 4 คอร์และ RAM ขนาด 8GB ฉันจึงเพิ่ม tokudb_cache_size เป็น 5GB ด้วย ฉันอยากรู้ว่าเราจะไปได้ไกลกว่านี้อีกไหม (200.000+/s) :-) - person Robbert; 08.10.2016
comment
ข้อมูลจำเพาะ HW บางประการ: การใช้งาน CPU อยู่ที่ทั้งหมด 25%, IOPS ของดิสก์อยู่ที่ 40 และสามารถเพิ่มได้ถึง 1,000 IOPS - person Robbert; 08.10.2016
comment
หาก tokudb อนุญาตให้คุณปิดการใช้งานการตรวจสอบคีย์ต่างประเทศ (เช่นเดียวกับที่ InnoDB ทำ) คุณอาจสามารถเรียกใช้การเชื่อมต่อฐานข้อมูลที่แตกต่างกัน สอง เพื่อทำการแทรกพร้อมกัน... การเชื่อมต่อหนึ่งระเบิดที่ตาราง t1 และอีกการเชื่อมต่อหนึ่งระเบิดที่ตาราง t1 ระเบิดพร้อมกันไปที่โต๊ะ t2 ทดสอบโดยปิดใช้งานการบันทึกไบนารี (sql_log_bin) ฉันสงสัยอย่างยิ่งว่าหนึ่งในปัญหาคอขวดใหญ่ของคุณคือคำสั่งแต่ละคำสั่ง ค่าใช้จ่ายทั้งหมดในการทำการแยกวิเคราะห์ ทำการตรวจสอบไวยากรณ์ ทำการตรวจสอบความหมาย เตรียมแผนปฏิบัติการ และดำเนินการตามแผน รับการล็อค สร้างการย้อนกลับ เขียนถึง บันทึก... - person spencer7593; 08.10.2016