สวัสดี ฉันกำลังสร้างแอปพลิเคชันสำหรับ DBMS
รู้สึกถึงพลัง! (…หรือว่าเพลิดเพลินไปกับการแสดง)
ทั้งหมดที่กล่าวมาทำให้ Tarantool เป็นแพลตฟอร์มที่น่าสนใจสำหรับการสร้างแอปพลิเคชันที่มีโหลดสูงซึ่งทำงานกับฐานข้อมูล ในแอปพลิเคชันดังกล่าว มักจำเป็นต้องมีการจำลองข้อมูล
ตามที่กล่าวไว้ข้างต้น Tarantool มีการจำลองข้อมูลในตัว หลักการดำเนินการคือการดำเนินการตามลำดับในการจำลองธุรกรรมทั้งหมดที่มีอยู่ในบันทึกหลัก (WAL) โดยปกติแล้วการจำลองแบบดังกล่าว (เราจะเรียกมันว่าต่อไป ระดับต่ำ) ใช้เพื่อรับรองความทนทานต่อข้อบกพร่องของแอปพลิเคชัน และ/หรือเพื่อกระจายโหลดการอ่านระหว่างโหนดคลัสเตอร์
ข้าว. 1. การจำลองแบบภายในคลัสเตอร์
ตัวอย่างของสถานการณ์ทางเลือกคือการถ่ายโอนข้อมูลที่สร้างในฐานข้อมูลหนึ่งไปยังฐานข้อมูลอื่นเพื่อการประมวลผล/ตรวจสอบ ในกรณีหลัง อาจใช้วิธีแก้ปัญหาที่สะดวกกว่า ระดับสูง การจำลอง - การจำลองข้อมูลในระดับตรรกะทางธุรกิจของแอปพลิเคชัน เหล่านั้น. เราไม่ได้ใช้โซลูชันสำเร็จรูปที่สร้างไว้ใน DBMS แต่ใช้การจำลองแบบด้วยตัวเราเองภายในแอปพลิเคชันที่เรากำลังพัฒนา วิธีนี้มีทั้งข้อดีและข้อเสีย เรามาดูข้อดีกันดีกว่า
1. การประหยัดการจราจร:
- คุณไม่สามารถถ่ายโอนข้อมูลทั้งหมดได้ แต่เพียงบางส่วนเท่านั้น (ตัวอย่างเช่นคุณสามารถถ่ายโอนได้เพียงบางตารางบางคอลัมน์หรือบันทึกที่ตรงตามเกณฑ์ที่กำหนด)
- ต่างจากการจำลองแบบระดับต่ำซึ่งดำเนินการอย่างต่อเนื่องในแบบอะซิงโครนัส (ใช้งานในเวอร์ชันปัจจุบันของ Tarantool - 1.10) หรือโหมดซิงโครนัส (ที่จะนำไปใช้ในเวอร์ชันถัดไปของ Tarantool) การจำลองแบบระดับสูงสามารถทำได้ในเซสชัน (เช่น แอปพลิเคชันจะซิงโครไนซ์ข้อมูลก่อน - ข้อมูลเซสชันการแลกเปลี่ยน จากนั้นจะมีการหยุดการจำลองชั่วคราว หลังจากนั้นเซสชันการแลกเปลี่ยนถัดไปจะเกิดขึ้น ฯลฯ );
- หากบันทึกมีการเปลี่ยนแปลงหลายครั้ง คุณสามารถถ่ายโอนได้เฉพาะเวอร์ชันล่าสุดเท่านั้น (ไม่เหมือนกับการจำลองแบบระดับต่ำ ซึ่งการเปลี่ยนแปลงทั้งหมดที่ทำกับต้นแบบจะถูกเล่นตามลำดับบนแบบจำลอง)
2. ไม่มีปัญหาในการใช้งานการแลกเปลี่ยน HTTP ซึ่งช่วยให้คุณสามารถซิงโครไนซ์ฐานข้อมูลระยะไกลได้
ข้าว. 2. การจำลองแบบผ่าน HTTP
3. โครงสร้างฐานข้อมูลระหว่างข้อมูลที่ถูกถ่ายโอนไม่จำเป็นต้องเหมือนกัน (ยิ่งกว่านั้น ในกรณีทั่วไป คุณสามารถใช้ 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 อยู่นอกขอบเขตของบทความนี้ คุณสามารถอ่านเกี่ยวกับเรื่องนี้ได้ที่นี่:
การดำเนินการส่วนลูกค้า/ทาส
มาดูกันว่าการใช้งานของฝ่ายรับจะเป็นอย่างไร มาสร้างช่องว่างด้านรับเพื่อเก็บข้อมูลที่ดาวน์โหลดมา:
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
ผลการวิจัย
- Tarantool DBMS เป็นผลิตภัณฑ์ที่น่าสนใจและมีแนวโน้มสำหรับการสร้างแอปพลิเคชันที่มีภาระงานสูง
- การจำลองข้อมูลระดับสูงมีข้อดีมากกว่าการจำลองข้อมูลระดับต่ำหลายประการ
- วิธีการจำลองแบบระดับสูงที่กล่าวถึงในบทความช่วยให้คุณลดจำนวนข้อมูลที่ถ่ายโอนให้เหลือน้อยที่สุดโดยถ่ายโอนเฉพาะบันทึกที่เปลี่ยนแปลงตั้งแต่เซสชันการแลกเปลี่ยนครั้งล่าสุด
ที่มา: will.com