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


100

ลองนึกภาพกรณีต่อไปนี้:

  • ไคลเอนต์ 1,000 รายเชื่อมต่อกับเพจ Meteor ที่แสดงเนื้อหาของคอลเลกชัน "Somestuff"

  • "Somestuff" เป็นคอลเลกชันที่มีสินค้า 1,000 ชิ้น

  • มีคนแทรกรายการใหม่ลงในคอลเลกชัน "Somestuff"

อะไรจะเกิดขึ้น:

  • ทั้งหมดMeteor.Collectionบนไคลเอนต์จะได้รับการอัปเดตเช่นการแทรกที่ส่งต่อไปยังพวกเขาทั้งหมด (ซึ่งหมายถึงข้อความแทรกหนึ่งข้อความที่ส่งไปยังลูกค้า 1,000 ราย)

ค่าใช้จ่ายในแง่ของ CPU สำหรับเซิร์ฟเวอร์คืออะไรในการพิจารณาว่าไคลเอ็นต์ใดที่ต้องอัปเดต?

ถูกต้องหรือไม่ที่จะส่งต่อเฉพาะค่าที่แทรกไปยังไคลเอนต์ไม่ใช่รายการทั้งหมด

วิธีนี้ใช้ในชีวิตจริงได้อย่างไร? มีการวัดประสิทธิภาพหรือการทดลองในระดับดังกล่าวหรือไม่

คำตอบ:


119

คำตอบสั้น ๆ คือมีเพียงข้อมูลใหม่เท่านั้นที่ถูกส่งไปตามสาย นี่คือวิธีการทำงาน

มีสามส่วนที่สำคัญของเซิร์ฟเวอร์ Meteor ที่จัดการการสมัครสมาชิก ได้แก่ฟังก์ชันการเผยแพร่ซึ่งกำหนดตรรกะของข้อมูลที่การสมัครสมาชิกให้ ไดรเวอร์ Mongoซึ่งนาฬิกาฐานข้อมูลสำหรับการเปลี่ยนแปลง และกล่องผสานซึ่งรวมการสมัครสมาชิกที่ใช้งานอยู่ทั้งหมดของลูกค้าและส่งออกไปทางเครือข่ายไปยังไคลเอนต์

เผยแพร่ฟังก์ชัน

ทุกครั้งที่ลูกค้าดาวตกสมัครคอลเลกชันเซิร์ฟเวอร์ทำงาน ฟังก์ชั่นเผยแพร่ งานของฟังก์ชันการเผยแพร่คือการหาชุดเอกสารที่ไคลเอ็นต์ควรมีและส่งคุณสมบัติเอกสารแต่ละรายการลงในกล่องผสาน จะทำงานหนึ่งครั้งสำหรับลูกค้าที่สมัครใหม่แต่ละราย คุณสามารถใส่ JavaScript ที่คุณต้องการในฟังก์ชันการเผยแพร่เช่นการควบคุมการเข้าถึงที่ซับซ้อนโดยพลการโดยใช้this.userId. ฟังก์ชั่นการเผยแพร่ส่งข้อมูลลงในช่องผสานโดยการโทรthis.added, และthis.changed this.removedดู เอกสารเผยแพร่ฉบับเต็มสำหรับรายละเอียดเพิ่มเติม

ฟังก์ชั่นเผยแพร่ส่วนใหญ่ไม่ได้โคลนรอบกับระดับต่ำ added, changedและremovedAPI แม้ว่า หากมีการเผยแพร่ผลตอบแทนการทำงานของเคอร์เซอร์ Mongo เซิร์ฟเวอร์ดาวตกจะเชื่อมต่อการส่งออกของไดรเวอร์ Mongo ( insert, updateและremovedเรียกกลับ) เพื่อป้อนข้อมูลของกล่องผสาน ( this.added, this.changedและthis.removed) ค่อนข้างเรียบร้อยที่คุณสามารถตรวจสอบสิทธิ์ทั้งหมดได้ล่วงหน้าในฟังก์ชันการเผยแพร่จากนั้นเชื่อมต่อไดรเวอร์ฐานข้อมูลเข้ากับกล่องผสานโดยตรงโดยไม่ต้องใช้รหัสผู้ใช้ใด ๆ และเมื่อเปิดการเผยแพร่อัตโนมัติแม้เพียงเล็กน้อยนี้จะถูกซ่อนไว้: เซิร์ฟเวอร์จะตั้งค่าการสืบค้นสำหรับเอกสารทั้งหมดในแต่ละคอลเลกชั่นโดยอัตโนมัติและส่งไปยังกล่องผสาน

