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


15

นี่เป็นแอปพลิเคชั่นบันทึก / นับที่นับจำนวนแพ็กเก็ตและการนับประเภทของแพ็คเก็ต ฯลฯ ในเครือข่ายการแชท p2p ซึ่งเท่ากับประมาณ 4-6 ล้านแพ็กเก็ตในระยะเวลา 5 นาที และเนื่องจากฉันใช้เพียงแค่ "ภาพรวม" ของข้อมูลนี้ฉันจึงลบแพ็กเก็ตที่เก่ากว่า 5 นาทีทุกห้านาที ดังนั้นจำนวนสูงสุดของไอเท็มที่จะอยู่ในคอลเล็กชันนี้คือ 10 ถึง 12 ล้าน

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

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

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

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

ฉันพยายาม mongodb แต่การใช้ซีพียูสำหรับมันนั้นบ้าและไม่ได้เก็บไว้อย่างใดอย่างหนึ่ง

ปัญหาหลักของฉันเกิดขึ้นทุก 5 นาทีเพราะฉันลบแพ็กเก็ตทั้งหมดที่เก่ากว่า 5 นาทีและใช้ "ภาพรวม" ของข้อมูลนี้ เช่นฉันใช้การสืบค้น LINQ เพื่อนับจำนวนแพ็คเก็ตที่มีประเภทแพ็คเก็ตบางอย่าง ฉันยังเรียกใช้แบบสอบถาม () ที่แตกต่างกันของข้อมูลที่ฉันดึงข้อมูล 4 ไบต์ (ที่อยู่ IP) ออกจากคีย์ของ keyvaluepair และรวมเข้ากับค่าที่ร้องขอในมูลค่าของ keyvalupair และใช้เพื่อรับจำนวนที่แตกต่างกันของ เพื่อนจากทุกแพ็กเก็ต

ปัจจุบันแอปพลิเคชันวนเวียนอยู่รอบ ๆ การใช้หน่วยความจำ 1.1GB และเมื่อมีการเรียกใช้สแน็ปช็อตมันจะสามารถใช้งานได้เป็นสองเท่า

ตอนนี้จะไม่เป็นปัญหาหากฉันมี ram จำนวนบ้า แต่ vm ที่ฉันใช้งานอยู่นั้น จำกัด อยู่ที่ 2GB ของ ram ในขณะนี้

มีวิธีแก้ปัญหาง่าย ๆ ไหม?


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

ในขณะที่คุณได้ลองทั้ง MySQL และ MongoDB แล้วดูเหมือนว่าข้อกำหนดของแอปพลิเคชันของคุณ (ถ้าคุณต้องการทำอย่างถูกต้อง) กำหนดว่าคุณต้องการแรงม้าเพิ่มขึ้น หากใบสมัครของคุณมีความสำคัญต่อคุณควรเพิ่มเซิร์ฟเวอร์ คุณอาจต้องการทบทวนรหัส "การล้างข้อมูล" อีกครั้ง ฉันแน่ใจว่าคุณสามารถหาวิธีการที่เหมาะสมที่สุดในการจัดการนั้นตราบเท่าที่มันไม่ทำให้แอปของคุณใช้ไม่ได้
Matt Beckman

4
ผู้สร้างโปรไฟล์ของคุณบอกอะไรคุณ
jasonk

คุณจะไม่ได้อะไรเร็วกว่ากองในท้องถิ่น ข้อเสนอแนะของฉันคือการเรียกใช้การรวบรวมขยะด้วยตนเองหลังจากการกวาดล้าง
vartec

@vartec - ตามความเป็นจริงตรงกันข้ามกับความเชื่อที่ได้รับความนิยมการเรียกใช้ตัวรวบรวมขยะด้วยตนเองไม่ได้รับประกันความจริงทันที ... GC อาจเลื่อนการดำเนินการไปเป็นช่วงหลัง ๆ ตามอัลกอริทึมของ gc เรียกใช้ทุก 5 นาทีอาจเพิ่มความเครียดแทนการบรรเทา เพียงแค่พูด;)
Jas

คำตอบ:


12

แทนที่จะมีหนึ่งพจนานุกรมและค้นหาพจนานุกรมนั้นสำหรับรายการที่เก่าเกินไป มี 10 พจนานุกรม ทุกๆ 30 วินาทีหรือมากกว่านั้นจะสร้างพจนานุกรม "ปัจจุบัน" ใหม่และทิ้งพจนานุกรมที่เก่าที่สุดโดยไม่ต้องค้นหาเลย

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


1
แบ่งพาร์ติชันตามเวลา! สิ่งที่ฉันอยากจะแนะนำ
James Anderson

ปัญหาของเรื่องนี้คือฉันจะต้องค้นหาพจนานุกรมทั้งหมดที่เกิดขึ้นภายในห้านาทีที่ผ่านมา เนื่องจากมีการเชื่อมต่อ 300 การเชื่อมต่อแพ็คเก็ตเดียวกันจะมาถึงที่แต่ละคนอย่างน้อยหนึ่งครั้ง ดังนั้นเพื่อที่จะไม่จัดการแพ็คเก็ตเดียวกันมากกว่าหนึ่งครั้งฉันต้องเก็บพวกเขาเป็นเวลาอย่างน้อย 5 นาที
Josh

