*นี่คือส่วนที่ 1 ของ ชุด ที่ซึ่งใครๆ ก็สามารถถามคำถามเกี่ยวกับ Geth ได้ และฉันจะพยายามตอบคำถามที่ได้รับคะแนนสูงสุดในแต่ละสัปดาห์ด้วยการเขียนสั้นๆ คำถามที่ได้รับการโหวตสูงสุดประจำสัปดาห์นี้คือ: คุณช่วยแบ่งปันว่าโครงสร้างฐานข้อมูลแบบเรียบแตกต่างจากโครงสร้างเดิมได้อย่างไร*
รัฐใน Ethereum
ก่อนจะเจาะลึกถึงโครงสร้างความเร่ง เรามาดูสิ่งที่ Ethereum เรียกว่ากันก่อนดีกว่า สถานะ และวิธีการจัดเก็บในปัจจุบันในระดับต่างๆ ของนามธรรม
Ethereum รักษาสถานะที่แตกต่างกันสองประเภท: ชุดของบัญชี; และชุดช่องเก็บของสำหรับบัญชีสัญญาแต่ละบัญชี จากก มุมมองที่เป็นนามธรรมล้วนๆทั้งสองอย่างนี้เป็นการแมปคีย์/ค่าอย่างง่าย ชุดบัญชีจะแมปที่อยู่ของ nonce ยอดคงเหลือ ฯลฯ พื้นที่จัดเก็บข้อมูลของสัญญาเดียวจะแมปคีย์ที่กำหนดเอง – กำหนดและใช้งานโดยสัญญา – กับค่าที่กำหนดเอง
น่าเสียดายที่ในขณะที่จัดเก็บคู่คีย์-ค่าเหล่านี้เป็นข้อมูลแบบแบนจะมีประสิทธิภาพมาก แต่การตรวจสอบความถูกต้องกลายเป็นเรื่องยากในการคำนวณ ทุกครั้งที่ทำการแก้ไข เราจะต้องแฮชข้อมูลทั้งหมดตั้งแต่เริ่มต้น
แทนที่จะแฮชชุดข้อมูลทั้งหมดตลอดเวลา เราสามารถแยกมันออกเป็นชิ้นเล็กๆ ที่อยู่ติดกัน และสร้างต้นไม้ไว้ด้านบนได้! ข้อมูลที่เป็นประโยชน์ดั้งเดิมจะอยู่ในใบไม้ และแต่ละโหนดภายในจะเป็นแฮชของทุกสิ่งที่อยู่ด้านล่าง สิ่งนี้จะช่วยให้เราสามารถคำนวณจำนวนแฮชลอการิทึมใหม่ได้เมื่อมีการแก้ไขบางสิ่งเท่านั้น โครงสร้างข้อมูลนี้มีชื่อจริง ๆ แล้วมันมีชื่อเสียง ต้นไม้เมิร์เคิล–
น่าเสียดายที่เรายังขาดความซับซ้อนในการคำนวณเล็กน้อย เค้าโครงแผนผัง Merkle ข้างต้นมีประสิทธิภาพมากในการรวมการแก้ไขข้อมูลที่มีอยู่ แต่การแทรกและการลบจะเปลี่ยนขอบเขตของก้อนและทำให้ใช้งานไม่ได้ ทั้งหมด แฮชที่คำนวณได้
แทนที่จะแบ่งชุดข้อมูลแบบสุ่มสี่สุ่มห้า เราสามารถใช้คีย์เองเพื่อจัดระเบียบข้อมูลให้อยู่ในรูปแบบต้นไม้ตามคำนำหน้าทั่วไป! วิธีนี้การแทรกหรือการลบจะไม่เปลี่ยนโหนดทั้งหมด แต่จะเปลี่ยนเพียงเส้นทางลอการิทึมจากรากหนึ่งไปอีกโหนดหนึ่ง โครงสร้างข้อมูลนี้เรียกว่าก ต้นแพทริเซีย–
รวมแนวคิดทั้งสองเข้าด้วยกัน – เค้าโครงแผนผังของแผนผัง Patricia และอัลกอริธึมการแฮชของแผนผัง Merkle – แล้วคุณจะพบว่า ต้นแมร์เคิล แพทริเซียโครงสร้างข้อมูลจริงที่ใช้เพื่อแสดงสถานะใน Ethereum รับประกันความซับซ้อนของลอการิทึมสำหรับการแก้ไข การแทรก การลบ และการตรวจสอบ! สิ่งพิเศษเล็กๆ น้อยๆ ก็คือคีย์ต่างๆ จะถูกแฮชก่อนการแทรกเพื่อสร้างสมดุลให้กับความพยายาม
ที่เก็บข้อมูลของรัฐใน Ethereum
คำอธิบายข้างต้นอธิบาย ทำไม Ethereum เก็บสถานะไว้ในแผนผัง Merkle Patricia อนิจจา ทันทีที่การดำเนินการที่ต้องการได้รับ ทุกตัวเลือกย่อมต้องแลกมาด้วย ค่าใช้จ่ายของ การอัปเดตลอการิทึมและการตรวจสอบลอการิทึม เป็น การอ่านลอการิทึมและการจัดเก็บลอการิทึม สำหรับ ทุกคีย์– นี่เป็นเพราะว่าโหนดภายในทุกโหนดจำเป็นต้องบันทึกลงดิสก์ทีละรายการ
ฉันไม่มีตัวเลขที่ถูกต้องสำหรับความลึกของการทดลองบัญชีในขณะนี้ แต่เมื่อประมาณหนึ่งปีที่แล้ว เรากำลังทำให้ความลึกของบัญชีอยู่ที่ 7 ซึ่งหมายความว่า การดำเนินการของการทดลองทุกครั้ง (เช่น ความสมดุลในการอ่าน การเขียน nonce) จะต้องแตะอย่างน้อย 7 -8 โหนดภายใน ดังนั้นจะทำการเข้าถึงฐานข้อมูลถาวรอย่างน้อย 7-8 รายการ LevelDB ยังจัดระเบียบข้อมูลได้สูงสุด 7 ระดับ ดังนั้นจึงมีตัวคูณเพิ่มเติมจากที่นั่น ผลลัพธ์สุทธิก็คือก เดี่ยว การเข้าถึงของรัฐคาดว่าจะขยายออกไป สุ่ม 25-50 การเข้าถึงดิสก์ คูณสิ่งนี้กับสถานะทั้งหมดที่อ่านและเขียนว่าธุรกรรมทั้งหมดในบล็อกแตะแล้วคุณจะไปถึง น่ากลัว ตัวเลข.
(แน่นอนว่าการใช้งานไคลเอนต์ทั้งหมดพยายามอย่างดีที่สุดเพื่อลดค่าใช้จ่ายนี้ Geth ใช้พื้นที่หน่วยความจำขนาดใหญ่สำหรับการแคชโหนด trie และยังใช้การตัดในหน่วยความจำเพื่อหลีกเลี่ยงการเขียนลงโหนดดิสก์ที่ถูกลบออกไปหลังจากผ่านไปสองสามบล็อก นั่นสำหรับความแตกต่าง โพสต์ในบล็อกอย่างไรก็ตาม)
แม้ว่าตัวเลขเหล่านี้จะน่ากลัว แต่สิ่งเหล่านี้คือต้นทุนในการใช้งานโหนด Ethereum และมีความสามารถในการตรวจสอบสถานะทั้งหมดด้วยการเข้ารหัสลับตลอดเวลา แต่เราทำได้ดีกว่านี้ได้ไหม?
การเข้าถึงทั้งหมดไม่ได้ถูกสร้างขึ้นเท่ากัน
Ethereum อาศัยการพิสูจน์การเข้ารหัสสำหรับสถานะของตน ไม่มีทางแก้ไขการขยายดิสก์ได้หากเราต้องการรักษาความสามารถในการตรวจสอบข้อมูลทั้งหมดไว้ ก็บอกแล้วเรา สามารถ – และทำ – เชื่อถือข้อมูลที่เราตรวจสอบแล้ว
ไม่มีประโยชน์ที่จะตรวจสอบและตรวจสอบทุกรายการสถานะอีกครั้งทุกครั้งที่เราดึงมันขึ้นมาจากดิสก์ ต้นไม้ Merkle Patricia เป็นสิ่งจำเป็นสำหรับการเขียน แต่เป็นค่าใช้จ่ายในการอ่าน เราไม่สามารถกำจัดมันได้ และเราไม่สามารถทำให้มันผอมลงได้ แต่นั่น ไม่ได้หมายความว่า เราจำเป็นต้องใช้มันทุกที่
โหนด Ethereum เข้าถึงสถานะได้จากที่ต่างๆ ไม่กี่แห่ง:
- เมื่อนำเข้าบล็อกใหม่ การเรียกใช้โค้ด EVM จะทำการอ่านและเขียนสถานะที่สมดุลไม่มากก็น้อย อย่างไรก็ตาม บล็อกการปฏิเสธการให้บริการอาจอ่านมากกว่าเขียนอย่างมาก
- เมื่อตัวดำเนินการโหนดดึงสถานะ (เช่น eth_call และตระกูล) การเรียกใช้โค้ด EVM จะอ่านเท่านั้น (สามารถเขียนได้เช่นกัน แต่สิ่งเหล่านั้นจะถูกละทิ้งในตอนท้ายและจะไม่คงอยู่)
- เมื่อโหนดกำลังซิงโครไนซ์ โหนดกำลังร้องขอสถานะจากโหนดระยะไกลที่จำเป็นต้องขุดและให้บริการผ่านเครือข่าย
ตามรูปแบบการเข้าถึงข้างต้น หากเราสามารถลัดวงจรการอ่านไม่ให้เข้าสู่สถานะ trie การดำเนินการของโหนดจำนวนมากจะกลายเป็น อย่างมีนัยสำคัญ เร็วขึ้น. นอกจากนี้ยังอาจเปิดใช้รูปแบบการเข้าถึงใหม่ๆ บางอย่าง (เช่น การวนซ้ำตามสถานะ) ซึ่งเมื่อก่อนมีราคาแพงมาก
แน่นอนว่าย่อมมีข้อแลกเปลี่ยนเสมอ โครงสร้างการเร่งความเร็วใหม่ใดๆ ก็ตามจะมีค่าใช้จ่ายเพิ่มเติมโดยไม่ต้องกำจัด Trie ออก คำถามคือว่าค่าใช้จ่ายเพิ่มเติมนั้นมีมูลค่าเพียงพอที่จะรับประกันหรือไม่?
กลับสู่ราก
เราได้สร้างต้นไม้ Merkle Patricia ที่มีมนต์ขลังนี้ขึ้นมาเพื่อแก้ไขปัญหาทั้งหมดของเรา และตอนนี้เราต้องการหลีกเลี่ยงที่จะอ่าน เราควรใช้โครงสร้างการเร่งความเร็วใดเพื่อให้อ่านเร็วอีกครั้ง ถ้าเราไม่ต้องการ Trie เราก็ไม่ต้องการความซับซ้อนใดๆ เราสามารถย้อนกลับไปสู่ต้นกำเนิดได้
ดังที่ได้กล่าวไว้ในต้นโพสต์นี้ว่า อุดมคติทางทฤษฎี การจัดเก็บข้อมูลสำหรับสถานะของ Ethereum นั้นเป็นการจัดเก็บคีย์-ค่าอย่างง่าย (แยกสำหรับบัญชีและแต่ละสัญญา) อย่างไรก็ตาม หากปราศจากข้อจำกัดของต้น Merkle Patricia ก็ “ไม่มีอะไร” ที่จะหยุดยั้งเราไม่ให้นำโซลูชันในอุดมคติไปใช้จริง!
สักพักหนึ่ง Geth ได้แนะนำมัน สแนปชอต โครงสร้างการเร่งความเร็ว (ไม่ได้เปิดใช้งานตามค่าเริ่มต้น) สแน็ปช็อตคือมุมมองที่สมบูรณ์ของสถานะ Ethereum ในบล็อกที่กำหนด การใช้งานเชิงนามธรรมอย่างชาญฉลาด เป็นการดัมพ์ของบัญชีและช่องจัดเก็บข้อมูลทั้งหมด ซึ่งแสดงโดยที่เก็บคีย์-ค่าแบบคงที่
เมื่อใดก็ตามที่เราต้องการเข้าถึงบัญชีหรือช่องจัดเก็บข้อมูล เราจะจ่ายเพียง 1 การค้นหา LevelDB แทนที่จะเป็น 7-8 ตามการทดลอง การอัปเดตสแน็ปช็อตก็ทำได้ง่ายในทางทฤษฎี หลังจากประมวลผลบล็อกแล้ว เราจะเขียน LevelDB เพิ่มเติม 1 รายการต่อช่องที่อัปเดต
สแน็ปช็อตจะลดการอ่านจาก O(log n) เป็น O(1) (คูณด้วยโอเวอร์เฮดของ LevelDB) โดยมีค่าใช้จ่ายในการเพิ่มการเขียนจาก O(log n) เป็น O(1 + log n) (คูณด้วยโอเวอร์เฮดของ LevelDB) และเพิ่มพื้นที่จัดเก็บดิสก์ จาก O(n log n) ถึง O(n + n log n)
ปีศาจอยู่ในรายละเอียด
การรักษาภาพรวมที่ใช้งานได้ของสถานะ Ethereum นั้นมีความซับซ้อน ตราบใดที่บล็อกมาต่อกัน สร้างต่อจากบล็อกสุดท้ายเสมอ แนวทางที่ไร้เดียงสาในการรวมการเปลี่ยนแปลงเข้ากับสแน็ปช็อตก็ใช้งานได้ อย่างไรก็ตาม หากมีการสร้างองค์กรขนาดเล็ก (แม้แต่บล็อกเดียว) เราก็ประสบปัญหาแล้ว เนื่องจากไม่มีการเลิกทำ การเขียนแบบถาวรเป็นการดำเนินการทางเดียวสำหรับการแสดงข้อมูลแบบเรียบ ที่แย่กว่านั้นคือ การเข้าถึงสถานะเก่า (เช่น 3 บล็อกเก่าสำหรับ DApp บางตัว หรือ 64+ สำหรับการซิงค์แบบเร็ว/สแนป) เป็นไปไม่ได้
เพื่อเอาชนะข้อจำกัดนี้ สแน็ปช็อตของ Geth ประกอบด้วยสองเอนทิตี: เลเยอร์ดิสก์ถาวรที่เป็นสแน็ปช็อตที่สมบูรณ์ของบล็อกเก่า (เช่น HEAD-128); และแผนผังของชั้นต่าง ๆ ในหน่วยความจำที่รวบรวมการเขียนไว้ด้านบน
เมื่อใดก็ตามที่มีการประมวลผลบล็อกใหม่ เราจะไม่รวมการเขียนเข้ากับเลเยอร์ดิสก์โดยตรง แต่เพียงสร้างเลเยอร์ diff ในหน่วยความจำใหม่พร้อมกับการเปลี่ยนแปลง หากเลเยอร์ต่างในหน่วยความจำซ้อนทับกันเพียงพอที่ด้านบน เลเยอร์ด้านล่างจะเริ่มรวมเข้าด้วยกันและผลักไปที่ดิสก์ในที่สุด เมื่อใดก็ตามที่ต้องอ่าน state merchandise เราจะเริ่มต้นที่เลเยอร์ diff บนสุดและย้อนกลับไปเรื่อย ๆ จนกว่าเราจะพบมันหรือไปถึงดิสก์
การแสดงข้อมูลนี้มีประสิทธิภาพมากเนื่องจากสามารถแก้ไขปัญหาได้มากมาย เนื่องจากเลเยอร์ diff ในหน่วยความจำถูกประกอบเข้าด้วยกันเป็นแผนผัง การปรับโครงสร้างที่ตื้นกว่า 128 บล็อกจึงสามารถเลือกเลเยอร์ diff ที่เป็นของบล็อกหลักและสร้างต่อจากที่นั่นได้ DApps และตัวซิงค์ระยะไกลที่ต้องการสถานะเก่าสามารถเข้าถึง 128 รายการล่าสุด ค่าใช้จ่ายเพิ่มขึ้น 128 การค้นหาแผนที่ แต่การค้นหาในหน่วยความจำ 128 รายการเป็นลำดับความสำคัญที่เร็วกว่าการอ่านดิสก์ 8 รายการ ขยาย 4x-5x โดย LevelDB
แน่นอนว่ายังมี gotchas และคำเตือนอีกมากมาย โดยไม่ต้องลงรายละเอียดมากเกินไป รายการสั้นๆ ของประเด็นปลีกย่อยคือ:
- การทำลายตนเอง (และการลบออก) เป็นสัตว์พิเศษเนื่องจากจำเป็นต้องลัดวงจรการสืบเชื้อสายของชั้นต่าง ๆ
- หากมีการจัดระเบียบใหม่ลึกกว่าเลเยอร์ดิสก์ถาวร สแน็ปช็อตจะต้องถูกละทิ้งและสร้างใหม่ทั้งหมด นี้มีราคาแพงมาก
- เมื่อปิดระบบ เลเยอร์ต่างในหน่วยความจำจะต้องคงอยู่ในเจอร์นัลและโหลดสำรองข้อมูล มิฉะนั้นสแน็ปช็อตจะไม่มีประโยชน์เมื่อรีสตาร์ท
- ใช้เลเยอร์ diff ล่างสุดเป็นตัวสะสมและล้างลงดิสก์เมื่อเกินการใช้งานหน่วยความจำบางส่วนเท่านั้น ซึ่งช่วยให้สามารถขจัดข้อมูลซ้ำซ้อนของการเขียนสำหรับช่องเดียวกันข้ามบล็อกได้
- จัดสรรแคชการอ่านสำหรับเลเยอร์ดิสก์ เพื่อให้สัญญาในการเข้าถึงสล็อตโบราณเดียวกันซ้ำไปซ้ำมาไม่ทำให้เกิดการเข้าถึงดิสก์
- ใช้ตัวกรองบลูมสะสมในเลเยอร์ต่างในหน่วยความจำเพื่อตรวจจับอย่างรวดเร็วว่ามีโอกาสที่รายการจะอยู่ในความแตกต่างหรือไม่ หรือเราสามารถไปที่ดิสก์ได้ทันทีหรือไม่
- คีย์ไม่ใช่ข้อมูลดิบ (ที่อยู่บัญชี คีย์การจัดเก็บ) แต่เป็นแฮชของข้อมูลเหล่านี้ เพื่อให้แน่ใจว่าสแน็ปช็อตมีลำดับการวนซ้ำเหมือนกับแผนผัง Merkle Patricia
- การสร้างเลเยอร์ดิสก์ถาวรต้องใช้เวลามากกว่าหน้าต่างการตัดสำหรับสถานะที่พยายามอย่างมาก ดังนั้นแม้แต่ตัวสร้างก็ยังจำเป็นต้องติดตามลูกโซ่แบบไดนามิก
ความดีความชั่วความน่าเกลียด
โครงสร้างการเร่งความเร็วสแน็ปช็อตของ Geth ช่วยลดความซับซ้อนในการอ่านสถานะโดยประมาณตามลำดับความสำคัญ ซึ่งหมายความว่า DoS แบบอ่านจะได้รับลำดับความสำคัญที่ยากต่อการดึงออกมา และ eth_call การร้องขอจะได้รับลำดับความสำคัญเร็วขึ้น (หากไม่ใช่ CPU ที่ผูกไว้)
สแน็ปช็อตยังช่วยให้สามารถวนซ้ำสถานะบล็อกล่าสุดได้อย่างรวดเร็วอย่างเห็นได้ชัด นี่เป็นเหตุผลหลักในการสร้างสแนปช็อตจริงๆเนื่องจากอนุญาตให้มีการสร้างสิ่งใหม่ได้ สแน็ป อัลกอริธึมการซิงค์– อธิบายว่านั่นเป็นโพสต์บล็อกใหม่ทั้งหมด แต่การวัดประสิทธิภาพล่าสุดของ Rinkeby พูดได้มากมาย:
แน่นอนว่าข้อเสียย่อมมีอยู่เสมอ หลังจากการซิงค์ครั้งแรกเสร็จสมบูรณ์ จะใช้เวลาประมาณ 9-10 ชั่วโมงบน mainnet ในการสร้างสแน็ปช็อตเริ่มต้น (จะคงอยู่หลังจากนั้น) และใช้พื้นที่ดิสก์เพิ่มเติมประมาณ 15+GB
ส่วนส่วนที่น่าเกลียดล่ะ? เราใช้เวลามากกว่า 6 เดือนกว่าจะรู้สึกมั่นใจเพียงพอเกี่ยวกับสแนปชอตในการจัดส่ง แต่ถึงตอนนี้ก็ยังอยู่เบื้องหลัง –ภาพรวม ตั้งค่าสถานะและยังคงมีการปรับแต่งและขัดเกลาการใช้งานหน่วยความจำและการกู้คืนข้อขัดข้อง
โดยรวมแล้ว เราภูมิใจกับการปรับปรุงนี้มาก มันเป็นงานจำนวนมหาศาลและเป็นช็อตสำคัญในความมืดที่ต้องทำทุกอย่างและหวังว่ามันจะออกมาดี เช่นเดียวกับข้อเท็จจริงที่น่าสนุก snap sync เวอร์ชันแรก (leaf sync) เขียนขึ้นเมื่อ 2.5 ปีที่แล้ว และถูกบล็อกตั้งแต่นั้นมาเนื่องจากเราขาดการเร่งความเร็วที่จำเป็นเพื่อทำให้อิ่มตัว
บทส่งท้าย
หวังว่าคุณจะสนุกกับการโพสต์แรกของ ถามเรื่องเกทครับ– ฉันใช้เวลาประมาณสองเท่ากว่าที่ฉันตั้งใจไว้ แต่ฉันรู้สึกว่าหัวข้อนี้สมควรได้รับเวลาพิเศษ เจอกันใหม่สัปดาห์หน้า
(ปล.: ฉันจงใจไม่เชื่อมโยงเว็บไซต์ถาม/ลงคะแนนเสียงเข้ากับโพสต์นี้ เพราะฉันแน่ใจว่ามันเป็นเรื่องชั่วคราว และฉันไม่ต้องการทิ้งลิงก์เสียไว้ให้ลูกหลาน และไม่มีใครซื้อชื่อและโฮสต์สิ่งที่เป็นอันตรายใน อนาคต คุณสามารถหามันได้ ท่ามกลางโพสต์ Twitter ของฉัน–