การจำลองแบบระดับสูงใน Tarantool DBMS

สวัสดี ฉันกำลังสร้างแอปพลิเคชันสำหรับ DBMS ทารันทูล เป็นแพลตฟอร์มที่พัฒนาโดย Mail.ru Group ซึ่งรวม DBMS ประสิทธิภาพสูงและแอปพลิเคชันเซิร์ฟเวอร์ในภาษา Lua โซลูชันความเร็วสูงที่ใช้ Tarantool ทำได้สำเร็จ โดยเฉพาะอย่างยิ่ง เนื่องจากการรองรับโหมดในหน่วยความจำของ DBMS และความสามารถในการดำเนินการตรรกะทางธุรกิจของแอปพลิเคชันในพื้นที่ที่อยู่เดียวพร้อมข้อมูล ในเวลาเดียวกัน รับประกันความคงอยู่ของข้อมูลโดยใช้ธุรกรรม ACID (บันทึก WAL จะถูกเก็บรักษาไว้บนดิสก์) Tarantool มีการสนับสนุนในตัวสำหรับการจำลองและการแบ่งส่วน เริ่มตั้งแต่เวอร์ชัน 2.1 รองรับการสืบค้นในภาษา SQL Tarantool เป็นโอเพ่นซอร์สและได้รับอนุญาตภายใต้ใบอนุญาต BSD แบบง่าย นอกจากนี้ยังมีเวอร์ชัน Enterprise เชิงพาณิชย์ด้วย

การจำลองแบบระดับสูงใน Tarantool DBMS
รู้สึกถึงพลัง! (…หรือว่าเพลิดเพลินไปกับการแสดง)

ทั้งหมดที่กล่าวมาทำให้ Tarantool เป็นแพลตฟอร์มที่น่าสนใจสำหรับการสร้างแอปพลิเคชันที่มีโหลดสูงซึ่งทำงานกับฐานข้อมูล ในแอปพลิเคชันดังกล่าว มักจำเป็นต้องมีการจำลองข้อมูล

ตามที่กล่าวไว้ข้างต้น Tarantool มีการจำลองข้อมูลในตัว หลักการดำเนินการคือการดำเนินการตามลำดับในการจำลองธุรกรรมทั้งหมดที่มีอยู่ในบันทึกหลัก (WAL) โดยปกติแล้วการจำลองแบบดังกล่าว (เราจะเรียกมันว่าต่อไป ระดับต่ำ) ใช้เพื่อรับรองความทนทานต่อข้อบกพร่องของแอปพลิเคชัน และ/หรือเพื่อกระจายโหลดการอ่านระหว่างโหนดคลัสเตอร์

การจำลองแบบระดับสูงใน Tarantool DBMS
ข้าว. 1. การจำลองแบบภายในคลัสเตอร์

ตัวอย่างของสถานการณ์ทางเลือกคือการถ่ายโอนข้อมูลที่สร้างในฐานข้อมูลหนึ่งไปยังฐานข้อมูลอื่นเพื่อการประมวลผล/ตรวจสอบ ในกรณีหลัง อาจใช้วิธีแก้ปัญหาที่สะดวกกว่า ระดับสูง การจำลอง - การจำลองข้อมูลในระดับตรรกะทางธุรกิจของแอปพลิเคชัน เหล่านั้น. เราไม่ได้ใช้โซลูชันสำเร็จรูปที่สร้างไว้ใน DBMS แต่ใช้การจำลองแบบด้วยตัวเราเองภายในแอปพลิเคชันที่เรากำลังพัฒนา วิธีนี้มีทั้งข้อดีและข้อเสีย เรามาดูข้อดีกันดีกว่า

1. การประหยัดการจราจร:

  • คุณไม่สามารถถ่ายโอนข้อมูลทั้งหมดได้ แต่เพียงบางส่วนเท่านั้น (ตัวอย่างเช่นคุณสามารถถ่ายโอนได้เพียงบางตารางบางคอลัมน์หรือบันทึกที่ตรงตามเกณฑ์ที่กำหนด)
  • ต่างจากการจำลองแบบระดับต่ำซึ่งดำเนินการอย่างต่อเนื่องในแบบอะซิงโครนัส (ใช้งานในเวอร์ชันปัจจุบันของ Tarantool - 1.10) หรือโหมดซิงโครนัส (ที่จะนำไปใช้ในเวอร์ชันถัดไปของ Tarantool) การจำลองแบบระดับสูงสามารถทำได้ในเซสชัน (เช่น แอปพลิเคชันจะซิงโครไนซ์ข้อมูลก่อน - ข้อมูลเซสชันการแลกเปลี่ยน จากนั้นจะมีการหยุดการจำลองชั่วคราว หลังจากนั้นเซสชันการแลกเปลี่ยนถัดไปจะเกิดขึ้น ฯลฯ );
  • หากบันทึกมีการเปลี่ยนแปลงหลายครั้ง คุณสามารถถ่ายโอนได้เฉพาะเวอร์ชันล่าสุดเท่านั้น (ไม่เหมือนกับการจำลองแบบระดับต่ำ ซึ่งการเปลี่ยนแปลงทั้งหมดที่ทำกับต้นแบบจะถูกเล่นตามลำดับบนแบบจำลอง)