ในทางกลับกันคุณไม่ จำกัด เฉพาะการเผยแพร่การสืบค้นฐานข้อมูล ตัวอย่างเช่นคุณสามารถเขียนฟังก์ชันการเผยแพร่ที่อ่านตำแหน่ง GPS จากอุปกรณ์ภายใน a Meteor.setIntervalหรือสำรวจ REST API เดิมจากบริการเว็บอื่น ในกรณีที่คุณต้องการปล่อยเปลี่ยนแปลงกล่องผสานโดยการเรียกระดับต่ำadded, changedและremovedDDP API

คนขับ Mongo

ไดรเวอร์ Mongo ของงานคือการดูฐานข้อมูล Mongo เพื่อให้การเปลี่ยนแปลงคำสั่งที่มีชีวิต คำสั่งเหล่านี้ทำงานอย่างต่อเนื่องและกลับไปปรับปรุงตลอดการเปลี่ยนแปลงผลโดยการโทรadded, removedและchangedเรียกกลับ

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

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

กล่องผสาน

งานของกล่องผสานคือการรวมผล ( added, changedและremoved สาย) ทั้งหมดของฟังก์ชั่นการใช้งานเผยแพร่ของลูกค้าเข้าไปในกระแสข้อมูลเดียว มีหนึ่งกล่องผสานสำหรับแต่ละไคลเอนต์ที่เชื่อมต่อ มันมีสำเนา minimongo cache ของลูกค้าที่สมบูรณ์

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

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

เกิดอะไรขึ้นกับการอัปเดต

ตอนนี้เราได้กำหนดขั้นตอนสำหรับสถานการณ์ของคุณแล้ว

เรามีลูกค้าที่เชื่อมต่อ 1,000 ราย แต่ละคนสมัครรับข้อความค้นหา Mongo แบบสดเดียวกัน ( Somestuff.find({})) เนื่องจากแบบสอบถามเหมือนกันสำหรับไคลเอ็นต์แต่ละตัวโปรแกรมควบคุมจึงเรียกใช้แบบสอบถามสดเพียงรายการเดียว มี 1,000 กล่องผสานที่ใช้งานอยู่ และแต่ละคนของลูกค้าเผยแพร่ฟังก์ชั่นการจดทะเบียนadded, changedและ removedบนว่าแบบสอบถามที่มีชีวิตที่ฟีดเป็นหนึ่งในกล่องผสาน ไม่มีสิ่งอื่นใดที่เชื่อมต่อกับกล่องผสาน

ก่อนอื่นคนขับ Mongo เมื่อไคลเอนต์รายใดรายหนึ่งแทรกเอกสารใหม่ลงในSomestuffเครื่องจะทริกเกอร์การคำนวณใหม่ โปรแกรมควบคุม Mongo เรียกใช้แบบสอบถามสำหรับเอกสารทั้งหมดในSomestuffอีกครั้งเปรียบเทียบผลลัพธ์กับผลลัพธ์ก่อนหน้านี้ในหน่วยความจำพบว่ามีเอกสารใหม่หนึ่งฉบับและเรียกการinsertเรียกกลับที่ลงทะเบียนไว้ 1,000 รายการ

ถัดไปฟังก์ชั่นการเผยแพร่ มีน้อยมากที่เกิดขึ้นที่นี่: แต่ละ 1,000 เรียกกลับข้อมูลดันลงในช่องผสานโดยการเรียกinsertadded

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