1
ส่วนหนึ่งของปัญหากับโครงสร้างทั่วไปคือมันไม่ได้ถูกปรับแต่งเพื่อวัตถุประสงค์เฉพาะ บางทีคุณควรเพิ่มฟิลด์ "nextItemForHash" และฟิลด์ "nextItemForTimeBucket" ให้กับโครงสร้าง Packet ของคุณและใช้ตารางแฮชของคุณเองและหยุดใช้พจนานุกรม ด้วยวิธีนี้คุณสามารถค้นหาแพ็คเก็ตทั้งหมดที่เก่าเกินไปและค้นหาได้เพียงครั้งเดียวเมื่อใส่แพ็กเก็ต (เช่นมีเค้กของคุณและกินด้วย) นอกจากนี้ยังช่วยค่าใช้จ่ายในการจัดการหน่วยความจำ (เนื่องจาก "พจนานุกรม" จะไม่จัดสรร / เพิ่มโครงสร้างข้อมูลเพิ่มเติมสำหรับการจัดการพจนานุกรม)
เบรนแดน

@ Josh วิธีที่เร็วที่สุดในการพิจารณาว่าคุณเคยเห็นบางสิ่งมาก่อนหน้านี้เป็นแฮชเซ็ตหรือไม่ ชุดแฮชแบบแบ่งเวลาจะรวดเร็วและคุณยังไม่จำเป็นต้องค้นหาเพื่อขับไล่ไอเท็มเก่า ๆ หากคุณไม่เคยเห็นมาก่อนคุณสามารถเก็บไว้ในพจนานุกรมของคุณ (y / ies)
พื้นฐาน


3

ความคิดแรกที่คิดขึ้นมาคือเหตุผลที่คุณรอ 5 นาที คุณช่วยถ่ายภาพสแนปชอตบ่อยขึ้นและลดภาระงานใหญ่ที่คุณเห็นในขอบเขต 5 นาทีได้หรือไม่?

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

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


1
อย่าใช้สตริง / ไบต์อาร์เรย์ใช้สิ่งที่คล้ายกับ BitArray: msdn.microsoft.com/en-us/library/…เพื่อหลีกเลี่ยงการต้องใช้ bit-twiddle ด้วยตนเอง มิฉะนั้นนี่เป็นคำตอบที่ดีไม่มีตัวเลือกง่าย ๆ นอกเหนือจากอัลกอริธึมที่ดีกว่าฮาร์ดแวร์หรือฮาร์ดแวร์ที่ดีกว่า
Ed James

1
สิ่งที่ห้านาทีเนื่องจากการเชื่อมต่อ 300 เหล่านี้อาจได้รับแพคเก็ตเดียวกัน ดังนั้นฉันต้องติดตามสิ่งที่ฉันจัดการไปแล้วและ 5 นาทีคือระยะเวลาที่แพ็กเก็ตใช้ในการแพร่กระจายไปยังโหนดทั้งหมดในเครือข่ายนี้โดยเฉพาะ
Josh

3

วิธีง่ายๆลองmemcached

  • มันได้รับการปรับให้เหมาะกับการทำงานแบบนี้
  • มันสามารถใช้หน่วยความจำสำรองในกล่องที่ไม่ว่างไม่เพียง แต่ในกล่องเฉพาะของคุณ
  • มันมีกลไกการหมดอายุแคชในตัวซึ่งขี้เกียจไม่มีอาการสะอึก

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

วิธีการที่ซับซ้อนมากขึ้นพยายามRedis

  • มันได้รับการปรับให้เหมาะกับการทำงานแบบนี้
  • มันมีกลไกการหมดอายุแคชในตัว
  • มันชั่ง / เศษได้อย่างง่ายดาย
  • มันมีความเพียร

ข้อเสียคือมันซับซ้อนกว่าเล็กน้อย


1
Memcached สามารถแบ่งข้ามเครื่องเพื่อเพิ่มจำนวนหน่วยความจำที่มีอยู่ คุณอาจมีเซิร์ฟเวอร์ที่สองเป็นอันดับข้อมูลไปยังระบบไฟล์เพื่อที่คุณจะไม่สูญเสียสิ่งใดหากกล่อง memcache หยุดทำงาน Memcache API นั้นใช้งานง่ายมากและทำงานได้จากภาษาใด ๆ ที่อนุญาตให้คุณใช้สแต็คที่แตกต่างกันในที่ต่างๆ
Michael Shopsin

1

คุณไม่จำเป็นต้องจัดเก็บแพ็คเกจทั้งหมดสำหรับการสืบค้นที่คุณกล่าวถึง ตัวอย่างเช่น - ตัวนับประเภทแพ็คเกจ:

คุณต้องมีสองอาร์เรย์

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

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

ดังนั้นสำหรับแต่ละแพ็คเกจจะมีการดำเนินการดังต่อไปนี้:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

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


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

1

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

ใช้ struct แทนที่จะเป็นคลาสสำหรับข้อมูลของคุณ (แต่จำไว้ว่ามันถือเป็นค่าที่มีความหมายแบบ pass-by-copy) นี่จะเป็นการค้นหาระดับหนึ่งที่ gc ต้องทำผ่านเครื่องหมาย

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

การค้นหารายการโครงสร้างอาจเร็วกว่าการค้นหาพจนานุกรม (ขึ้นอยู่กับสิ่งที่คุณทำ) ขึ้นอยู่กับสิ่งที่คุณทำ

การรวมกันของ struct & list จะลดทั้งการใช้หน่วยความจำและขนาดของตัวรวบรวมขยะอย่างมาก


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