2. ไม่มีปัญหาในการใช้งานการแลกเปลี่ยน HTTP ซึ่งช่วยให้คุณสามารถซิงโครไนซ์ฐานข้อมูลระยะไกลได้

การจำลองแบบระดับสูงใน Tarantool DBMS
ข้าว. 2. การจำลองแบบผ่าน HTTP

3. โครงสร้างฐานข้อมูลระหว่างข้อมูลที่ถูกถ่ายโอนไม่จำเป็นต้องเหมือนกัน (ยิ่งกว่านั้น ในกรณีทั่วไป คุณสามารถใช้ DBMS ภาษาการเขียนโปรแกรม แพลตฟอร์ม ฯลฯ ที่แตกต่างกันได้)

การจำลองแบบระดับสูงใน Tarantool DBMS
ข้าว. 3. การจำลองแบบในระบบที่ต่างกัน

ข้อเสียคือ โดยเฉลี่ยแล้วการเขียนโปรแกรมจะยาก/มีค่าใช้จ่ายมากกว่าการกำหนดค่า และแทนที่จะปรับแต่งฟังก์ชันการทำงานในตัว คุณจะต้องปรับใช้ฟังก์ชันของคุณเอง

หากในสถานการณ์ของคุณ ข้อดีข้างต้นมีความสำคัญ (หรือเป็นเงื่อนไขที่จำเป็น) ก็สมเหตุสมผลแล้วที่จะใช้การจำลองแบบระดับสูง มาดูหลายวิธีในการใช้การจำลองข้อมูลระดับสูงใน Tarantool DBMS

การลดปริมาณการรับส่งข้อมูล

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

จะลดปริมาณข้อมูลที่ถ่ายโอนระหว่างการจำลองแบบระดับสูงได้อย่างไร วิธีแก้ปัญหาที่ตรงไปตรงมาคือเลือกข้อมูลตามวันที่และเวลา เมื่อต้องการทำเช่นนี้ คุณสามารถใช้เขตข้อมูลวันที่-เวลาที่มีอยู่แล้วในตาราง (ถ้ามี) ตัวอย่างเช่น เอกสาร "คำสั่งซื้อ" อาจมีฟิลด์ "เวลาดำเนินการตามคำสั่งซื้อที่ต้องการ" - delivery_time. ปัญหาในการแก้ปัญหานี้คือค่าในช่องนี้ไม่จำเป็นต้องอยู่ในลำดับที่สอดคล้องกับการสร้างคำสั่งซื้อ ดังนั้นเราจึงจำค่าฟิลด์สูงสุดไม่ได้ delivery_timeส่งระหว่างเซสชันการแลกเปลี่ยนก่อนหน้า และในระหว่างเซสชันการแลกเปลี่ยนครั้งถัดไป ให้เลือกบันทึกทั้งหมดที่มีค่าฟิลด์ที่สูงกว่า delivery_time. อาจมีการเพิ่มบันทึกที่มีค่าฟิลด์ต่ำกว่าระหว่างเซสชันการแลกเปลี่ยน delivery_time. นอกจากนี้ คำสั่งดังกล่าวอาจมีการเปลี่ยนแปลงซึ่งแต่ก็ไม่ส่งผลกระทบต่อสนาม delivery_time. ในทั้งสองกรณี การเปลี่ยนแปลงจะไม่ถูกโอนจากต้นทางไปยังปลายทาง เพื่อแก้ไขปัญหาเหล่านี้ เราจะต้องถ่ายโอนข้อมูลแบบ "ทับซ้อนกัน" เหล่านั้น. ในแต่ละเซสชันการแลกเปลี่ยน เราจะถ่ายโอนข้อมูลทั้งหมดด้วยค่าฟิลด์ delivery_timeเกินจุดใดจุดหนึ่งในอดีต (เช่น N ชั่วโมงจากช่วงเวลาปัจจุบัน) อย่างไรก็ตาม เป็นที่แน่ชัดว่าสำหรับระบบขนาดใหญ่ วิธีการนี้มีความซ้ำซ้อนอย่างมาก และสามารถลดการประหยัดการรับส่งข้อมูลที่เราพยายามอย่างเต็มที่ได้ นอกจากนี้ ตารางที่กำลังถ่ายโอนอาจไม่มีฟิลด์ที่เกี่ยวข้องกับวันที่-เวลา