ต้นทุน CPU ทั้งหมดคือต้นทุนในการเปลี่ยนข้อความค้นหา Mongo หนึ่งรายการบวกค่าใช้จ่าย 1,000 กล่องผสานเพื่อตรวจสอบสถานะของลูกค้าและสร้างเพย์โหลดข้อความ DDP ใหม่ ข้อมูลเดียวที่ไหลผ่านสายคือออบเจ็กต์ JSON เดียวที่ส่งไปยังไคลเอ็นต์ 1,000 เครื่องซึ่งสอดคล้องกับเอกสารใหม่ในฐานข้อมูลพร้อมด้วยข้อความ RPC หนึ่งข้อความไปยังเซิร์ฟเวอร์จากไคลเอนต์ที่ทำการแทรกต้นฉบับ

การเพิ่มประสิทธิภาพ

นี่คือสิ่งที่เราวางแผนไว้อย่างแน่นอน

  • ไดรเวอร์ Mongo ที่มีประสิทธิภาพมากขึ้น เรา ปรับไดรเวอร์ให้เหมาะสม ใน 0.5.1 เพื่อเรียกใช้ผู้สังเกตการณ์เพียงคนเดียวต่อแบบสอบถามที่แตกต่างกัน

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

  • ไดรเวอร์ Mongo ฟังก์ชันการเผยแพร่และการผสานไม่จำเป็นต้องทำงานในกระบวนการเดียวกันหรือแม้แต่ในเครื่องเดียวกัน แอปพลิเคชันบางตัวเรียกใช้แบบสอบถามสดที่ซับซ้อนและต้องการ CPU มากขึ้นเพื่อดูฐานข้อมูล คนอื่น ๆ มีแบบสอบถามที่แตกต่างกันเพียงเล็กน้อย (ลองนึกภาพบล็อกเอ็นจิ้น) แต่อาจมีไคลเอนต์ที่เชื่อมต่อจำนวนมากซึ่งจำเป็นต้องใช้ CPU มากขึ้นสำหรับการผสานกล่อง การแยกส่วนประกอบเหล่านี้จะทำให้เราปรับขนาดแต่ละชิ้นได้อย่างอิสระ

  • ฐานข้อมูลจำนวนมากสนับสนุนทริกเกอร์ที่เริ่มทำงานเมื่อมีการอัปเดตแถวและระบุแถวเก่าและใหม่ ด้วยคุณสมบัติดังกล่าวโปรแกรมควบคุมฐานข้อมูลสามารถลงทะเบียนทริกเกอร์แทนการสำรวจการเปลี่ยนแปลง


มีตัวอย่างวิธีใช้ Meteor.publish เพื่อเผยแพร่ข้อมูลที่ไม่ใช่เคอร์เซอร์หรือไม่? เช่นผลลัพธ์จาก API ที่เหลือแบบเดิมที่กล่าวถึงในคำตอบหรือไม่
Tony

@ โทนี่: มันอยู่ในเอกสาร ตรวจสอบตัวอย่างการนับห้อง
Mitar

มันเป็นที่น่าสังเกตว่าในรุ่น 0.7, 0.7.1, 0.7.2 ดาวตกเปลี่ยนไป OpLog สังเกตไดร์เวอร์สำหรับแบบสอบถามส่วนใหญ่ (ยกเว้นskip, $nearและ$whereมีคำสั่ง) ซึ่งอยู่ไกลมีประสิทธิภาพมากขึ้นในการโหลด CPU, แบนด์วิธเครือข่ายและช่วยให้การปรับใบสมัคร เซิร์ฟเวอร์
imslavko

แล้วเมื่อผู้ใช้ทุกคนไม่เห็นข้อมูลเดียวกัน 1. พวกเขาสมัครรับข้อมูลหัวข้อต่างๆ 2. พวกเขามีบทบาทที่แตกต่างกันดังนั้นในหัวข้อหลักเดียวกันจึงมีข้อความบางส่วนที่ไม่ควรเข้าถึง
tgkprog

