Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> ฐานข้อมูล

กลยุทธ์ในการลดพื้นที่ที่สูญเปล่าใน MongoDB

กลยุทธ์ในการลดพื้นที่ที่สูญเปล่าใน MongoDB

Appboy เป็นแพลตฟอร์มการตลาดอัตโนมัติชั้นนำของโลกสำหรับแอพมือถือ เรารวบรวมจุดข้อมูลหลายพันล้านจุดในแต่ละเดือนโดยการติดตามสิ่งที่ผู้ใช้ทำในแอปบนอุปกรณ์เคลื่อนที่ของลูกค้าของเรา และอนุญาตให้พวกเขากำหนดเป้าหมายผู้ใช้สำหรับอีเมล การแจ้งเตือนแบบพุช และข้อความในแอปตามพฤติกรรมหรือข้อมูลประชากร MongoDB ให้พลังแก่สแต็กฐานข้อมูลส่วนใหญ่ของเรา และเราโฮสต์ชาร์ดจำนวนมากในหลายคลัสเตอร์ที่ ObjectRocket

กลยุทธ์การเพิ่มประสิทธิภาพประสิทธิภาพร่วมกันอย่างหนึ่งกับ MongoDB คือการใช้ชื่อฟิลด์แบบสั้นในเอกสาร นั่นคือแทนที่จะสร้างเอกสารที่มีลักษณะเช่นนี้…

{first_name: "Jon", last_name: "Hyman"}

…ใช้ชื่อฟิลด์ที่สั้นลงเพื่อให้เอกสารดูเหมือน…

{fn: "Jon", ln: "Hyman"}

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

นอกจากการรวบรวมข้อมูลเหตุการณ์แล้ว Appboy ยังช่วยให้ลูกค้าของเราจัดเก็บสิ่งที่เราเรียกว่า "แอตทริบิวต์ที่กำหนดเอง" สำหรับผู้ใช้แต่ละราย ตัวอย่างเช่น แอปกีฬาอาจต้องการจัดเก็บ "ผู้เล่นที่ชื่นชอบ" ของผู้ใช้ ในขณะที่แอปนิตยสารหรือหนังสือพิมพ์อาจจัดเก็บไม่ว่าลูกค้าจะเป็น "สมาชิกรายปี" หรือไม่ ที่ Appboy เรามีเอกสารสำหรับผู้ใช้ปลายทางแต่ละรายของแอปที่เราติดตาม และเราจัดเก็บแอตทริบิวต์ที่กำหนดเองเหล่านั้นไว้ข้างช่องต่างๆ เช่น ชื่อหรือนามสกุล เพื่อประหยัดพื้นที่และปรับปรุงประสิทธิภาพ เราย่อชื่อฟิลด์ของทุกสิ่งที่เราจัดเก็บไว้ในเอกสาร สำหรับเขตข้อมูลที่เราทราบล่วงหน้า (เช่น ชื่อ อีเมล เพศ ฯลฯ) เราสามารถใช้นามแฝงของเราเองได้ (เช่น "fn" หมายถึง "ชื่อ") แต่เราไม่สามารถคาดเดาชื่อของแอตทริบิวต์ที่กำหนดเองได้ ที่ลูกค้าของเราจะบันทึก หากลูกค้าตัดสินใจสร้างแอตทริบิวต์ที่กำหนดเองชื่อ "supercalifragilisticexpialidocious" เราไม่ต้องการเก็บไว้ในเอกสารทั้งหมดของพวกเขา

เพื่อแก้ปัญหานี้ เราแปลงชื่อฟิลด์แอตทริบิวต์ที่กำหนดเองโดยใช้สิ่งที่เราเรียกว่า "ที่เก็บชื่อ" อย่างมีประสิทธิภาพ เป็นเอกสารใน MongoDB ที่จับคู่ค่าต่างๆ เช่น “ผู้เล่นที่ชื่นชอบ” กับสตริงที่สั้นมากไม่ซ้ำใครคาดเดาได้ เราสามารถสร้างแผนที่นี้ได้โดยใช้ตัวดำเนินการอะตอมของ MongoDB เท่านั้น

สคีมาเอกสารที่เก็บชื่อเป็นพื้นฐานอย่างยิ่ง:มีเอกสารหนึ่งฉบับสำหรับลูกค้าแต่ละราย และเอกสารแต่ละฉบับมีฟิลด์อาร์เรย์เพียงฟิลด์เดียวที่ชื่อว่า "รายการ" แนวคิดคืออาร์เรย์จะประกอบด้วยค่าทั้งหมดสำหรับแอตทริบิวต์ที่กำหนดเองและดัชนีของสตริงที่กำหนดจะเป็นโทเค็น ดังนั้น หากเราต้องการแปล “ผู้เล่นที่ชื่นชอบ” เป็นชื่อฟิลด์สั้นๆ ที่คาดเดาได้ เราเพียงแค่ตรวจสอบ “รายการ” เพื่อดูว่ามันอยู่ที่ไหนในอาร์เรย์ หากไม่มี เราสามารถออก atomic push เพื่อเพิ่มองค์ประกอบที่ส่วนท้ายของอาร์เรย์ (db.custom_attribute_name_stores.update({_id :X รายการ:{$ne :“Favorite Player”}}, {$ push:{list:“Favorite Player”}})) โหลดเอกสารใหม่และกำหนดดัชนี ตามหลักการแล้ว เราจะใช้ $addToSet แต่ $addToSet ไม่รับประกันการสั่งซื้อ ในขณะที่ $push ถูกบันทึกไว้ให้ผนวกต่อท้ายโดยค่าเริ่มต้น

