Metode yang efisien untuk menyimpan puluhan juta objek untuk query, dengan jumlah insert yang tinggi per detik?

15

Ini pada dasarnya adalah aplikasi pencatatan / penghitungan yang menghitung jumlah paket dan menghitung jenis paket, dll. Pada jaringan obrolan P2P. Ini setara dengan sekitar 4-6 juta paket dalam periode 5 menit. Dan karena saya hanya mengambil "snapshot" dari informasi ini, saya hanya menghapus paket yang lebih lama dari 5 menit setiap lima menit. Jadi maksimum item yang akan ada dalam koleksi ini adalah 10 hingga 12 juta.

Karena saya perlu membuat 300 koneksi ke superpeers yang berbeda, ada kemungkinan setiap paket mencoba untuk dimasukkan setidaknya 300 kali (yang mungkin mengapa menyimpan data ini dalam memori adalah satu-satunya pilihan yang masuk akal).

Saat ini, saya telah menggunakan Kamus untuk menyimpan informasi ini. Tetapi karena sejumlah besar item yang saya coba simpan, saya mengalami masalah dengan tumpukan objek besar dan jumlah penggunaan memori terus bertambah seiring waktu.

Dictionary<ulong, Packet>

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

Saya telah mencoba menggunakan mysql, tetapi tidak dapat mengikuti jumlah data yang perlu saya masukkan (sambil memeriksa untuk memastikan itu bukan duplikat), dan itu saat menggunakan transaksi.

Saya mencoba mongodb, tetapi penggunaan cpu untuk itu gila dan tidak menjaga keduanya.

Masalah utama saya muncul setiap 5 menit, karena saya menghapus semua paket yang lebih dari 5 menit, dan mengambil "snapshot" dari data ini. Karena saya menggunakan query LINQ untuk menghitung jumlah paket yang berisi jenis paket tertentu. Saya juga memanggil kueri () kueri yang berbeda pada data, di mana saya menghapus 4 byte (alamat ip) dari kunci keyvaluepair, dan menggabungkannya dengan nilai port requesting dalam Nilai keyvalupair dan menggunakannya untuk mendapatkan jumlah yang berbeda dari rekan dari semua paket.

Aplikasi saat ini melayang-layang sekitar 1,1GB penggunaan memori, dan ketika sebuah snapshot disebut itu bisa sejauh menggandakan penggunaan.

Sekarang ini tidak akan menjadi masalah jika saya memiliki jumlah ram yang gila, tetapi vm yang saya jalankan terbatas pada 2GB ram saat ini.

Apakah ada solusi mudah?

Josh
sumber
Skenario ini sangat intensif memori dan di atas semua itu Anda menggunakan vm untuk menjalankan aplikasi, wow. Ngomong-ngomong, apakah Anda menjelajahi memcached untuk menyimpan paket. Pada dasarnya Anda dapat menjalankan memcached pada mesin yang terpisah dan aplikasi dapat terus berjalan di vm itu sendiri.
Karena Anda sudah mencoba MySQL dan MongoDB, sepertinya persyaratan aplikasi Anda (jika Anda ingin melakukannya dengan benar) menentukan bahwa Anda hanya perlu lebih banyak tenaga kuda. Jika aplikasi Anda penting bagi Anda, siapkan server. Anda juga mungkin ingin mengunjungi kembali kode "purging" Anda. Saya yakin Anda dapat menemukan cara penanganan yang lebih optimal, sejauh itu tidak membuat aplikasi Anda tidak dapat digunakan.
Matt Beckman
4
Apa yang dikatakan oleh profiler Anda?
jasonk
Anda tidak akan mendapatkan apa pun lebih cepat dari tumpukan lokal. Saran saya adalah memohon pengumpulan sampah secara manual setelah dibersihkan.
vartec
@ vartec - sebenarnya, bertentangan dengan kepercayaan populer, pemulung secara manual tidak benar-benar menjamin pengumpulan sampah dengan segera. GC mungkin menunda tindakan ke periode selanjutnya sesuai dengan algoritma gc sendiri. Menggunakannya setiap 5 menit bahkan mungkin menambah ketegangan, alih-alih menghilangkannya. Hanya mengatakan;)
Jas