@debergalis เกี่ยวกับการยกเลิกแคชบางทีคุณอาจพบแนวคิดจากกระดาษของฉันvanisoft.pl/~lopuszanski/public/cache_invalidation.pdfคุ้มค่า
qbolec

29

จากประสบการณ์ของฉันการใช้ไคลเอนต์จำนวนมากในขณะที่แบ่งปันคอลเลกชันขนาดใหญ่ใน Meteor นั้นไม่สามารถใช้งานได้เป็นหลักเนื่องจากเวอร์ชัน 0.7.0.1 ฉันจะพยายามอธิบายว่าทำไม

ตามที่อธิบายไว้ในโพสต์ดังกล่าวข้างต้นและยังอยู่ในhttps://github.com/meteor/meteor/issues/1821เซิร์ฟเวอร์ดาวตกที่มีการเก็บสำเนาของข้อมูลที่เผยแพร่สำหรับลูกค้าในแต่ละกล่องผสาน นี่คือสิ่งที่ช่วยให้เวทมนตร์ของ Meteor เกิดขึ้น แต่ยังส่งผลให้ฐานข้อมูลที่ใช้ร่วมกันขนาดใหญ่ถูกเก็บไว้ในหน่วยความจำของกระบวนการโหนดซ้ำ ๆ แม้ว่าจะใช้การเพิ่มประสิทธิภาพที่เป็นไปได้สำหรับคอลเลกชันแบบคงที่เช่นใน ( มีวิธีบอกอุกกาบาตว่าคอลเลกชันคงที่หรือไม่ (จะไม่เปลี่ยนแปลง)เราก็ประสบปัญหาอย่างมากกับการใช้ CPU และหน่วยความจำของกระบวนการโหนด

ในกรณีของเราเรากำลังเผยแพร่ชุดเอกสาร 15,000 ชุดให้กับลูกค้าแต่ละรายซึ่งเป็นแบบคงที่ ปัญหาคือการคัดลอกเอกสารเหล่านี้ไปยังกล่องผสานของไคลเอ็นต์ (ในหน่วยความจำ) เมื่อเชื่อมต่อโดยพื้นฐานแล้วทำให้กระบวนการ Node เป็น CPU 100% เป็นเวลาเกือบหนึ่งวินาทีและส่งผลให้มีการใช้หน่วยความจำเพิ่มเติมจำนวนมาก สิ่งนี้ไม่สามารถปรับขนาดได้โดยเนื้อแท้เนื่องจากไคลเอนต์ที่เชื่อมต่อใด ๆ จะทำให้เซิร์ฟเวอร์คุกเข่าลง (และการเชื่อมต่อพร้อมกันจะปิดกั้นซึ่งกันและกัน) และการใช้หน่วยความจำจะเพิ่มขึ้นตามจำนวนไคลเอนต์ ในกรณีของเราไคลเอนต์แต่ละรายทำให้เกิดการใช้หน่วยความจำเพิ่มขึ้น~ 60MBแม้ว่าข้อมูลดิบที่ถ่ายโอนจะมีเพียงประมาณ 5MB