ดังนั้น ณ จุดนี้ เราสามารถแปลบางอย่างเช่น "ผู้เล่นที่ชื่นชอบ" เป็นค่าจำนวนเต็มได้ สมมติว่าค่านั้นคือ 1 จากนั้นเอกสารผู้ใช้ของเราจะมีลักษณะดังนี้:

{
  fn: "Jon", 
  ln: "Hyman",
  custom: {
    1: "LeBron James"
  }
}

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

ตอนนี้ คุณอาจกำลังคิดว่า MongoDB เตือนเกี่ยวกับเอกสารที่มีการเติบโตอย่างต่อเนื่อง และเอกสารที่เก็บชื่อของเราสามารถเติบโตได้ไม่จำกัด ในทางปฏิบัติ เราได้ขยายการใช้งานของเราเล็กน้อย เพื่อให้เราสามารถจัดเก็บเอกสารได้มากกว่าหนึ่งฉบับต่อลูกค้าหนึ่งราย ซึ่งช่วยให้เราสามารถกำหนดจำนวนองค์ประกอบอาร์เรย์ที่เราอนุญาตได้อย่างเหมาะสมก่อนที่จะสร้างเอกสารใหม่ ส่วนที่ดีที่สุดคือเรายังทำทั้งหมดนี้ได้โดยใช้ MongoDB เท่านั้น! เพื่อให้บรรลุสิ่งนี้ เราได้เพิ่มฟิลด์อื่นให้กับเอกสารแต่ละฉบับที่เรียกว่า “ค่าน้อยที่สุด” ฟิลด์ "ค่าน้อยที่สุด" แสดงจำนวนองค์ประกอบที่เพิ่มลงในเอกสารก่อนหน้าก่อนที่จะสร้างองค์ประกอบนี้ ดังนั้นหากเราเห็นเอกสารที่มี "ค่าน้อยที่สุด" 100 และ "รายการ" ของ ["ผู้ถือตั๋วฤดูกาล", "ผู้เล่นที่ชื่นชอบ"] แสดงว่าค่าโทเค็นสำหรับ "ผู้เล่นที่ชื่นชอบ" คือ 101 (เราใช้ศูนย์ -ตามการจัดทำดัชนี) ในตัวอย่างนี้ เราจะจัดเก็บ 100 ค่าในอาร์เรย์ "รายการ" เท่านั้นก่อนที่จะสร้างเอกสารใหม่ ในตอนนี้ เมื่อทำการแทรก เราจะปรับเปลี่ยนการกดเล็กน้อยเพื่อดำเนินการกับเอกสารที่มีค่า “น้อยที่สุด . สูงสุด ค่า” และตรวจสอบให้แน่ใจว่าไม่มี “list.99” (หมายความว่าไม่มีสิ่งใดในดัชนี 99 ในอาร์เรย์ “รายการ”) หากมีองค์ประกอบอยู่ที่ดัชนีนั้นแล้ว การดำเนินการพุชจะไม่ทำอะไรเลย ในกรณีนั้น เราทราบดีว่าเราจำเป็นต้องสร้างเอกสารชื่อร้านใหม่ที่มี “ค่าน้อยที่สุด” เท่ากับจำนวนองค์ประกอบทั้งหมดที่มีอยู่ในเอกสารทั้งหมด การใช้ $findAndModify แบบอะตอมมิก เราสามารถสร้างเอกสารใหม่ได้หากไม่มีอยู่ ดึงกลับแล้วลองกด $push อีกครั้ง

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

เราใช้กระบวนทัศน์ "name store token" ในส่วนต่างๆ ของแอปพลิเคชันของเราเพื่อลดขนาดชื่อฟิลด์ในขณะที่ยังคงใช้สคีมาที่ยืดหยุ่นต่อไป นอกจากนี้ยังสามารถเป็นประโยชน์สำหรับค่า สมมติว่าแอปสถานีวิทยุจัดเก็บแอตทริบิวต์ที่กำหนดเองซึ่งเป็นอาร์เรย์ของศิลปินที่มีประสิทธิภาพสูงสุด 50 อันดับแรกที่ผู้ใช้ฟัง แทนที่จะมีอาร์เรย์ที่มี 50 สตริงในนั้น เราสามารถแปลงชื่อสถานีวิทยุและจัดเก็บอาร์เรย์จำนวนเต็ม 50 ให้กับผู้ใช้แทนได้ การค้นหาผู้ใช้ที่ชอบศิลปินบางคนตอนนี้เกี่ยวข้องกับการค้นหาโทเค็นสองรายการ:รายการแรกสำหรับชื่อฟิลด์และอีกรายการสำหรับค่า แต่เนื่องจากเราแคชการแปลจากค่าเป็นโทเค็น เราจึงสามารถใช้ multi-get ในเลเยอร์แคชเพื่อคงการไปกลับแคชเพียงครั้งเดียวเมื่อแปลค่าจำนวนเท่าใดก็ได้

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

ต้องการเรียนรู้เพิ่มเติมหรือไม่? ฉันจะหารือเกี่ยวกับ devops ที่ Appboy ระหว่างการประชุม Rackspace Solve NYC ในวันที่ 18 กันยายนที่ Cipriani