วิธีแก้ปัญหาอื่นที่ซับซ้อนกว่าในแง่ของการนำไปปฏิบัติคือการยอมรับการรับข้อมูล ในกรณีนี้ ในระหว่างการแลกเปลี่ยนแต่ละครั้ง ข้อมูลทั้งหมดจะถูกส่ง ซึ่งผู้รับไม่ได้รับการยืนยันการรับ หากต้องการใช้สิ่งนี้ คุณจะต้องเพิ่มคอลัมน์บูลีนลงในตารางต้นฉบับ (เช่น is_transferred). หากผู้รับรับทราบการรับบันทึก ฟิลด์ที่เกี่ยวข้องก็จะรับค่านั้นไป trueหลังจากนั้นรายการจะไม่เกี่ยวข้องกับการแลกเปลี่ยนอีกต่อไป ตัวเลือกการใช้งานนี้มีข้อเสียดังต่อไปนี้ ขั้นแรก สำหรับแต่ละบันทึกที่ถ่ายโอน จะต้องสร้างและส่งการตอบรับ โดยคร่าวๆ แล้ว สิ่งนี้อาจเทียบได้กับการเพิ่มปริมาณข้อมูลที่ถ่ายโอนเป็นสองเท่า และนำไปสู่การเพิ่มจำนวนการรับส่งข้อมูลไปกลับเป็นสองเท่า ประการที่สอง ไม่มีความเป็นไปได้ในการส่งบันทึกเดียวกันไปยังผู้รับหลายราย (ผู้รับรายแรกที่ได้รับจะยืนยันการรับสำหรับตัวมันเองและสำหรับผู้อื่นทั้งหมด)

วิธีการที่ไม่มีข้อเสียที่ระบุข้างต้นคือการเพิ่มคอลัมน์ลงในตารางที่ส่งเพื่อติดตามการเปลี่ยนแปลงในแถว คอลัมน์ดังกล่าวอาจเป็นประเภทวันที่-เวลา และต้องตั้งค่า/อัปเดตโดยแอปพลิเคชันให้เป็นเวลาปัจจุบันในแต่ละครั้งที่มีการเพิ่ม/เปลี่ยนแปลงบันทึก (แบบอะตอมมิกที่มีการเพิ่ม/เปลี่ยนแปลง) เป็นตัวอย่าง ลองเรียกคอลัมน์ update_time. ด้วยการบันทึกค่าฟิลด์สูงสุดของคอลัมน์นี้สำหรับบันทึกที่ถ่ายโอน เราสามารถเริ่มเซสชันการแลกเปลี่ยนครั้งถัดไปด้วยค่านี้ (เลือกบันทึกที่มีค่าฟิลด์ update_timeเกินกว่าค่าที่เก็บไว้ก่อนหน้านี้) ปัญหาของแนวทางหลังคือการเปลี่ยนแปลงข้อมูลสามารถเกิดขึ้นเป็นชุดได้ อันเป็นผลมาจากค่าฟิลด์ในคอลัมน์ update_time อาจจะไม่ซ้ำกัน ดังนั้น คอลัมน์นี้จึงไม่สามารถใช้กับเอาต์พุตข้อมูลแบบแบ่งส่วน (ทีละหน้า) ได้ ในการแสดงข้อมูลทีละหน้า คุณจะต้องประดิษฐ์กลไกเพิ่มเติมที่อาจมีประสิทธิภาพต่ำมาก (เช่น การดึงบันทึกทั้งหมดที่มีค่าจากฐานข้อมูล update_time สูงกว่าที่กำหนดและสร้างบันทึกจำนวนหนึ่ง โดยเริ่มจากออฟเซ็ตที่แน่นอนตั้งแต่จุดเริ่มต้นของตัวอย่าง)