Jawaban:

12

Alih-alih memiliki satu kamus dan mencari kamus itu untuk entri yang terlalu lama; punya 10 kamus. Setiap 30 detik atau lebih, buat kamus "saat ini" baru dan buang kamus tertua tanpa pencarian sama sekali.

Selanjutnya, ketika Anda membuang kamus tertua, letakkan semua benda lama ke dalam antrian FILO untuk nanti, dan alih-alih menggunakan "baru" untuk membuat objek baru tarik objek lama dari antrian FILO dan gunakan metode untuk merekonstruksi yang lama objek (kecuali antrian objek lama kosong). Ini dapat menghindari banyak alokasi dan banyak overhead pengumpulan sampah.

Brendan
sumber
1
Partisi berdasarkan irisan waktu! Apa yang akan saya sarankan.
James Anderson
Masalahnya adalah, saya harus memeriksa semua kamus yang dibuat dalam lima menit terakhir. Karena ada 300 koneksi, paket yang sama akan tiba di masing-masing setidaknya satu kali. Jadi untuk tidak menangani paket yang sama lebih dari sekali, saya harus menyimpannya setidaknya selama 5 menit.
Josh
1
Bagian dari masalah dengan struktur generik adalah bahwa mereka tidak dikustomisasi untuk tujuan tertentu. Mungkin Anda harus menambahkan bidang "nextItemForHash" dan bidang "nextItemForTimeBucket" ke struktur Paket Anda dan mengimplementasikan tabel hash Anda sendiri, dan berhenti menggunakan Kamus. Dengan begitu Anda dapat dengan cepat menemukan semua paket yang terlalu tua dan hanya mencari sekali ketika sebuah paket dimasukkan (mis. Ambil kue Anda dan makan juga). Ini juga akan membantu untuk overhead manajemen memori (karena "Kamus" tidak akan mengalokasikan / membebaskan struktur data tambahan untuk manajemen Kamus).
Brendan
@Josh cara tercepat untuk menentukan apakah Anda pernah melihat sesuatu sebelumnya adalah hashset . Kumpulan hash yang diiris waktu akan cepat dan Anda masih tidak perlu mencari untuk mengusir item lama. Jika Anda belum pernah melihatnya, maka Anda dapat menyimpannya di kamus Anda (y / ies).
Dasar
3

Pikiran pertama yang muncul dalam pikiran adalah mengapa Anda menunggu 5 menit. Bisakah Anda melakukan snap-shot lebih sering dan dengan demikian mengurangi kelebihan besar yang Anda lihat pada batas 5 menit?

Kedua, LINQ bagus untuk kode ringkas, tetapi dalam kenyataannya LINQ adalah gula sintaksis pada "biasa" C # dan tidak ada jaminan bahwa itu akan menghasilkan kode yang paling optimal. Sebagai latihan Anda bisa mencoba dan menulis ulang hot spot dengan LINQ, Anda mungkin tidak meningkatkan kinerja tetapi Anda akan memiliki ide yang lebih jelas tentang apa yang Anda lakukan dan itu akan membuat pembuatan profil bekerja lebih mudah.

Hal lain yang harus dilihat adalah struktur data. Saya tidak tahu apa yang Anda lakukan dengan data Anda, tetapi bisakah Anda menyederhanakan data yang Anda simpan dengan cara apa pun? Bisakah Anda menggunakan array string atau byte dan kemudian mengekstrak bagian-bagian yang relevan dari item-item itu saat Anda membutuhkannya? Bisakah Anda menggunakan struct bukan kelas dan bahkan melakukan sesuatu yang jahat dengan stackalloc untuk menyisihkan memori dan menghindari GC berjalan?