ในกรณีของเราเนื่องจากคอลเล็กชันเป็นแบบคงที่เราจึงแก้ปัญหานี้ด้วยการส่งเอกสารทั้งหมดเป็น.jsonไฟล์ซึ่งถูก gzipped โดย nginx และโหลดลงในคอลเล็กชันแบบไม่ระบุตัวตนทำให้มีการถ่ายโอนข้อมูลเพียง ~ 1MB โดยไม่มี CPU เพิ่มเติม หรือหน่วยความจำในกระบวนการโหนดและเวลาในการโหลดเร็วขึ้นมาก การดำเนินการทั้งหมดในคอลเลกชันนี้ทำได้โดยใช้_ids จากสิ่งพิมพ์ที่มีขนาดเล็กกว่ามากบนเซิร์ฟเวอร์ทำให้สามารถรักษาผลประโยชน์ส่วนใหญ่ของ Meteor ได้ สิ่งนี้ทำให้แอปสามารถปรับขนาดให้เข้ากับลูกค้าได้มากขึ้น นอกจากนี้เนื่องจากแอปของเราส่วนใหญ่เป็นแบบอ่านอย่างเดียวเราจึงปรับปรุงความสามารถในการปรับขยายเพิ่มเติมโดยการเรียกใช้อินสแตนซ์ Meteor หลายตัวที่อยู่ด้านหลัง nginx ด้วยการจัดสรรภาระงาน (แม้ว่าจะมี Mongo ตัวเดียวก็ตาม) เนื่องจากอินสแตนซ์ Node แต่ละรายการเป็นแบบเธรดเดียว

อย่างไรก็ตามปัญหาของการแบ่งปันคอลเลกชันขนาดใหญ่ที่เขียนได้ระหว่างลูกค้าหลายรายเป็นปัญหาด้านวิศวกรรมที่ต้องแก้ไขโดย Meteor อาจมีวิธีที่ดีกว่าการเก็บสำเนาทุกอย่างสำหรับลูกค้าแต่ละราย แต่ต้องใช้ความคิดอย่างจริงจังเนื่องจากปัญหาระบบกระจาย ปัญหาปัจจุบันของการใช้งาน CPU และหน่วยความจำขนาดใหญ่จะไม่ปรับขนาด


@ แฮรี่ oplog ไม่สำคัญในสถานการณ์นี้ ข้อมูลคงที่
Andrew Mao

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

@MistereeDevlord ความแตกต่างของการเปลี่ยนแปลงและแคชของข้อมูลไคลเอนต์จะแยกกันในขณะนี้ แม้ว่าทุกคนจะมีข้อมูลเดียวกันและต้องการความแตกต่างเพียงอย่างเดียว แต่แคชต่อไคลเอนต์ก็แตกต่างกันเนื่องจากเซิร์ฟเวอร์ไม่สามารถปฏิบัติเหมือนกันได้ สิ่งนี้สามารถทำได้อย่างชาญฉลาดกว่าการใช้งานที่มีอยู่
Andrew Mao

@AndrewMao คุณจะแน่ใจได้อย่างไรว่าไฟล์ gzipped มีความปลอดภัยเมื่อส่งไปยังไคลเอนต์กล่าวคือมีเพียงไคลเอนต์ที่ล็อกอินเท่านั้นที่สามารถเข้าถึงได้
FullStack

4

การทดลองที่คุณสามารถใช้เพื่อตอบคำถามนี้:

  1. ติดตั้งดาวตกทดสอบ: meteor create --example todos
  2. เรียกใช้ภายใต้ Webkit inspector (WKI)
  3. ตรวจสอบเนื้อหาของข้อความXHR ที่เคลื่อนที่ข้ามสาย
  4. สังเกตว่าคอลเลกชันทั้งหมดไม่ได้ถูกย้ายข้ามสายไฟ

สำหรับเคล็ดลับเกี่ยวกับวิธีการใช้การตรวจสอบ WKI นี้ออกบทความ มันล้าสมัยเล็กน้อย แต่ส่วนใหญ่ยังใช้ได้โดยเฉพาะสำหรับคำถามนี้


2
คำอธิบายกลไกการสำรวจ: eventedmind.com/posts/meteor-liveresultsset
cmather

3

ตอนนี้ยังคงเป็นเวลาหนึ่งปีแล้วดังนั้นฉันจึงคิดว่าความรู้ก่อน "Meteor 1.0" ดังนั้นสิ่งต่างๆอาจมีการเปลี่ยนแปลงอีกครั้ง? ฉันยังคงมองหาสิ่งนี้ http://meteorhacks.com/does-meteor-scale.html นำไปสู่ ​​"How to scale Meteor?" บทความ http://meteorhacks.com/how-to-scale-meteor.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.