คุณสามารถปรับปรุงประสิทธิภาพของการถ่ายโอนข้อมูลได้โดยการปรับปรุงแนวทางก่อนหน้านี้เล็กน้อย ในการทำเช่นนี้เราจะใช้ประเภทจำนวนเต็ม (จำนวนเต็มยาว) เป็นค่าฟิลด์คอลัมน์สำหรับการติดตามการเปลี่ยนแปลง มาตั้งชื่อคอลัมน์กันเถอะ row_ver. ค่าฟิลด์ของคอลัมน์นี้ต้องยังคงได้รับการตั้งค่า/อัปเดตทุกครั้งที่มีการสร้าง/แก้ไขเรกคอร์ด แต่ในกรณีนี้ เขตข้อมูลจะไม่ถูกกำหนดวันที่-เวลาปัจจุบัน แต่ค่าของตัวนับบางตัวเพิ่มขึ้นหนึ่งตัว ส่งผลให้คอลัมน์ row_ver จะมีค่าที่ไม่ซ้ำกันและสามารถนำมาใช้ไม่เพียงแต่เพื่อแสดงข้อมูล "เดลต้า" (ข้อมูลที่เพิ่ม/เปลี่ยนแปลงตั้งแต่สิ้นสุดเซสชันการแลกเปลี่ยนครั้งก่อน) แต่ยังใช้เพื่อแบ่งข้อมูลออกเป็นหน้าๆ อย่างง่ายดายและมีประสิทธิภาพ

วิธีการเสนอล่าสุดในการลดจำนวนข้อมูลที่ถ่ายโอนภายในกรอบการจำลองแบบระดับสูงดูเหมือนจะเหมาะสมและเป็นสากลที่สุดสำหรับฉัน มาดูรายละเอียดเพิ่มเติมกัน

การส่งผ่านข้อมูลโดยใช้ตัวนับเวอร์ชันแถว

การใช้งานเซิร์ฟเวอร์/ส่วนหลัก

ใน MS SQL Server มีประเภทคอลัมน์พิเศษเพื่อใช้แนวทางนี้ - rowversion. แต่ละฐานข้อมูลมีตัวนับที่เพิ่มขึ้นทีละครั้งในแต่ละครั้งที่มีการเพิ่ม/เปลี่ยนแปลงบันทึกในตารางที่มีคอลัมน์เหมือนกัน rowversion. ค่าของตัวนับนี้ถูกกำหนดให้กับฟิลด์ของคอลัมน์นี้ในบันทึกที่เพิ่ม/เปลี่ยนแปลงโดยอัตโนมัติ Tarantool DBMS ไม่มีกลไกในตัวที่คล้ายกัน อย่างไรก็ตาม การใช้งานด้วยตนเองใน Tarantool นั้นไม่ใช่เรื่องยาก มาดูวิธีการทำกัน

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

ก่อนที่จะดำเนินการฐานข้อมูลใด ๆ ใน Tarantool คุณต้องรันคำสั่งต่อไปนี้:

box.cfg{}

ด้วยเหตุนี้ Tarantool จะเริ่มเขียนสแน็ปช็อตฐานข้อมูลและบันทึกธุรกรรมลงในไดเร็กทอรีปัจจุบัน

มาสร้างลำดับกัน row_version:

box.schema.sequence.create('row_version',
    { if_not_exists = true })

ตัวเลือก if_not_exists อนุญาตให้เรียกใช้สคริปต์การสร้างได้หลายครั้ง: หากมีวัตถุอยู่ Tarantool จะไม่พยายามสร้างใหม่อีกครั้ง ตัวเลือกนี้จะใช้ในคำสั่ง DDL ที่ตามมาทั้งหมด

เรามาสร้างพื้นที่เป็นตัวอย่างกันเถอะ

box.schema.space.create('goods', {
    format = {
        {
            name = 'id',
            type = 'unsigned'

        },
        {
            name = 'name',
            type = 'string'

        },
        {
            name = 'code',
            type = 'unsigned'

        },
        {
            name = 'row_ver',
            type = 'unsigned'

        }
    },
    if_not_exists = true
})

ที่นี่เราตั้งชื่อของพื้นที่ (goods) ชื่อฟิลด์และประเภท

ฟิลด์ที่เพิ่มขึ้นอัตโนมัติใน Tarantool ก็ถูกสร้างขึ้นโดยใช้ลำดับเช่นกัน มาสร้างคีย์หลักแบบเพิ่มค่าอัตโนมัติตามฟิลด์กัน id:

box.schema.sequence.create('goods_id',
    { if_not_exists = true })