Steve
sumber
1
Jangan gunakan array string / byte, gunakan sesuatu seperti BitArray: msdn.microsoft.com/en-us/library/… untuk menghindari keharusan menggigit-twiddle secara manual. Kalau tidak, ini adalah jawaban yang baik, tidak ada pilihan yang mudah selain algoritma yang lebih baik, lebih banyak perangkat keras atau perangkat keras yang lebih baik.
Ed James
1
Hal lima menit adalah karena fakta bahwa 300 koneksi ini dapat menerima paket yang sama. Jadi saya harus melacak apa yang sudah saya tangani, dan 5 menit adalah jumlah waktu yang diperlukan untuk paket untuk sepenuhnya menyebar ke semua node di jaringan khusus ini.
Josh
3

Pendekatan sederhana: coba memcached .

  • Itu dioptimalkan untuk menjalankan tugas-tugas seperti ini.
  • Itu dapat menggunakan kembali memori cadangan pada kotak yang kurang sibuk, tidak hanya pada kotak khusus Anda.
  • Ini memiliki mekanisme kedaluwarsa cache bawaan, yang malas jadi tidak ada masalah.

The downside adalah bahwa itu berbasis memori dan tidak memiliki kegigihan. Jika sebuah instance turun, data hilang. Jika Anda membutuhkan kegigihan, buat serialisasi data sendiri.

Pendekatan yang lebih kompleks: coba Redis .

The downside adalah bahwa itu sedikit lebih kompleks.

9000
sumber
1
Memcached dapat dibagi di seluruh mesin untuk meningkatkan jumlah ram yang tersedia. Anda bisa memiliki server kedua yang mengelompokkan data ke sistem file sehingga Anda tidak akan kehilangan banyak hal jika kotak memcache turun. API Memcache sangat mudah digunakan dan berfungsi dari bahasa apa pun yang memungkinkan Anda menggunakan tumpukan berbeda di tempat yang berbeda.
Michael Shopsin
1

Anda tidak harus menyimpan semua paket untuk pertanyaan yang telah Anda sebutkan. Misalnya - penghitung jenis paket:

Anda membutuhkan dua array:

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

Array pertama melacak berapa banyak paket dalam tipe yang berbeda. Array kedua melacak berapa banyak lagi paket yang ditambahkan dalam setiap menit sehingga Anda tahu berapa banyak paket yang perlu dihapus pada setiap interval menit. Saya harap Anda bisa mengatakan bahwa array kedua digunakan sebagai antrian FIFO bulat.

Jadi untuk setiap paket, operasi berikut dilakukan:

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;
}

Kapan saja, penghitung paket dapat diambil oleh indeks secara instan dan kami tidak menyimpan semua paket.

Codism
sumber
Alasan utama karena harus menyimpan data yang saya lakukan, adalah kenyataan bahwa 300 koneksi ini dapat menerima paket yang sama persis. Jadi saya harus menyimpan setiap paket yang terlihat setidaknya selama lima menit untuk memastikan saya tidak menangani / menghitungnya lebih dari sekali. Untuk itulah gunanya kunci kamus.
Josh
1

(Saya tahu ini adalah pertanyaan lama, tetapi saya berlari melewatinya sambil mencari solusi untuk masalah serupa di mana pass pengumpulan sampah gen kedua menghentikan aplikasi selama beberapa detik, jadi merekam untuk orang lain dalam situasi yang sama).

Gunakan struct daripada kelas untuk data Anda (tapi ingat itu diperlakukan sebagai nilai dengan semantik pass-by-copy). Ini mengambil satu tingkat pencarian gc harus melakukan setiap tanda lulus.

Gunakan array (jika Anda tahu ukuran data yang Anda simpan) atau Daftar - yang menggunakan array secara internal. Jika Anda benar-benar membutuhkan akses acak cepat, gunakan kamus indeks array. Ini menghilangkan beberapa level (atau selusin atau lebih jika Anda menggunakan SortedDictionary) agar gc harus mencari.

Bergantung pada apa yang Anda lakukan, mencari daftar struct mungkin lebih cepat daripada pencarian kamus (karena lokalisasi memori) - profil untuk aplikasi khusus Anda.

Kombinasi struct & list mengurangi penggunaan memori dan ukuran penyapu sampah secara signifikan.

Malcolm
sumber
Saya memiliki percobaan baru-baru ini, yang menghasilkan koleksi & kamus dalam disk secepat, menggunakan sqlite github.com/modma/PersistenceCollections
ModMa