Kami punya situasi di mana saya harus berurusan dengan gelombang besar peristiwa yang datang ke server kami, sekitar 1000 peristiwa per detik, rata-rata (puncaknya bisa ~ 2000).
Masalah
Sistem kami dihosting di Heroku dan menggunakan Heroku Postgres DB yang relatif mahal , yang memungkinkan maksimum 500 DB koneksi. Kami menggunakan pooling koneksi untuk terhubung dari server ke DB.
Acara datang lebih cepat daripada kelompok koneksi DB yang bisa menangani
Masalah yang kami miliki adalah bahwa acara datang lebih cepat daripada yang bisa ditangani oleh kumpulan koneksi. Pada saat satu koneksi telah menyelesaikan jaringan bolak-balik dari server ke DB, sehingga dapat dilepaskan kembali ke kolam, lebih dari n
peristiwa tambahan yang masuk.
Akhirnya acara menumpuk, menunggu untuk diselamatkan dan karena tidak ada koneksi yang tersedia di kolam, mereka kehabisan waktu dan seluruh sistem dibuat non-operasional.
Kami telah memecahkan keadaan darurat dengan memancarkan peristiwa frekuensi tinggi yang menyinggung dengan kecepatan lebih lambat dari klien, tetapi kami masih ingin tahu bagaimana menangani skenario ini jika kami perlu menangani peristiwa frekuensi tinggi itu.
Kendala
Klien lain mungkin ingin membaca acara secara bersamaan
Klien lain terus menerus meminta untuk membaca semua acara dengan kunci tertentu, bahkan jika mereka belum disimpan dalam DB.
Klien dapat meminta GET api/v1/events?clientId=1
dan mendapatkan semua acara yang dikirim oleh klien 1, bahkan jika acara tersebut belum selesai menyimpan dalam DB dulu.
Apakah ada contoh "ruang kelas" tentang cara menangani ini?
Solusi yang memungkinkan
Enqueue acara di server kami
Kami dapat melakukan enqueue acara di server (dengan antrian memiliki konkurensi maksimum 400 sehingga kumpulan koneksi tidak habis).
Ini ide yang buruk karena:
- Ini akan memakan memori server yang tersedia. Acara enqueued yang ditumpuk akan mengkonsumsi RAM dalam jumlah besar.
- Server kami restart sekali setiap 24 jam . Ini adalah batasan keras yang dikenakan oleh Heroku. Server dapat memulai kembali sementara acara enqueued menyebabkan kita kehilangan acara enqueued.
- Ini memperkenalkan status di server, sehingga merusak skalabilitas. Jika kami memiliki pengaturan multi-server dan klien ingin membaca semua peristiwa yang diselamatkan + enqueued, kami tidak akan tahu di server mana acara enqueued hidup.
Gunakan antrian pesan terpisah
Saya berasumsi kita bisa menggunakan antrian pesan, (seperti RabbitMQ ?), Di mana kita memompa pesan di dalamnya dan di ujung lain ada server lain yang hanya berurusan dengan menyimpan acara di DB.
Saya tidak yakin apakah antrian pesan memungkinkan kueri acara enqueued (yang belum disimpan) jadi jika klien lain ingin membaca pesan klien lain, saya bisa mendapatkan pesan yang disimpan dari DB dan pesan yang tertunda dari antrian dan menyatukannya sehingga saya dapat mengirimnya kembali ke klien baca-permintaan.
Gunakan beberapa basis data, masing-masing menyimpan sebagian pesan dengan server pusat koordinator DB untuk mengelolanya
Solusi lain yang kami miliki adalah menggunakan banyak basis data, dengan pusat "koordinator DB / penyeimbang beban". Setelah menerima suatu acara, koordinator ini akan memilih salah satu database untuk menulis pesan. Ini seharusnya memungkinkan kita untuk menggunakan banyak basis data Heroku sehingga menaikkan batas koneksi ke 500 x jumlah basis data.
Setelah kueri baca, koordinator ini dapat mengeluarkan SELECT
kueri ke setiap basis data, menggabungkan semua hasil dan mengirimkannya kembali ke klien yang meminta baca.
Ini ide yang buruk karena:
- Gagasan ini terdengar seperti ... ahem .. over-engineering? Akan menjadi mimpi buruk untuk dikelola juga (cadangan dll.). Ini rumit untuk dibangun dan dipelihara dan kecuali jika benar-benar diperlukan itu terdengar seperti pelanggaran KISS .
- Itu mengorbankan Konsistensi . Melakukan transaksi lintas banyak DB tidak perlu dilakukan jika kita menggunakan ide ini.
sumber
ANALYZE
query sendiri dan mereka tidak masalah. Saya juga telah membangun prototipe untuk menguji hipotesis kumpulan koneksi dan memverifikasi bahwa ini memang masalahnya. Basis data dan server itu sendiri hidup pada mesin yang berbeda maka latensi. Juga, kami tidak ingin melepaskan Heroku kecuali benar-benar diperlukan, tidak khawatir tentang penyebaran adalah nilai tambah yang besar bagi kami.select null
pada 500 koneksi. Saya yakin Anda akan menemukan bahwa kolam koneksi bukan masalah di sana.Jawaban:
Input stream
Tidak jelas apakah 1000 acara / detik Anda mewakili puncak atau jika ini adalah beban berkelanjutan:
Solusi yang diajukan
Secara intuitif, dalam kedua kasus ini, saya akan memilih aliran acara berbasis Kafka :
Ini sangat scalable di semua tingkatan:
Menawarkan acara yang belum ditulis dalam DB kepada klien
Anda ingin klien Anda bisa mendapatkan akses juga ke informasi yang masih dalam pipa dan belum ditulis ke DB. Ini sedikit lebih rumit.
Opsi 1: Menggunakan cache untuk melengkapi permintaan db
Saya belum menganalisis secara mendalam, tetapi gagasan pertama yang muncul di benak saya adalah membuat pemroses kueri menjadi konsumen topik kafka, tetapi dalam kelompok konsumen kafka yang berbeda . Pemroses permintaan kemudian akan menerima semua pesan yang akan diterima oleh penulis DB, tetapi secara independen. Itu kemudian bisa menyimpannya dalam cache lokal. Kueri kemudian akan berjalan pada DB + cache (+ penghapusan duplikat).
Desainnya kemudian akan terlihat seperti:
Skalabilitas lapisan kueri ini dapat dicapai dengan menambahkan lebih banyak prosesor kueri (masing-masing dalam kelompok konsumennya sendiri).
Opsi 2: desain API ganda
Pendekatan IMHO yang lebih baik adalah menawarkan API ganda (gunakan mekanisme kelompok konsumen terpisah):
Keuntungannya, Anda membiarkan klien memutuskan apa yang menarik. Ini dapat menghindari bahwa Anda secara sistematis menggabungkan data DB dengan data yang baru diuangkan, ketika klien hanya tertarik pada peristiwa baru yang masuk. Jika penggabungan halus antara acara baru dan yang diarsipkan benar-benar diperlukan, maka klien harus mengaturnya.
Varian
Saya mengusulkan kafka karena dirancang untuk volume yang sangat tinggi dengan pesan persisten sehingga Anda dapat me-restart server jika diperlukan.
Anda bisa membangun arsitektur yang sama dengan RabbitMQ. Namun jika Anda perlu antrian terus-menerus, ini dapat menurunkan kinerja . Juga, sejauh yang saya tahu, satu-satunya cara untuk mencapai konsumsi paralel dari pesan yang sama oleh beberapa pembaca (misalnya penulis + cache) dengan RabbitMQ adalah dengan mengkloning antrian . Jadi skalabilitas yang lebih tinggi mungkin datang dengan harga yang lebih tinggi.
sumber
a distributed database (for example using a specialization of the server by group of keys)
? Juga mengapa Kafka bukan RabbitMQ? Apakah ada alasan tertentu untuk memilih satu dari yang lain?Use multiple databases
ide saya tetapi Anda mengatakan saya tidak boleh hanya secara acak (atau round-robin) mendistribusikan pesan ke setiap database. Baik?Dugaan saya adalah bahwa Anda perlu mengeksplorasi lebih hati-hati suatu pendekatan yang telah Anda tolak
Saran saya adalah mulai membaca berbagai artikel yang diterbitkan tentang arsitektur LMAX . Mereka berhasil membuat batching volume tinggi berfungsi untuk kasus penggunaan mereka, dan dimungkinkan untuk membuat trade off Anda terlihat lebih seperti milik mereka.
Juga, Anda mungkin ingin melihat apakah Anda bisa mendapatkan bacaan keluar dari jalan - idealnya Anda ingin dapat skala mereka secara independen dari tulisan. Itu mungkin berarti melihat ke CQRS (perintah segregasi tanggung jawab permintaan).
Dalam sistem terdistribusi, saya pikir Anda bisa cukup yakin bahwa pesan akan hilang. Anda mungkin dapat mengurangi beberapa dampak dari itu dengan bersikap bijaksana tentang hambatan urutan Anda (misalnya - memastikan bahwa penulisan ke penyimpanan tahan lama terjadi - sebelum acara dibagikan di luar sistem).
Mungkin - saya akan lebih cenderung melihat batas bisnis Anda untuk melihat apakah ada tempat alami untuk membuang data.
Yah, saya kira mungkin ada, tapi itu bukan tujuan saya. Intinya adalah bahwa desain harus dibangun ke dalamnya kekuatan yang dibutuhkan untuk maju dalam menghadapi kehilangan pesan.
Apa ini sering terlihat adalah model berbasis tarik dengan pemberitahuan. Penyedia menulis pesan ke toko tahan lama yang dipesan. Konsumen menarik pesan dari toko, melacak tanda airnya sendiri. Notifikasi push digunakan sebagai perangkat pengurang latensi - tetapi jika notifikasi hilang, pesan masih diambil (akhirnya) karena konsumen menarik jadwal reguler (perbedaannya adalah jika notifikasi diterima, tarikan terjadi lebih cepat ).
Lihat Perpesanan yang Andal Tanpa Transaksi Terdistribusi, oleh Udi Dahan (sudah dirujuk oleh Andy ) dan Data Polyglot oleh Greg Young.
sumber
In a distributed system, I think you can be pretty confident that messages are going to get lost
. Betulkah? Ada kasus di mana kehilangan data merupakan tradeoff yang dapat diterima? Saya mendapat kesan bahwa kehilangan data = kegagalan.Jika saya mengerti benar aliran saat ini adalah:
Jika demikian saya pikir perubahan pertama pada desain akan berhenti memiliki Anda bahkan menangani koneksi kode kembali ke kolam pada setiap acara. Alih-alih membuat kumpulan thread / proses penyisipan yang 1-ke-1 dengan jumlah koneksi DB. Ini masing-masing akan memegang koneksi DB khusus.
Menggunakan semacam antrian konkuren, Anda kemudian memiliki utas ini menarik pesan dari antrian konkuren dan menyisipkannya. Secara teori mereka tidak perlu mengembalikan koneksi ke pool atau meminta yang baru tetapi Anda mungkin perlu membangun dalam penanganan jika koneksi memburuk. Mungkin paling mudah untuk membunuh utas / proses dan memulai yang baru.
Ini harus secara efektif menghilangkan overhead koneksi pool. Anda tentu saja harus mampu melakukan push setidaknya 1000 / koneksi acara per detik pada setiap koneksi. Anda mungkin ingin mencoba jumlah koneksi yang berbeda karena memiliki 500 koneksi yang bekerja pada tabel yang sama dapat membuat pertentangan pada DB tetapi itu pertanyaan yang sama sekali berbeda. Hal lain yang perlu dipertimbangkan adalah penggunaan sisipan batch yaitu setiap utas menarik sejumlah pesan dan mendorongnya sekaligus. Selain itu, hindari memiliki beberapa koneksi yang berusaha memperbarui baris yang sama.
sumber
Asumsi
Saya akan menganggap bahwa beban yang Anda gambarkan adalah konstan, karena itu adalah skenario yang lebih sulit untuk dipecahkan.
Saya juga akan menganggap Anda memiliki beberapa cara untuk menjalankan beban kerja yang terpicu dan lama di luar proses aplikasi web Anda.
Larutan
Dengan asumsi bahwa Anda telah mengidentifikasi dengan benar kemacetan Anda - latensi antara proses Anda dan database Postgres - itulah masalah utama yang harus dipecahkan. Solusi perlu memperhitungkan kendala konsistensi Anda dengan klien lain yang ingin membaca acara segera setelah dipraktikkan setelah mereka diterima.
Untuk mengatasi masalah latensi, Anda harus bekerja dengan cara yang meminimalkan jumlah latensi yang terjadi per peristiwa yang akan disimpan. Ini adalah hal utama yang perlu Anda capai jika Anda tidak mau atau tidak dapat mengubah perangkat keras . Karena Anda menggunakan layanan PaaS dan tidak memiliki kendali atas perangkat keras atau jaringan, satu-satunya cara untuk mengurangi latensi per peristiwa adalah dengan semacam penulisan peristiwa yang dikumpulkan.
Anda perlu menyimpan antrian acara secara lokal yang akan memerah dan ditulis secara berkala ke db Anda, baik setelah mencapai ukuran tertentu, atau setelah jumlah waktu yang telah berlalu. Suatu proses perlu memonitor antrian ini untuk memicu flush ke toko. Seharusnya ada banyak contoh tentang cara mengelola antrian serentak yang disiram secara berkala dalam bahasa pilihan Anda - Berikut adalah contoh dalam C # , dari wastafel batching berkala perpustakaan Serilog yang populer.
Jawaban SO ini menjelaskan cara tercepat untuk menyiram data di Postgres - walaupun itu akan meminta batching Anda menyimpan antrian pada disk, dan kemungkinan ada masalah yang harus dipecahkan di sana ketika disk Anda hilang saat reboot di Heroku.
Paksaan
Jawaban lain telah disebutkan CQRS , dan itu adalah pendekatan yang benar untuk menyelesaikan kendala. Anda ingin melembabkan model yang sudah dibaca karena setiap peristiwa diproses - pola Mediator dapat membantu merangkum acara dan mendistribusikannya ke beberapa penangan yang sedang diproses. Jadi satu penangan dapat menambahkan acara ke model baca Anda yang ada dalam memori yang dapat ditanyakan oleh klien, dan penangan lainnya dapat bertanggung jawab untuk mengantri acara tersebut pada penulisan batch akhirnya.
Manfaat utama CQRS adalah Anda memisahkan model baca dan tulis konseptual Anda - yang merupakan cara mewah untuk mengatakan Anda menulis ke dalam satu model, dan Anda membaca dari model lain yang sama sekali berbeda. Untuk mendapatkan manfaat skalabilitas dari CQRS, Anda biasanya ingin memastikan setiap model disimpan secara terpisah dengan cara yang optimal untuk pola penggunaannya. Dalam hal ini kita dapat menggunakan model baca agregat - misalnya, cache Redis, atau hanya dalam memori - untuk memastikan pembacaan kita cepat dan konsisten, sementara kita masih menggunakan database transaksional untuk menulis data.
sumber
Ini adalah masalah jika setiap proses membutuhkan satu koneksi basis data. Sistem harus dirancang sehingga Anda memiliki kumpulan pekerja di mana setiap pekerja hanya membutuhkan satu koneksi basis data dan setiap pekerja dapat memproses beberapa peristiwa.
Antrian pesan dapat digunakan dengan desain itu, Anda memerlukan produsen pesan yang mendorong peristiwa ke antrian pesan dan pekerja (konsumen) memproses pesan dari antrian.
Batasan ini hanya mungkin jika peristiwa disimpan dalam basis data tanpa pemrosesan apa pun (peristiwa mentah). Jika acara diproses sebelum disimpan dalam database, maka satu-satunya cara untuk mendapatkan acara adalah dari database.
Jika klien hanya ingin menanyakan acara mentah maka saya akan menyarankan menggunakan mesin pencari seperti Pencarian Elastis. Anda bahkan akan mendapatkan API permintaan / pencarian secara gratis.
Karena tampaknya meminta pertanyaan sebelum disimpan dalam basis data penting bagi Anda, solusi sederhana seperti Elastic Search seharusnya berfungsi. Anda pada dasarnya hanya menyimpan semua peristiwa di dalamnya dan tidak menduplikasi data yang sama dengan menyalinnya ke dalam basis data.
Pencarian Skala Elastis itu mudah, tetapi bahkan dengan konfigurasi dasar itu adalah pemain yang cukup tinggi.
Saat Anda membutuhkan pemrosesan, proses Anda bisa mendapatkan acara dari ES, memproses dan menyimpannya dalam database. Saya tidak tahu apa tingkat kinerja yang Anda butuhkan dari pemrosesan ini, tetapi itu akan sepenuhnya terpisah dari permintaan acara dari ES. Anda seharusnya tidak memiliki masalah koneksi, karena Anda dapat memiliki jumlah pekerja yang tetap dan masing-masing dengan satu koneksi database.
sumber
Peristiwa 1k atau 2k (5KB) per detik tidak terlalu banyak untuk basis data jika memiliki skema dan mesin penyimpanan yang sesuai. Seperti yang disarankan oleh @eddyce, seorang master dengan satu atau lebih budak dapat memisahkan permintaan baca dari melakukan penulisan. Menggunakan lebih sedikit koneksi DB akan memberi Anda throughput keseluruhan yang lebih baik.
Untuk permintaan ini, mereka juga perlu membaca dari master db karena akan ada jeda replikasi pada slave yang sudah dibaca.
Saya telah menggunakan (Percona) MySQL dengan mesin TokuDB untuk penulisan volume yang sangat tinggi. Ada juga mesin MyRocks berdasarkan LSMtrees yang bagus untuk menulis banyak. Untuk kedua mesin ini dan kemungkinan juga PostgreSQL ada pengaturan untuk isolasi transaksi serta melakukan perilaku sinkronisasi yang secara dramatis dapat meningkatkan kapasitas menulis. Di masa lalu kami menerima hingga 1s data yang hilang yang dilaporkan ke klien db sebagai komitmen. Dalam kasus lain ada SSD yang didukung baterai untuk menghindari kehilangan.
Amazon RDS Aurora dalam citarasa MySQL diklaim memiliki throughput tulis 6x lebih tinggi dengan replikasi tanpa biaya (mirip dengan budak yang berbagi sistem file dengan master). Rasa Aurora PostgreSQL juga memiliki mekanisme replikasi canggih yang berbeda.
sumber
Saya akan menjatuhkan heroku bersama-sama, yaitu, saya akan menjatuhkan pendekatan terpusat: banyak menulis bahwa memuncak koneksi kolam maksimum adalah salah satu alasan utama mengapa cluster db ditemukan, terutama karena Anda tidak memuat tulisan db (s) dengan permintaan baca yang dapat dilakukan oleh db lain di cluster, saya akan mencoba dengan topologi master-slave, apalagi - seperti orang lain yang telah disebutkan, memiliki instalasi db Anda sendiri akan memungkinkan untuk menyempurnakan keseluruhan sistem untuk memastikan waktu propagasi kueri akan ditangani dengan benar.
Semoga berhasil
sumber