box.space.goods:create_index('primary', {
    parts = { 'id' },
    sequence = 'goods_id',
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

Tarantool รองรับดัชนีหลายประเภท ดัชนีที่ใช้กันมากที่สุดคือประเภท TREE และ HASH ซึ่งขึ้นอยู่กับโครงสร้างที่สอดคล้องกับชื่อ TREE เป็นประเภทดัชนีที่หลากหลายที่สุด ช่วยให้คุณสามารถดึงข้อมูลในลักษณะที่มีการจัดระเบียบ แต่สำหรับการเลือกความเท่าเทียมกัน HASH จะเหมาะสมกว่า ดังนั้นจึงแนะนำให้ใช้ HASH สำหรับคีย์หลัก (ซึ่งเป็นสิ่งที่เราทำ)

หากต้องการใช้คอลัมน์ row_ver ในการถ่ายโอนข้อมูลที่เปลี่ยนแปลง คุณจะต้องผูกค่าลำดับเข้ากับฟิลด์ของคอลัมน์นี้ row_ver. แต่ต่างจากคีย์หลักตรงค่าฟิลด์คอลัมน์ row_ver ควรเพิ่มขึ้นทีละรายการไม่เพียงแต่เมื่อเพิ่มบันทึกใหม่ แต่ยังรวมถึงเมื่อเปลี่ยนแปลงรายการที่มีอยู่ด้วย คุณสามารถใช้ทริกเกอร์สำหรับสิ่งนี้ Tarantool มีทริกเกอร์พื้นที่สองประเภท: before_replace и on_replace. ทริกเกอร์จะเริ่มทำงานเมื่อใดก็ตามที่ข้อมูลในพื้นที่เปลี่ยนแปลง (สำหรับทูเพิลแต่ละตัวที่ได้รับผลกระทบจากการเปลี่ยนแปลง ฟังก์ชันทริกเกอร์จะถูกเปิดใช้งาน) ไม่เหมือน on_replace, before_replace-triggers อนุญาตให้คุณแก้ไขข้อมูลของ tuple ที่เรียกใช้งานทริกเกอร์ ดังนั้นทริกเกอร์ประเภทสุดท้ายจึงเหมาะกับเรา

box.space.goods:before_replace(function(old, new)
    return box.tuple.new({new[1], new[2], new[3],
        box.sequence.row_version:next()})
end)

ทริกเกอร์ต่อไปนี้จะแทนที่ค่าของฟิลด์ row_ver สิ่งทูเพิลที่เก็บไว้เป็นค่าถัดไปของลำดับ row_version.

เพื่อให้สามารถดึงข้อมูลจากอวกาศได้ goods ตามคอลัมน์ row_verมาสร้างดัชนีกันดีกว่า:

box.space.goods:create_index('row_ver', {
    parts = { 'row_ver' },
    unique = true,
    type = 'TREE',
    if_not_exists = true
})

ประเภทดัชนี - ต้นไม้ (TREE), เพราะ เราจะต้องดึงข้อมูลตามลำดับค่าในคอลัมน์จากน้อยไปมาก row_ver.

มาเพิ่มข้อมูลลงในพื้นที่:

box.space.goods:insert{nil, 'pen', 123}
box.space.goods:insert{nil, 'pencil', 321}
box.space.goods:insert{nil, 'brush', 100}
box.space.goods:insert{nil, 'watercolour', 456}
box.space.goods:insert{nil, 'album', 101}
box.space.goods:insert{nil, 'notebook', 800}
box.space.goods:insert{nil, 'rubber', 531}
box.space.goods:insert{nil, 'ruler', 135}

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

ตรวจสอบผลการแทรก:

tarantool> box.space.goods:select()
---
- - [1, 'pen', 123, 1]
  - [2, 'pencil', 321, 2]
  - [3, 'brush', 100, 3]
  - [4, 'watercolour', 456, 4]
  - [5, 'album', 101, 5]
  - [6, 'notebook', 800, 6]
  - [7, 'rubber', 531, 7]
  - [8, 'ruler', 135, 8]
...

อย่างที่คุณเห็น ฟิลด์แรกและฟิลด์สุดท้ายจะถูกกรอกโดยอัตโนมัติ ตอนนี้การเขียนฟังก์ชันสำหรับการอัปโหลดการเปลี่ยนแปลงพื้นที่ทีละหน้าจะเป็นเรื่องง่าย goods:

local page_size = 5
local function get_goods(row_ver)
    local index = box.space.goods.index.row_ver
    local goods = {}
    local counter = 0
    for _, tuple in index:pairs(row_ver, {
        iterator = 'GT' }) do
        local obj = tuple:tomap({ names_only = true })
        table.insert(goods, obj)
        counter = counter + 1
        if counter >= page_size then
            break
        end
    end
    return goods
end

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

การสุ่มตัวอย่างข้อมูลใน Tarantool ทำได้ผ่านดัชนี การทำงาน get_goods ใช้ตัววนซ้ำตามดัชนี row_ver เพื่อรับข้อมูลที่เปลี่ยนแปลง ประเภทตัววนซ้ำคือ GT (มากกว่า, มากกว่า) ซึ่งหมายความว่าตัววนซ้ำจะสำรวจค่าดัชนีตามลำดับโดยเริ่มจากคีย์ที่ส่งผ่าน (ค่าฟิลด์ row_ver).

ตัววนซ้ำส่งคืนสิ่งอันดับ เพื่อให้สามารถถ่ายโอนข้อมูลผ่าน HTTP ได้ในภายหลัง จำเป็นต้องแปลงสิ่งอันดับเป็นโครงสร้างที่สะดวกสำหรับการทำให้เป็นอนุกรมในภายหลัง ตัวอย่างใช้ฟังก์ชันมาตรฐานสำหรับสิ่งนี้ tomap. แทนที่จะใช้ tomap คุณสามารถเขียนฟังก์ชันของคุณเองได้ ตัวอย่างเช่น เราอาจต้องการเปลี่ยนชื่อฟิลด์ name,ห้ามผ่านสนาม code และเพิ่มฟิลด์ comment:

local function unflatten_goods(tuple)
    local obj = {}
    obj.id = tuple.id
    obj.goods_name = tuple.name
    obj.comment = 'some comment'
    obj.row_ver = tuple.row_ver
    return obj
end

ขนาดหน้าของข้อมูลที่ส่งออก (จำนวนบันทึกในหนึ่งส่วน) ถูกกำหนดโดยตัวแปร page_size. ในตัวอย่างค่า page_size คือ 5 ในโปรแกรมจริง ขนาดหน้ามักจะมีความสำคัญมากกว่า ขึ้นอยู่กับขนาดเฉลี่ยของทูเพิลสเปซ ขนาดหน้าที่เหมาะสมที่สุดสามารถกำหนดได้จากการทดลองโดยการวัดเวลาการถ่ายโอนข้อมูล ยิ่งขนาดหน้าใหญ่ขึ้น จำนวนการไปกลับระหว่างด้านการส่งและการรับก็จะยิ่งน้อยลง วิธีนี้ทำให้คุณสามารถลดเวลาโดยรวมในการดาวน์โหลดการเปลี่ยนแปลงได้ อย่างไรก็ตาม หากขนาดหน้าใหญ่เกินไป เราจะใช้เวลานานเกินไปบนเซิร์ฟเวอร์ในการเรียงลำดับตัวอย่าง เป็นผลให้อาจมีความล่าช้าในการประมวลผลคำขออื่น ๆ ที่มาถึงเซิร์ฟเวอร์ พารามิเตอร์ page_size สามารถโหลดได้จากไฟล์กำหนดค่า สำหรับแต่ละพื้นที่ที่ส่ง คุณสามารถตั้งค่าของตัวเองได้ อย่างไรก็ตาม สำหรับช่องว่างส่วนใหญ่ ค่าเริ่มต้น (เช่น 100) อาจเหมาะสม

เรามารันฟังก์ชันกัน get_goods:

tarantool> get_goods(0)

---
- - row_ver: 1
    code: 123
    name: pen
    id: 1
  - row_ver: 2
    code: 321
    name: pencil
    id: 2
  - row_ver: 3
    code: 100
    name: brush
    id: 3
  - row_ver: 4
    code: 456
    name: watercolour
    id: 4
  - row_ver: 5
    code: 101
    name: album
    id: 5
...

ลองหาค่าสนามดู row_ver จากบรรทัดสุดท้ายแล้วเรียกใช้ฟังก์ชันอีกครั้ง:

tarantool> get_goods(5)

---
- - row_ver: 6
    code: 800
    name: notebook
    id: 6
  - row_ver: 7
    code: 531
    name: rubber
    id: 7
  - row_ver: 8
    code: 135
    name: ruler
    id: 8
...

อีกครั้ง:

tarantool> get_goods(8)
---
- []
...

อย่างที่คุณเห็น เมื่อใช้วิธีนี้ ฟังก์ชันจะส่งคืนระเบียนพื้นที่ทั้งหมดทีละหน้า goods. หน้าสุดท้ายตามด้วยส่วนที่เลือกว่าง

มาทำการเปลี่ยนแปลงพื้นที่กันเถอะ:

box.space.goods:update(4, {{'=', 6, 'copybook'}})
box.space.goods:insert{nil, 'clip', 234}
box.space.goods:insert{nil, 'folder', 432}

เราได้เปลี่ยนค่าฟิลด์ name สำหรับหนึ่งรายการและเพิ่มสองรายการใหม่

ทำซ้ำการเรียกใช้ฟังก์ชันครั้งล่าสุด:

tarantool> get_goods(8)
---



- - row_ver: 9
    code: 800
    name: copybook
    id: 6
  - row_ver: 10
    code: 234
    name: clip
    id: 9
  - row_ver: 11
    code: 432
    name: folder
    id: 10
...

ฟังก์ชันส่งคืนบันทึกที่เปลี่ยนแปลงและเพิ่ม ดังนั้นฟังก์ชัน get_goods ช่วยให้คุณได้รับข้อมูลที่เปลี่ยนแปลงนับตั้งแต่การโทรครั้งล่าสุด ซึ่งเป็นพื้นฐานของวิธีการจำลองแบบที่กำลังพิจารณา

เราจะปล่อยให้การออกผลลัพธ์ผ่าน HTTP ในรูปแบบ JSON อยู่นอกขอบเขตของบทความนี้ คุณสามารถอ่านเกี่ยวกับเรื่องนี้ได้ที่นี่: https://habr.com/ru/company/mailru/blog/272141/

การดำเนินการส่วนลูกค้า/ทาส

มาดูกันว่าการใช้งานของฝ่ายรับจะเป็นอย่างไร มาสร้างช่องว่างด้านรับเพื่อเก็บข้อมูลที่ดาวน์โหลดมา:

box.schema.space.create('goods', {
    format = {
        {
            name = 'id',
            type = 'unsigned'

        },
        {
            name = 'name',
            type = 'string'

        },
        {
            name = 'code',
            type = 'unsigned'

        }
    },
    if_not_exists = true
})

box.space.goods:create_index('primary', {
    parts = { 'id' },
    sequence = 'goods_id',
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

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

นอกจากนี้เรายังต้องมีพื้นที่ในการบันทึกค่าอีกด้วย row_ver:

box.schema.space.create('row_ver', {
    format = {
        {
            name = 'space_name',
            type = 'string'

        },
        {
            name = 'value',
            type = 'string'

        }
    },
    if_not_exists = true
})

box.space.row_ver:create_index('primary', {
    parts = { 'space_name' },
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

สำหรับแต่ละพื้นที่โหลด (field space_name) เราจะบันทึกค่าที่โหลดล่าสุดไว้ที่นี่ row_ver (สนาม value). คอลัมน์ทำหน้าที่เป็นคีย์หลัก space_name.

มาสร้างฟังก์ชันเพื่อโหลดข้อมูลพื้นที่กันดีกว่า goods ผ่านทาง HTTP ในการดำเนินการนี้ เราจำเป็นต้องมีไลบรารีที่ใช้ไคลเอ็นต์ HTTP บรรทัดต่อไปนี้โหลดไลบรารีและสร้างอินสแตนซ์ไคลเอ็นต์ HTTP:

local http_client = require('http.client').new()

เรายังต้องการไลบรารีสำหรับการดีซีเรียลไลซ์ json:

local json = require('json')

เพียงพอที่จะสร้างฟังก์ชันการโหลดข้อมูล:

local function load_data(url, row_ver)
    local url = ('%s?rowVer=%s'):format(url,
        tostring(row_ver))
    local body = nil
    local data = http_client:request('GET', url, body, {
        keepalive_idle =  1,
        keepalive_interval = 1
    })
    return json.decode(data.body)
end

ฟังก์ชั่นดำเนินการคำขอ HTTP ไปยังที่อยู่ URL และส่ง row_ver เป็นพารามิเตอร์และส่งกลับผลลัพธ์ดีซีเรียลไลซ์ของการร้องขอ

ฟังก์ชั่นสำหรับบันทึกข้อมูลที่ได้รับมีลักษณะดังนี้:

local function save_goods(goods)
    local n = #goods
    box.atomic(function()
        for i = 1, n do
            local obj = goods[i]
            box.space.goods:put(
                obj.id, obj.name, obj.code)
        end
    end)
end

วงจรการบันทึกข้อมูลลงสู่อวกาศ goods วางในธุรกรรม (ฟังก์ชันใช้สำหรับสิ่งนี้ box.atomic) เพื่อลดจำนวนการทำงานของดิสก์

ในที่สุดฟังก์ชันการซิงโครไนซ์พื้นที่ท้องถิ่น goods ด้วยแหล่งที่มาคุณสามารถนำไปใช้ได้ดังนี้:

local function sync_goods()
    local tuple = box.space.row_ver:get('goods')
    local row_ver = tuple and tuple.value or 0

    —— set your url here:
    local url = 'http://127.0.0.1:81/test/goods/list'

    while true do
        local goods = load_goods(url, row_ver)

        local count = #goods
        if count == 0 then
            return
        end

        save_goods(goods)

        row_ver = goods[count].rowVer
        box.space.row_ver:put({'goods', row_ver})
    end
end

ก่อนอื่นเราอ่านค่าที่บันทึกไว้ก่อนหน้านี้ row_ver สำหรับพื้นที่ goods. ถ้ามันหายไป (ช่วงการแลกเปลี่ยนครั้งแรก) เราก็ถือว่ามันเป็น row_ver ศูนย์. ต่อไปในรอบนี้ เราจะทำการดาวน์โหลดข้อมูลที่เปลี่ยนแปลงทีละหน้าจากแหล่งที่มาที่ URL ที่ระบุ ในการวนซ้ำแต่ละครั้ง เราจะบันทึกข้อมูลที่ได้รับลงในพื้นที่ท้องถิ่นที่เหมาะสมและอัปเดตค่า row_ver (ในที่ว่าง row_ver และในตัวแปร row_ver) - รับค่า row_ver จากบรรทัดสุดท้ายของข้อมูลที่โหลด

เพื่อป้องกันการวนซ้ำโดยไม่ตั้งใจ (ในกรณีที่เกิดข้อผิดพลาดในโปรแกรม) การวนซ้ำ while สามารถแทนที่ด้วย for:

for _ = 1, max_req do ...

อันเป็นผลมาจากการรันฟังก์ชัน sync_goods ช่องว่าง goods ผู้รับจะมีบันทึกพื้นที่ทั้งหมดเวอร์ชันล่าสุด goods ในแหล่งที่มา

แน่นอนว่าการลบข้อมูลไม่สามารถถ่ายทอดในลักษณะนี้ได้ หากมีความจำเป็นดังกล่าว คุณสามารถใช้เครื่องหมายลบได้ เพิ่มลงในพื้นที่ goods ฟิลด์บูลีน is_deleted และแทนที่จะลบบันทึกทางกายภาพ เราใช้การลบแบบลอจิคัล - เราตั้งค่าฟิลด์ is_deleted สู่ความหมาย true. บางครั้งแทนที่จะเป็นเขตข้อมูลบูลีน is_deleted การใช้สนามสะดวกกว่า deletedซึ่งเก็บวันที่-เวลาของการลบเรกคอร์ดแบบลอจิคัล หลังจากดำเนินการลบแบบลอจิคัล บันทึกที่ถูกทำเครื่องหมายสำหรับการลบจะถูกโอนจากต้นทางไปยังปลายทาง (ตามตรรกะที่กล่าวถึงข้างต้น)

ลำดับ row_ver สามารถใช้ในการส่งข้อมูลจากพื้นที่อื่น: ไม่จำเป็นต้องสร้างลำดับแยกต่างหากสำหรับแต่ละพื้นที่ที่ส่ง

เราพิจารณาวิธีการจำลองข้อมูลระดับสูงที่มีประสิทธิภาพในแอปพลิเคชันที่ใช้ Tarantool DBMS

ผลการวิจัย

  1. Tarantool DBMS เป็นผลิตภัณฑ์ที่น่าสนใจและมีแนวโน้มสำหรับการสร้างแอปพลิเคชันที่มีภาระงานสูง
  2. การจำลองข้อมูลระดับสูงมีข้อดีมากกว่าการจำลองข้อมูลระดับต่ำหลายประการ
  3. วิธีการจำลองแบบระดับสูงที่กล่าวถึงในบทความช่วยให้คุณลดจำนวนข้อมูลที่ถ่ายโอนให้เหลือน้อยที่สุดโดยถ่ายโอนเฉพาะบันทึกที่เปลี่ยนแปลงตั้งแต่เซสชันการแลกเปลี่ยนครั้งล่าสุด

ที่มา: will.com

เพิ่มความคิดเห็น